Skip to content

Commit d93293a

Browse files
authored
fix(e2e): stabilize local Docker smoke test (#1935)
* fix(docker): honor configured supervisor image Signed-off-by: Evan Lezar <elezar@nvidia.com> * fix(cli): isolate ssh from host linker environment Signed-off-by: Evan Lezar <elezar@nvidia.com> --------- Signed-off-by: Evan Lezar <elezar@nvidia.com>
1 parent c636e70 commit d93293a

5 files changed

Lines changed: 133 additions & 111 deletions

File tree

‎crates/openshell-driver-docker/README.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,11 @@ The Docker driver bind-mounts a host-side Linux `openshell-sandbox` binary into
7979
each sandbox container. Resolution order is:
8080

8181
1. `supervisor_bin` in `[openshell.drivers.docker]`.
82-
2. A sibling `openshell-sandbox` next to the running `openshell-gateway` binary.
83-
3. A local Linux cargo target build for the Docker daemon architecture.
84-
4. `supervisor_image` in `[openshell.drivers.docker]`, or the
85-
release-matched default supervisor image, extracting `/openshell-sandbox`.
82+
2. `supervisor_image` in `[openshell.drivers.docker]`, extracting
83+
`/openshell-sandbox` from that image.
84+
3. A sibling `openshell-sandbox` next to the running `openshell-gateway` binary.
85+
4. A local Linux cargo target build for the Docker daemon architecture.
86+
5. The release-matched default supervisor image, extracting `/openshell-sandbox`.
8687

8788
Release and Docker-image gateway builds bake the matching supervisor image tag
8889
into the binary at compile time. The default Docker supervisor image is not

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

Lines changed: 68 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ const DOCKER_NETWORK_DRIVER: &str = "bridge";
8080

8181
/// Default image holding the Linux `openshell-sandbox` binary. The gateway
8282
/// pulls this image and extracts the binary to a host-side cache when no
83-
/// explicit `supervisor_bin` override or local build is available.
83+
/// explicit `supervisor_bin`, configured `supervisor_image`, sibling binary,
84+
/// or local build is available.
8485
const DEFAULT_DOCKER_SUPERVISOR_IMAGE_REPO: &str = "ghcr.io/nvidia/openshell/supervisor";
8586

8687
/// Return the default `ghcr.io/nvidia/openshell/supervisor:<tag>` reference
@@ -156,10 +157,9 @@ pub struct DockerComputeConfig {
156157
/// Optional override for the Linux `openshell-sandbox` binary mounted into containers.
157158
pub supervisor_bin: Option<PathBuf>,
158159

159-
/// Optional override for the image the gateway pulls to extract the
160-
/// Linux `openshell-sandbox` binary when no explicit binary path or
161-
/// local build is available. Defaults to
162-
/// `ghcr.io/nvidia/openshell/supervisor:<gateway-image-tag>`.
160+
/// Optional image used to extract the Linux `openshell-sandbox` binary.
161+
/// Ignored when `supervisor_bin` is set. See `resolve_supervisor_bin` for
162+
/// the full resolution order.
163163
pub supervisor_image: Option<String>,
164164

165165
/// Host-side CA certificate for Docker sandbox mTLS.
@@ -2978,56 +2978,89 @@ fn normalize_docker_arch(arch: &str) -> String {
29782978
}
29792979
}
29802980

2981-
pub(crate) async fn resolve_supervisor_bin(
2982-
docker: &Docker,
2981+
#[derive(Debug, Eq, PartialEq)]
2982+
enum SupervisorBinSource {
2983+
Binary(PathBuf),
2984+
Image(String),
2985+
}
2986+
2987+
fn resolve_supervisor_bin_source(
29832988
docker_config: &DockerComputeConfig,
2984-
daemon_arch: &str,
2985-
) -> CoreResult<PathBuf> {
2989+
current_exe: Option<&Path>,
2990+
target_candidates: &[PathBuf],
2991+
) -> CoreResult<SupervisorBinSource> {
29862992
// Tier 1: explicit supervisor_bin in [openshell.drivers.docker].
29872993
if let Some(path) = docker_config.supervisor_bin.clone() {
29882994
let path = canonicalize_existing_file(&path, "docker supervisor binary")?;
29892995
validate_linux_elf_binary(&path)?;
2990-
return Ok(path);
2996+
return Ok(SupervisorBinSource::Binary(path));
2997+
}
2998+
2999+
// Tier 2: explicit supervisor_image in [openshell.drivers.docker].
3000+
// A configured image should be the source of truth even when a local
3001+
// developer build is present under target/.
3002+
if let Some(image) = docker_config.supervisor_image.clone() {
3003+
return Ok(SupervisorBinSource::Image(image));
29913004
}
29923005

2993-
// Tier 2: sibling `openshell-sandbox` next to the running gateway
3006+
// Tier 3: sibling `openshell-sandbox` next to the running gateway
29943007
// (release artifact layout). Linux-only because the sibling must be a
29953008
// Linux ELF to bind-mount into a Linux container.
2996-
if cfg!(target_os = "linux") {
2997-
let current_exe = std::env::current_exe()
2998-
.map_err(|err| Error::config(format!("failed to resolve current executable: {err}")))?;
2999-
if let Some(parent) = current_exe.parent() {
3000-
let sibling = parent.join("openshell-sandbox");
3001-
if sibling.is_file() {
3002-
let path = canonicalize_existing_file(&sibling, "docker supervisor binary")?;
3003-
if validate_linux_elf_binary(&path).is_ok() {
3004-
return Ok(path);
3005-
}
3009+
if cfg!(target_os = "linux")
3010+
&& let Some(current_exe) = current_exe
3011+
&& let Some(parent) = current_exe.parent()
3012+
{
3013+
let sibling = parent.join("openshell-sandbox");
3014+
if sibling.is_file() {
3015+
let path = canonicalize_existing_file(&sibling, "docker supervisor binary")?;
3016+
if validate_linux_elf_binary(&path).is_ok() {
3017+
return Ok(SupervisorBinSource::Binary(path));
30063018
}
30073019
}
30083020
}
30093021

3010-
// Tier 3: local cargo target build (developer workflow). Preferred
3011-
// over a registry pull when available because it matches whatever the
3012-
// developer just built.
3013-
let target_candidates = linux_supervisor_candidates(daemon_arch);
3014-
for candidate in &target_candidates {
3022+
// Tier 4: local cargo target build (developer workflow). Preferred
3023+
// over the default registry image when available because it matches
3024+
// whatever the developer just built.
3025+
for candidate in target_candidates {
30153026
if candidate.is_file() {
30163027
let path = canonicalize_existing_file(candidate, "docker supervisor binary")?;
30173028
if validate_linux_elf_binary(&path).is_ok() {
3018-
return Ok(path);
3029+
return Ok(SupervisorBinSource::Binary(path));
30193030
}
30203031
}
30213032
}
30223033

3023-
// Tier 4: pull the supervisor image from a registry and extract the
3024-
// binary to a host-side cache keyed by image content digest. This is
3025-
// the default path for released gateway binaries.
3026-
let image = docker_config
3027-
.supervisor_image
3028-
.clone()
3029-
.unwrap_or_else(default_docker_supervisor_image);
3030-
extract_supervisor_bin_from_image(docker, &image).await
3034+
// Tier 5: pull the release-matched default supervisor image and extract
3035+
// the binary to a host-side cache keyed by image content digest.
3036+
Ok(SupervisorBinSource::Image(default_docker_supervisor_image()))
3037+
}
3038+
3039+
pub(crate) async fn resolve_supervisor_bin(
3040+
docker: &Docker,
3041+
docker_config: &DockerComputeConfig,
3042+
daemon_arch: &str,
3043+
) -> CoreResult<PathBuf> {
3044+
let current_exe =
3045+
if cfg!(target_os = "linux")
3046+
&& docker_config.supervisor_bin.is_none()
3047+
&& docker_config.supervisor_image.is_none()
3048+
{
3049+
Some(std::env::current_exe().map_err(|err| {
3050+
Error::config(format!("failed to resolve current executable: {err}"))
3051+
})?)
3052+
} else {
3053+
None
3054+
};
3055+
let target_candidates = linux_supervisor_candidates(daemon_arch);
3056+
3057+
match resolve_supervisor_bin_source(docker_config, current_exe.as_deref(), &target_candidates)?
3058+
{
3059+
SupervisorBinSource::Binary(path) => Ok(path),
3060+
SupervisorBinSource::Image(image) => {
3061+
extract_supervisor_bin_from_image(docker, &image).await
3062+
}
3063+
}
30313064
}
30323065

30333066
fn linux_supervisor_candidates(daemon_arch: &str) -> Vec<PathBuf> {

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1865,6 +1865,36 @@ fn default_docker_supervisor_image_uses_nvidia_ghcr_repo() {
18651865
);
18661866
}
18671867

1868+
#[test]
1869+
fn configured_supervisor_image_takes_precedence_over_local_binaries() {
1870+
let tempdir = TempDir::new().unwrap();
1871+
let bin_dir = tempdir.path().join("bin");
1872+
fs::create_dir_all(&bin_dir).unwrap();
1873+
let current_exe = bin_dir.join("openshell-gateway");
1874+
let sibling = bin_dir.join("openshell-sandbox");
1875+
fs::write(&current_exe, b"gateway").unwrap();
1876+
fs::write(&sibling, b"\x7fELFsibling").unwrap();
1877+
1878+
let local_build = tempdir.path().join("target/openshell-sandbox");
1879+
fs::create_dir_all(local_build.parent().unwrap()).unwrap();
1880+
fs::write(&local_build, b"\x7fELFlocal").unwrap();
1881+
1882+
let source = resolve_supervisor_bin_source(
1883+
&DockerComputeConfig {
1884+
supervisor_image: Some("example.com/openshell/supervisor:test".to_string()),
1885+
..Default::default()
1886+
},
1887+
Some(&current_exe),
1888+
&[local_build],
1889+
)
1890+
.unwrap();
1891+
1892+
assert_eq!(
1893+
source,
1894+
SupervisorBinSource::Image("example.com/openshell/supervisor:test".to_string())
1895+
);
1896+
}
1897+
18681898
#[test]
18691899
fn docker_supervisor_image_tag_prefers_explicit_build_tags() {
18701900
assert_eq!(

‎docs/reference/gateway-config.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ sandbox_namespace = "docker-dev"
218218
grpc_endpoint = "https://host.openshell.internal:17670"
219219
# Skip the image-pull-and-extract step by pointing at a locally built binary.
220220
supervisor_bin = "/usr/local/libexec/openshell/openshell-sandbox"
221+
# When supervisor_bin is omitted, Docker extracts /openshell-sandbox from this image.
221222
supervisor_image = "ghcr.io/nvidia/openshell/supervisor:latest"
222223
guest_tls_ca = "/etc/openshell/certs/ca.pem"
223224
guest_tls_cert = "/etc/openshell/certs/client.pem"

‎e2e/with-docker-gateway.sh‎

Lines changed: 29 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ require_container_engine_lane() {
6464
}
6565

6666
require_container_engine_lane docker Docker
67+
CONTAINER_ENGINE_QUIET="${CONTAINER_ENGINE_QUIET:-1}"
68+
# shellcheck source=tasks/scripts/container-engine.sh
69+
source "${ROOT}/tasks/scripts/container-engine.sh"
6770

6871
github_actions_host_docker_tmpdir() {
6972
if [ "${GITHUB_ACTIONS:-}" != "true" ] \
@@ -111,7 +114,6 @@ DOCKER_NETWORK_NAME=""
111114
DOCKER_NETWORK_CONNECTED_CONTAINER=""
112115
DOCKER_NETWORK_MANAGED=0
113116
GPU_MODE="${OPENSHELL_E2E_DOCKER_GPU:-0}"
114-
DOCKER_SUPERVISOR_ARGS=()
115117

116118
# Isolate CLI/SDK gateway metadata from the developer's real config.
117119
export XDG_CONFIG_HOME="${WORKDIR}/config"
@@ -292,25 +294,6 @@ if [ "${GPU_MODE}" = "1" ]; then
292294
fi
293295
fi
294296

295-
normalize_arch() {
296-
case "$1" in
297-
x86_64|amd64) echo "amd64" ;;
298-
aarch64|arm64) echo "arm64" ;;
299-
*) echo "$1" ;;
300-
esac
301-
}
302-
303-
linux_target_triple() {
304-
case "$1" in
305-
amd64) echo "x86_64-unknown-linux-gnu" ;;
306-
arm64) echo "aarch64-unknown-linux-gnu" ;;
307-
*)
308-
echo "ERROR: unsupported Docker daemon architecture '$1'" >&2
309-
exit 2
310-
;;
311-
esac
312-
}
313-
314297
resolve_docker_supervisor_image() {
315298
if [ -n "${OPENSHELL_DOCKER_SUPERVISOR_IMAGE:-}" ]; then
316299
printf '%s\n' "${OPENSHELL_DOCKER_SUPERVISOR_IMAGE}"
@@ -333,7 +316,7 @@ resolve_docker_supervisor_image() {
333316
return 0
334317
fi
335318

336-
printf '%s\n' ""
319+
printf '%s\n' "openshell/supervisor:dev"
337320
}
338321

339322
docker_pull_with_retry() {
@@ -362,6 +345,27 @@ docker_pull_with_retry() {
362345
return 1
363346
}
364347

348+
build_local_docker_supervisor_image_if_required() {
349+
local image=$1
350+
351+
if [ "${image}" != "openshell/supervisor:dev" ]; then
352+
return 0
353+
fi
354+
355+
local daemon_arch
356+
daemon_arch="$(ce_info_arch)"
357+
358+
echo "Building local Docker supervisor image ${image} for linux/${daemon_arch}..."
359+
CONTAINER_ENGINE=docker DOCKER_PLATFORM="linux/${daemon_arch}" IMAGE_TAG=dev \
360+
bash "${ROOT}/tasks/scripts/docker-build-image.sh" supervisor
361+
if docker image inspect "${image}" >/dev/null 2>&1; then
362+
return 0
363+
fi
364+
365+
echo "ERROR: expected supervisor image '${image}' after local build." >&2
366+
exit 2
367+
}
368+
365369
ensure_docker_supervisor_image() {
366370
local image=$1
367371

@@ -414,47 +418,12 @@ ensure_sandbox_image_available() {
414418
docker_pull_with_retry "${image}"
415419
}
416420

417-
DAEMON_ARCH="$(normalize_arch "$(docker info --format '{{.Architecture}}' 2>/dev/null || true)")"
418-
SUPERVISOR_TARGET="$(linux_target_triple "${DAEMON_ARCH}")"
419-
HOST_OS="$(uname -s)"
420-
HOST_ARCH="$(normalize_arch "$(uname -m)")"
421-
SUPERVISOR_OUT_DIR="${WORKDIR}/supervisor/${DAEMON_ARCH}"
422-
SUPERVISOR_BIN="${SUPERVISOR_OUT_DIR}/openshell-sandbox"
423-
424-
CARGO_BUILD_JOBS_ARG=()
425-
if [ -n "${CARGO_BUILD_JOBS:-}" ]; then
426-
CARGO_BUILD_JOBS_ARG=(-j "${CARGO_BUILD_JOBS}")
427-
fi
428-
429421
e2e_build_gateway_binaries "${ROOT}" TARGET_DIR GATEWAY_BIN CLI_BIN
430422

431423
SUPERVISOR_IMAGE="$(resolve_docker_supervisor_image)"
432-
if [ -n "${SUPERVISOR_IMAGE}" ]; then
433-
ensure_docker_supervisor_image "${SUPERVISOR_IMAGE}"
434-
echo "Using Docker supervisor image: ${SUPERVISOR_IMAGE}"
435-
DOCKER_SUPERVISOR_ARGS=(--docker-supervisor-image "${SUPERVISOR_IMAGE}")
436-
else
437-
echo "Building openshell-sandbox for ${SUPERVISOR_TARGET}..."
438-
mkdir -p "${SUPERVISOR_OUT_DIR}"
439-
if [ "${HOST_OS}" = "Linux" ] && [ "${HOST_ARCH}" = "${DAEMON_ARCH}" ]; then
440-
rustup target add "${SUPERVISOR_TARGET}" >/dev/null 2>&1 || true
441-
cargo build ${CARGO_BUILD_JOBS_ARG[@]+"${CARGO_BUILD_JOBS_ARG[@]}"} \
442-
--release -p openshell-sandbox --target "${SUPERVISOR_TARGET}"
443-
cp "${TARGET_DIR}/${SUPERVISOR_TARGET}/release/openshell-sandbox" "${SUPERVISOR_BIN}"
444-
else
445-
CONTAINER_ENGINE=docker \
446-
DOCKER_PLATFORM="linux/${DAEMON_ARCH}" \
447-
DOCKER_OUTPUT="type=local,dest=${SUPERVISOR_OUT_DIR}" \
448-
bash "${ROOT}/tasks/scripts/docker-build-image.sh" supervisor-output
449-
fi
450-
451-
if [ ! -f "${SUPERVISOR_BIN}" ]; then
452-
echo "ERROR: expected supervisor binary at ${SUPERVISOR_BIN}" >&2
453-
exit 1
454-
fi
455-
chmod +x "${SUPERVISOR_BIN}"
456-
DOCKER_SUPERVISOR_ARGS=(--docker-supervisor-bin "${SUPERVISOR_BIN}")
457-
fi
424+
build_local_docker_supervisor_image_if_required "${SUPERVISOR_IMAGE}"
425+
ensure_docker_supervisor_image "${SUPERVISOR_IMAGE}"
426+
echo "Using Docker supervisor image: ${SUPERVISOR_IMAGE}"
458427

459428
DEFAULT_SANDBOX_IMAGE="ghcr.io/nvidia/openshell-community/sandboxes/base:latest"
460429
SANDBOX_IMAGE="${OPENSHELL_E2E_DOCKER_SANDBOX_IMAGE:-${OPENSHELL_SANDBOX_IMAGE:-${DEFAULT_SANDBOX_IMAGE}}}"
@@ -521,19 +490,7 @@ GATEWAY_CONFIG="${STATE_DIR}/gateway.toml"
521490
printf 'guest_tls_cert = %s\n' "$(toml_string "${PKI_DIR}/client/tls.crt")"
522491
printf 'guest_tls_key = %s\n' "$(toml_string "${PKI_DIR}/client/tls.key")"
523492
printf 'enable_bind_mounts = true\n'
524-
# DOCKER_SUPERVISOR_ARGS holds either ("--docker-supervisor-bin" "<path>")
525-
# or ("--docker-supervisor-image" "<image>"); both map to TOML keys on
526-
# the docker driver config.
527-
for ((i=0; i<${#DOCKER_SUPERVISOR_ARGS[@]}; i+=2)); do
528-
case "${DOCKER_SUPERVISOR_ARGS[$i]}" in
529-
--docker-supervisor-bin)
530-
printf 'supervisor_bin = %s\n' "$(toml_string "${DOCKER_SUPERVISOR_ARGS[$((i+1))]}")"
531-
;;
532-
--docker-supervisor-image)
533-
printf 'supervisor_image = %s\n' "$(toml_string "${DOCKER_SUPERVISOR_ARGS[$((i+1))]}")"
534-
;;
535-
esac
536-
done
493+
printf 'supervisor_image = %s\n' "$(toml_string "${SUPERVISOR_IMAGE}")"
537494
if [ -n "${GATEWAY_HOST_ALIAS_IP}" ]; then
538495
printf 'host_gateway_ip = %s\n' "$(toml_string "${GATEWAY_HOST_ALIAS_IP}")"
539496
fi

0 commit comments

Comments
 (0)