Skip to content

Commit d001f29

Browse files
committed
fix(e2e): route docker gateway callbacks in CI
1 parent 7a8fb2f commit d001f29

3 files changed

Lines changed: 178 additions & 5 deletions

File tree

crates/openshell-driver-docker/src/lib.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,9 @@ impl DockerComputeDriver {
231231
}
232232
let network_name = docker_network_name(docker_config);
233233
let bridge_gateway_ip = ensure_bridge_network(&docker, &network_name).await?;
234-
let gateway_route = docker_gateway_route(&info, bridge_gateway_ip, gateway_port);
234+
let host_gateway_ip = parse_optional_host_gateway_ip(&config.host_gateway_ip)?;
235+
let gateway_route =
236+
docker_gateway_route(&info, bridge_gateway_ip, gateway_port, host_gateway_ip);
235237
let grpc_endpoint = docker_container_openshell_endpoint(
236238
&config.grpc_endpoint,
237239
HOST_OPENSHELL_INTERNAL,
@@ -1064,11 +1066,32 @@ fn docker_network_name(config: &DockerComputeConfig) -> String {
10641066
name.to_string()
10651067
}
10661068

1069+
fn parse_optional_host_gateway_ip(value: &str) -> CoreResult<Option<IpAddr>> {
1070+
let trimmed = value.trim();
1071+
if trimmed.is_empty() {
1072+
return Ok(None);
1073+
}
1074+
1075+
trimmed.parse().map(Some).map_err(|err| {
1076+
Error::config(format!(
1077+
"invalid OPENSHELL_HOST_GATEWAY_IP value '{trimmed}': {err}"
1078+
))
1079+
})
1080+
}
1081+
10671082
fn docker_gateway_route(
10681083
info: &SystemInfo,
10691084
bridge_gateway_ip: IpAddr,
10701085
port: u16,
1086+
host_gateway_ip: Option<IpAddr>,
10711087
) -> DockerGatewayRoute {
1088+
if let Some(host_alias_ip) = host_gateway_ip {
1089+
return DockerGatewayRoute::Bridge {
1090+
bind_address: SocketAddr::new(host_alias_ip, port),
1091+
host_alias_ip,
1092+
};
1093+
}
1094+
10721095
if is_docker_desktop(info) {
10731096
DockerGatewayRoute::HostGateway
10741097
} else {

crates/openshell-driver-docker/src/tests.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ fn docker_gateway_route_uses_host_gateway_for_docker_desktop() {
154154
&info,
155155
IpAddr::V4(Ipv4Addr::new(172, 18, 0, 1)),
156156
DEFAULT_SERVER_PORT,
157+
None,
157158
),
158159
DockerGatewayRoute::HostGateway
159160
);
@@ -174,6 +175,7 @@ fn docker_gateway_route_uses_bridge_gateway_for_linux_docker() {
174175
&info,
175176
IpAddr::V4(Ipv4Addr::new(172, 18, 0, 1)),
176177
DEFAULT_SERVER_PORT,
178+
None,
177179
);
178180

179181
assert_eq!(
@@ -192,6 +194,51 @@ fn docker_gateway_route_uses_bridge_gateway_for_linux_docker() {
192194
);
193195
}
194196

197+
#[test]
198+
fn docker_gateway_route_prefers_configured_host_gateway_ip() {
199+
let info = SystemInfo {
200+
operating_system: Some("Ubuntu 24.04 LTS".to_string()),
201+
..Default::default()
202+
};
203+
204+
let route = docker_gateway_route(
205+
&info,
206+
IpAddr::V4(Ipv4Addr::new(172, 18, 0, 1)),
207+
DEFAULT_SERVER_PORT,
208+
Some(IpAddr::V4(Ipv4Addr::new(172, 20, 0, 4))),
209+
);
210+
211+
assert_eq!(
212+
route,
213+
DockerGatewayRoute::Bridge {
214+
bind_address: "172.20.0.4:8080".parse().unwrap(),
215+
host_alias_ip: IpAddr::V4(Ipv4Addr::new(172, 20, 0, 4)),
216+
}
217+
);
218+
assert_eq!(
219+
docker_extra_hosts(&route),
220+
vec![
221+
"host.docker.internal:172.20.0.4".to_string(),
222+
"host.openshell.internal:172.20.0.4".to_string()
223+
]
224+
);
225+
}
226+
227+
#[test]
228+
fn parse_optional_host_gateway_ip_rejects_invalid_values() {
229+
assert_eq!(parse_optional_host_gateway_ip("").unwrap(), None);
230+
assert_eq!(
231+
parse_optional_host_gateway_ip("172.20.0.4").unwrap(),
232+
Some(IpAddr::V4(Ipv4Addr::new(172, 20, 0, 4)))
233+
);
234+
assert!(
235+
parse_optional_host_gateway_ip("not-an-ip")
236+
.unwrap_err()
237+
.to_string()
238+
.contains("OPENSHELL_HOST_GATEWAY_IP")
239+
);
240+
}
241+
195242
#[test]
196243
fn parse_cpu_limit_supports_cores_and_millicores() {
197244
assert_eq!(parse_cpu_limit("250m").unwrap(), Some(250_000_000));

e2e/with-docker-gateway.sh

Lines changed: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ GATEWAY_PID=""
6161
GATEWAY_LOG="${WORKDIR}/gateway.log"
6262
GATEWAY_CONFIG_DIR=""
6363
E2E_NAMESPACE=""
64+
DOCKER_NETWORK_NAME=""
65+
DOCKER_NETWORK_CONNECTED_CONTAINER=""
66+
DOCKER_NETWORK_MANAGED=0
6467
GPU_MODE="${OPENSHELL_E2E_DOCKER_GPU:-0}"
6568

6669
# Isolate CLI/SDK gateway metadata from the developer's real config.
@@ -107,6 +110,20 @@ cleanup() {
107110
fi
108111
fi
109112

113+
if [ -n "${DOCKER_NETWORK_CONNECTED_CONTAINER}" ] \
114+
&& [ -n "${DOCKER_NETWORK_NAME}" ] \
115+
&& command -v docker >/dev/null 2>&1; then
116+
docker network disconnect -f \
117+
"${DOCKER_NETWORK_NAME}" \
118+
"${DOCKER_NETWORK_CONNECTED_CONTAINER}" >/dev/null 2>&1 || true
119+
fi
120+
121+
if [ "${DOCKER_NETWORK_MANAGED}" = "1" ] \
122+
&& [ -n "${DOCKER_NETWORK_NAME}" ] \
123+
&& command -v docker >/dev/null 2>&1; then
124+
docker network rm "${DOCKER_NETWORK_NAME}" >/dev/null 2>&1 || true
125+
fi
126+
110127
if [ "${exit_code}" -ne 0 ] && [ -f "${GATEWAY_LOG}" ]; then
111128
echo "=== gateway log (preserved for debugging) ==="
112129
cat "${GATEWAY_LOG}"
@@ -172,6 +189,70 @@ pick_port() {
172189
python3 -c 'import socket; s=socket.socket(); s.bind(("",0)); print(s.getsockname()[1]); s.close()'
173190
}
174191

192+
ensure_e2e_docker_network() {
193+
local network=$1
194+
195+
if docker network inspect "${network}" >/dev/null 2>&1; then
196+
return 0
197+
fi
198+
199+
docker network create \
200+
--driver bridge \
201+
--attachable \
202+
--label openshell.ai/managed-by=openshell \
203+
--label "openshell.ai/sandbox-namespace=${E2E_NAMESPACE}" \
204+
"${network}" >/dev/null
205+
DOCKER_NETWORK_MANAGED=1
206+
}
207+
208+
github_actions_container_id() {
209+
if [ "${GITHUB_ACTIONS:-}" != "true" ] || [ ! -f /.dockerenv ]; then
210+
return 1
211+
fi
212+
213+
local container
214+
container="$(hostname)"
215+
if docker inspect "${container}" >/dev/null 2>&1; then
216+
printf '%s\n' "${container}"
217+
return 0
218+
fi
219+
220+
return 1
221+
}
222+
223+
connect_current_container_to_docker_network() {
224+
local network=$1
225+
local container
226+
227+
if ! container="$(github_actions_container_id)"; then
228+
return 1
229+
fi
230+
231+
local connect_err="${WORKDIR}/docker-network-connect.err"
232+
if ! docker network connect \
233+
--alias host.openshell.internal \
234+
"${network}" \
235+
"${container}" 2>"${connect_err}"; then
236+
if ! grep -qi "already exists" "${connect_err}"; then
237+
cat "${connect_err}" >&2
238+
return 1
239+
fi
240+
fi
241+
242+
DOCKER_NETWORK_CONNECTED_CONTAINER="${container}"
243+
244+
local container_ip
245+
container_ip="$(docker inspect \
246+
--format "{{with index .NetworkSettings.Networks \"${network}\"}}{{.IPAddress}}{{end}}" \
247+
"${container}")"
248+
if [ -z "${container_ip}" ]; then
249+
echo "ERROR: failed to resolve current job container IP on Docker network ${network}" >&2
250+
return 1
251+
fi
252+
253+
GATEWAY_HOST_ALIAS_IP="${container_ip}"
254+
}
255+
175256
if [ -n "${OPENSHELL_GATEWAY_ENDPOINT:-}" ]; then
176257
case "${OPENSHELL_GATEWAY_ENDPOINT}" in
177258
http://*) ;;
@@ -327,13 +408,23 @@ mkdir -p "${STATE_DIR}"
327408

328409
GATEWAY_ENDPOINT="https://host.openshell.internal:${HOST_PORT}"
329410
E2E_NAMESPACE="e2e-docker-$$-${HOST_PORT}"
411+
DOCKER_NETWORK_NAME="${E2E_NAMESPACE}"
412+
GATEWAY_HOST_ALIAS_IP=""
413+
414+
ensure_e2e_docker_network "${DOCKER_NETWORK_NAME}"
415+
if connect_current_container_to_docker_network "${DOCKER_NETWORK_NAME}"; then
416+
echo "Connected CI job container to Docker network ${DOCKER_NETWORK_NAME} (${GATEWAY_HOST_ALIAS_IP})."
417+
else
418+
GATEWAY_HOST_ALIAS_IP=""
419+
fi
330420

331421
echo "Starting openshell-gateway on port ${HOST_PORT} (namespace: ${E2E_NAMESPACE})..."
332-
"${GATEWAY_BIN}" \
422+
GATEWAY_ARGS=(
333423
--bind-address 0.0.0.0 \
334424
--port "${HOST_PORT}" \
335425
--drivers docker \
336426
--sandbox-namespace "${E2E_NAMESPACE}" \
427+
--docker-network-name "${DOCKER_NETWORK_NAME}" \
337428
--tls-cert "${PKI_DIR}/server.crt" \
338429
--tls-key "${PKI_DIR}/server.key" \
339430
--tls-client-ca "${PKI_DIR}/ca.crt" \
@@ -344,8 +435,12 @@ echo "Starting openshell-gateway on port ${HOST_PORT} (namespace: ${E2E_NAMESPAC
344435
--docker-tls-cert "${PKI_DIR}/client.crt" \
345436
--docker-tls-key "${PKI_DIR}/client.key" \
346437
--sandbox-image "${SANDBOX_IMAGE}" \
347-
--sandbox-image-pull-policy IfNotPresent \
348-
>"${GATEWAY_LOG}" 2>&1 &
438+
--sandbox-image-pull-policy IfNotPresent
439+
)
440+
if [ -n "${GATEWAY_HOST_ALIAS_IP}" ]; then
441+
GATEWAY_ARGS+=(--host-gateway-ip "${GATEWAY_HOST_ALIAS_IP}")
442+
fi
443+
"${GATEWAY_BIN}" "${GATEWAY_ARGS[@]}" >"${GATEWAY_LOG}" 2>&1 &
349444
GATEWAY_PID=$!
350445

351446
GATEWAY_NAME="openshell-e2e-docker-${HOST_PORT}"
@@ -358,12 +453,13 @@ export OPENSHELL_PROVISION_TIMEOUT="${OPENSHELL_PROVISION_TIMEOUT:-180}"
358453
echo "Waiting for gateway to become healthy..."
359454
elapsed=0
360455
timeout=120
456+
last_status_output=""
361457
while [ "${elapsed}" -lt "${timeout}" ]; do
362458
if ! kill -0 "${GATEWAY_PID}" 2>/dev/null; then
363459
echo "ERROR: openshell-gateway exited before becoming healthy"
364460
exit 1
365461
fi
366-
if "${CLI_BIN}" status >/dev/null 2>&1; then
462+
if last_status_output="$("${CLI_BIN}" status 2>&1)"; then
367463
echo "Gateway healthy after ${elapsed}s."
368464
break
369465
fi
@@ -372,6 +468,13 @@ while [ "${elapsed}" -lt "${timeout}" ]; do
372468
done
373469
if [ "${elapsed}" -ge "${timeout}" ]; then
374470
echo "ERROR: gateway did not become healthy within ${timeout}s"
471+
echo "=== last openshell status output ==="
472+
if [ -n "${last_status_output}" ]; then
473+
printf '%s\n' "${last_status_output}"
474+
else
475+
echo "<no output>"
476+
fi
477+
echo "=== end openshell status output ==="
375478
exit 1
376479
fi
377480

0 commit comments

Comments
 (0)