mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-02 07:34:45 +08:00
refactor(runtime): retire Community image dependencies (#3386)
* feat(sandbox): default to official Alpine sandbox image default_sandbox_image() now returns docker.io/library/alpine:3.22, a generic version-qualified official image, so a fresh install no longer depends on the community sandbox image catalog. All compute drivers (docker, podman, kubernetes, vm) inherit this fallback. Part of #3116. Signed-off-by: Akram Signed-off-by: Akram <akram.benaissi@gmail.com> * feat(deploy): default deployment configs to the official Alpine sandbox image Update the shared gateway default_image, Helm chart values, the standalone Kubernetes manifest, and the dev gateway task scripts to use docker.io/library/alpine:3.22 instead of the community base image, consistent with default_sandbox_image(). GPU e2e image-build base is left unchanged (CUDA needs a glibc base). Part of #3116. Signed-off-by: Akram Signed-off-by: Akram <akram.benaissi@gmail.com> * feat(driver): default to numeric non-root identity for USER-less images With the default sandbox image now Alpine, images that declare no OCI USER must start instead of being rejected. When the image declares no USER and the policy requests none, the Podman and Docker drivers now supply a numeric non-root identity (DEFAULT_SANDBOX_UID/GID = 1000) instead of rejecting, matching the numeric-identity behavior of the Kubernetes and VM drivers. The supervisor's resolved-identity path runs the sandbox as a synthesized non-root account without the account existing in the image. Images that declare a USER keep the OCI resolution path unchanged. Part of #3116. Signed-off-by: Akram <akram.benaissi@gmail.com> Signed-off-by: Evan Lezar <elezar@nvidia.com> * test(conformance): use Alpine workload image Signed-off-by: Evan Lezar <elezar@nvidia.com> * refactor(policy): drop community image /app path from default policy The restrictive default policy granted read-only access to /app, a directory that only existed in the community base image. A generic Alpine default has no /app, so remove it. Landlock best-effort already ignores absent paths; this just stops advertising a community-specific layout in the default. Part of #3116. Signed-off-by: Akram Signed-off-by: Akram <akram.benaissi@gmail.com> * docs(config): document Alpine default images Signed-off-by: Evan Lezar <elezar@nvidia.com> * fix(podman): report early sandbox termination Signed-off-by: Evan Lezar <elezar@nvidia.com> * fix(podman): initialize rootless workspace ownership Signed-off-by: Evan Lezar <elezar@nvidia.com> * fix(sandbox): qualify NVIDIA Ubuntu default Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): initialize rootful default workspace Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(sftp): add native sandbox adapter Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sftp): gate runtime helper support to Linux Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sftp): support standard OpenSSH file operations Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sftp): harden rename and special file handling Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(runtime): remove community image dependencies Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(e2e): build provider readiness tool fixture Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(e2e): use a dedicated Noble fixture for Docker tests Signed-off-by: Evan Lezar <elezar@nvidia.com> --------- Signed-off-by: Akram Signed-off-by: Akram <akram.benaissi@gmail.com> Signed-off-by: Evan Lezar <elezar@nvidia.com> Signed-off-by: Drew Newberry <anewberry@nvidia.com> Co-authored-by: Evan Lezar <elezar@nvidia.com> Co-authored-by: Drew Newberry <anewberry@nvidia.com>
This commit is contained in:
co-authored by
Evan Lezar
Drew Newberry
parent
24706c175b
commit
50230616d5
@@ -20,7 +20,7 @@ kid_path = ".cache/openshell-e2e/gateway-jwt/kid"
|
||||
gateway_id = "openshell-e2e"
|
||||
|
||||
[openshell.drivers.docker]
|
||||
default_image = "ghcr.io/nvidia/openshell-community/sandboxes/base:latest"
|
||||
default_image = "nvcr.io/nvidia/base/ubuntu:24.04"
|
||||
image_pull_policy = "if_not_present"
|
||||
sandbox_label = "openshell-e2e"
|
||||
sandbox_runtime_image = "localhost/openshell/sandbox:e2e-vm"
|
||||
|
||||
@@ -20,7 +20,7 @@ kid_path = ".cache/openshell-e2e/gateway-jwt/kid"
|
||||
gateway_id = "openshell-e2e"
|
||||
|
||||
[openshell.drivers.podman]
|
||||
default_image = "ghcr.io/nvidia/openshell-community/sandboxes/base:latest"
|
||||
default_image = "nvcr.io/nvidia/base/ubuntu:24.04"
|
||||
image_pull_policy = "if_not_present"
|
||||
health_check_interval_secs = 10
|
||||
network_name = "openshell-e2e"
|
||||
|
||||
@@ -53,11 +53,11 @@
|
||||
"supervisor_base_image_digest": "sha256:7c8cb692ae09657cbc4a3f3cbd0e8d5a2690ba38386aaaf252dbb060bf5eb2e6",
|
||||
"supervisor_base_runtime_image": "docker.io/library/alpine@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce",
|
||||
"supervisor_package_manifest_sha256": "6966057ffaf1ef0d4617d413dad53646bcd049a4b08f572796392fb0ffbb01b9",
|
||||
"sandbox_image_request": "ghcr.io/nvidia/openshell-community/sandboxes/base@sha256:c2a43bb0d765774e2790b3babfb20997bb2eac7b4bf4c6d7d8661e99817bf904",
|
||||
"sandbox_image_request": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c",
|
||||
"sandbox_image_id": "65fa5d3d598a07d385ddbea41bf593be15af306337b76255b40705287cebcbbf",
|
||||
"sandbox_image_digest": "sha256:c2a43bb0d765774e2790b3babfb20997bb2eac7b4bf4c6d7d8661e99817bf904",
|
||||
"sandbox_runtime_image": "ghcr.io/nvidia/openshell-community/sandboxes/base@sha256:c2a43bb0d765774e2790b3babfb20997bb2eac7b4bf4c6d7d8661e99817bf904",
|
||||
"sandbox_client_image_alias": "ghcr.io/nvidia/openshell-community/sandboxes/base:latest",
|
||||
"sandbox_image_digest": "sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c",
|
||||
"sandbox_runtime_image": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c",
|
||||
"sandbox_client_image_alias": "nvcr.io/nvidia/base/ubuntu:24.04",
|
||||
"sandbox_client_image_alias_id": "65fa5d3d598a07d385ddbea41bf593be15af306337b76255b40705287cebcbbf",
|
||||
"gateway_sha256_before_execution": "264cf3d809bd1d54633bce9251ec1a5540b567b8150640091df85bdfbe61797a",
|
||||
"cli_sha256_before_execution": "3ad0ec143bf6850af780bf643c303242956d6ef1066d4a9372bc62fef33ada20",
|
||||
@@ -121,11 +121,11 @@
|
||||
"supervisor_base_image_digest": "sha256:7c8cb692ae09657cbc4a3f3cbd0e8d5a2690ba38386aaaf252dbb060bf5eb2e6",
|
||||
"supervisor_base_runtime_image": "docker.io/library/alpine@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce",
|
||||
"supervisor_package_manifest_sha256": "6966057ffaf1ef0d4617d413dad53646bcd049a4b08f572796392fb0ffbb01b9",
|
||||
"sandbox_image_request": "ghcr.io/nvidia/openshell-community/sandboxes/base@sha256:c2a43bb0d765774e2790b3babfb20997bb2eac7b4bf4c6d7d8661e99817bf904",
|
||||
"sandbox_image_request": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c",
|
||||
"sandbox_image_id": "65fa5d3d598a07d385ddbea41bf593be15af306337b76255b40705287cebcbbf",
|
||||
"sandbox_image_digest": "sha256:c2a43bb0d765774e2790b3babfb20997bb2eac7b4bf4c6d7d8661e99817bf904",
|
||||
"sandbox_runtime_image": "ghcr.io/nvidia/openshell-community/sandboxes/base@sha256:c2a43bb0d765774e2790b3babfb20997bb2eac7b4bf4c6d7d8661e99817bf904",
|
||||
"sandbox_client_image_alias": "ghcr.io/nvidia/openshell-community/sandboxes/base:latest",
|
||||
"sandbox_image_digest": "sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c",
|
||||
"sandbox_runtime_image": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c",
|
||||
"sandbox_client_image_alias": "nvcr.io/nvidia/base/ubuntu:24.04",
|
||||
"sandbox_client_image_alias_id": "65fa5d3d598a07d385ddbea41bf593be15af306337b76255b40705287cebcbbf",
|
||||
"gateway_sha256_before_execution": "2bd42b145f0438f48a579b010537f501e036035b0e22a4ff70e37db733a6e6ff",
|
||||
"cli_sha256_before_execution": "82827d9068f3e9c75681a804f8fb48274ecf8acb179a46f179fb9fbfe828f69a",
|
||||
@@ -196,11 +196,11 @@
|
||||
"supervisor_base_image_digest": "sha256:7c8cb692ae09657cbc4a3f3cbd0e8d5a2690ba38386aaaf252dbb060bf5eb2e6",
|
||||
"supervisor_base_runtime_image": "docker.io/library/alpine@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce",
|
||||
"supervisor_package_manifest_sha256": "6966057ffaf1ef0d4617d413dad53646bcd049a4b08f572796392fb0ffbb01b9",
|
||||
"sandbox_image_request": "ghcr.io/nvidia/openshell-community/sandboxes/base@sha256:c2a43bb0d765774e2790b3babfb20997bb2eac7b4bf4c6d7d8661e99817bf904",
|
||||
"sandbox_image_request": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c",
|
||||
"sandbox_image_id": "65fa5d3d598a07d385ddbea41bf593be15af306337b76255b40705287cebcbbf",
|
||||
"sandbox_image_digest": "sha256:c2a43bb0d765774e2790b3babfb20997bb2eac7b4bf4c6d7d8661e99817bf904",
|
||||
"sandbox_runtime_image": "ghcr.io/nvidia/openshell-community/sandboxes/base@sha256:c2a43bb0d765774e2790b3babfb20997bb2eac7b4bf4c6d7d8661e99817bf904",
|
||||
"sandbox_client_image_alias": "ghcr.io/nvidia/openshell-community/sandboxes/base:latest",
|
||||
"sandbox_image_digest": "sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c",
|
||||
"sandbox_runtime_image": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c",
|
||||
"sandbox_client_image_alias": "nvcr.io/nvidia/base/ubuntu:24.04",
|
||||
"sandbox_client_image_alias_id": "65fa5d3d598a07d385ddbea41bf593be15af306337b76255b40705287cebcbbf",
|
||||
"gateway_sha256_before_execution": "5cc2f700d93dde5dd09060d9c9190ae44a99440b1399530a2b23941ec4e88f85",
|
||||
"cli_sha256_before_execution": "3ad0ec143bf6850af780bf643c303242956d6ef1066d4a9372bc62fef33ada20",
|
||||
@@ -218,7 +218,7 @@
|
||||
"external_driver_environment": {
|
||||
"OPENSHELL_COMPUTE_DRIVER_SOCKET": "/tmp/openshell-e2e-podman.H8oMaO/compute-driver.sock",
|
||||
"OPENSHELL_PODMAN_SOCKET": "/tmp/openshell-e2e-podman.H8oMaO/podman/podman.sock",
|
||||
"OPENSHELL_SANDBOX_IMAGE": "ghcr.io/nvidia/openshell-community/sandboxes/base@sha256:c2a43bb0d765774e2790b3babfb20997bb2eac7b4bf4c6d7d8661e99817bf904",
|
||||
"OPENSHELL_SANDBOX_IMAGE": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c",
|
||||
"OPENSHELL_SANDBOX_IMAGE_PULL_POLICY": "missing",
|
||||
"OPENSHELL_HEALTH_CHECK_INTERVAL_SECS": 10,
|
||||
"OPENSHELL_GRPC_ENDPOINT": "https://host.containers.internal:50871",
|
||||
@@ -290,11 +290,11 @@
|
||||
"supervisor_base_image_digest": "sha256:7c8cb692ae09657cbc4a3f3cbd0e8d5a2690ba38386aaaf252dbb060bf5eb2e6",
|
||||
"supervisor_base_runtime_image": "docker.io/library/alpine@sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce",
|
||||
"supervisor_package_manifest_sha256": "6966057ffaf1ef0d4617d413dad53646bcd049a4b08f572796392fb0ffbb01b9",
|
||||
"sandbox_image_request": "ghcr.io/nvidia/openshell-community/sandboxes/base@sha256:c2a43bb0d765774e2790b3babfb20997bb2eac7b4bf4c6d7d8661e99817bf904",
|
||||
"sandbox_image_request": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c",
|
||||
"sandbox_image_id": "65fa5d3d598a07d385ddbea41bf593be15af306337b76255b40705287cebcbbf",
|
||||
"sandbox_image_digest": "sha256:c2a43bb0d765774e2790b3babfb20997bb2eac7b4bf4c6d7d8661e99817bf904",
|
||||
"sandbox_runtime_image": "ghcr.io/nvidia/openshell-community/sandboxes/base@sha256:c2a43bb0d765774e2790b3babfb20997bb2eac7b4bf4c6d7d8661e99817bf904",
|
||||
"sandbox_client_image_alias": "ghcr.io/nvidia/openshell-community/sandboxes/base:latest",
|
||||
"sandbox_image_digest": "sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c",
|
||||
"sandbox_runtime_image": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c",
|
||||
"sandbox_client_image_alias": "nvcr.io/nvidia/base/ubuntu:24.04",
|
||||
"sandbox_client_image_alias_id": "65fa5d3d598a07d385ddbea41bf593be15af306337b76255b40705287cebcbbf",
|
||||
"gateway_sha256_before_execution": "fdefc047c1b675c311b1796db9f1b1a944456fc34b76547efc0352c6a75a7707",
|
||||
"cli_sha256_before_execution": "82827d9068f3e9c75681a804f8fb48274ecf8acb179a46f179fb9fbfe828f69a",
|
||||
@@ -312,7 +312,7 @@
|
||||
"external_driver_environment": {
|
||||
"OPENSHELL_COMPUTE_DRIVER_SOCKET": "/tmp/openshell-e2e-podman.M8xsgQ/compute-driver.sock",
|
||||
"OPENSHELL_PODMAN_SOCKET": "/tmp/openshell-e2e-podman.M8xsgQ/podman/podman.sock",
|
||||
"OPENSHELL_SANDBOX_IMAGE": "ghcr.io/nvidia/openshell-community/sandboxes/base@sha256:c2a43bb0d765774e2790b3babfb20997bb2eac7b4bf4c6d7d8661e99817bf904",
|
||||
"OPENSHELL_SANDBOX_IMAGE": "nvcr.io/nvidia/base/ubuntu@sha256:c280ee89f8bfcbdaba6179ad4347f60c509e841cbe63eb93002f01bf70e0819c",
|
||||
"OPENSHELL_SANDBOX_IMAGE_PULL_POLICY": "if_not_present",
|
||||
"OPENSHELL_HEALTH_CHECK_INTERVAL_SECS": 10,
|
||||
"OPENSHELL_GRPC_ENDPOINT": "https://host.containers.internal:32901",
|
||||
|
||||
@@ -17,7 +17,7 @@ ENV OPENSHELL_COMPUTE_DRIVER=kubernetes \
|
||||
OPENSHELL_GATEWAY_ID=openshell \
|
||||
OPENSHELL_SANDBOX_NAMESPACE=openshell \
|
||||
OPENSHELL_K8S_SANDBOX_SERVICE_ACCOUNT=openshell-sandbox \
|
||||
OPENSHELL_SANDBOX_IMAGE=ghcr.io/nvidia/openshell-community/sandboxes/base:latest \
|
||||
OPENSHELL_SANDBOX_IMAGE=nvcr.io/nvidia/base/ubuntu:24.04 \
|
||||
OPENSHELL_SANDBOX_IMAGE_PULL_POLICY=if_not_present \
|
||||
OPENSHELL_GRPC_ENDPOINT=http://openshell.openshell.svc.cluster.local:8080 \
|
||||
OPENSHELL_SUPERVISOR_IMAGE=${SUPERVISOR_IMAGE} \
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
ARG CUDA_BUILD_IMAGE=nvcr.io/nvidia/cuda:12.8.1-base-ubuntu22.04
|
||||
ARG OPENSHELL_SANDBOX_BASE_IMAGE=ghcr.io/nvidia/openshell-community/sandboxes/base:latest
|
||||
ARG OPENSHELL_SANDBOX_BASE_IMAGE=nvcr.io/nvidia/base/ubuntu:24.04
|
||||
|
||||
FROM ${CUDA_BUILD_IMAGE} AS builder
|
||||
|
||||
@@ -68,5 +68,5 @@ RUN chmod 0755 /usr/local/bin/openshell-gpu-workload \
|
||||
/usr/local/lib/openshell-gpu-workload/deviceQuery \
|
||||
/usr/local/lib/openshell-gpu-workload/vectorAdd
|
||||
|
||||
USER sandbox
|
||||
USER 1000:1000
|
||||
ENTRYPOINT ["/usr/local/bin/openshell-gpu-workload"]
|
||||
|
||||
@@ -12,7 +12,7 @@ runtime workload. It is a single image that runs two validation steps:
|
||||
|
||||
The image builds the samples from `NVIDIA/cuda-samples` tag `v12.8` with a CUDA
|
||||
12.8 builder image, then copies only the compiled binaries into the OpenShell
|
||||
community base final image.
|
||||
default workload final image.
|
||||
|
||||
The workload prints `OPENSHELL_GPU_WORKLOAD_SUCCESS` only after both samples
|
||||
pass. On failure it prints `OPENSHELL_GPU_WORKLOAD_FAILURE` and exits non-zero.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
ARG OPENSHELL_SANDBOX_BASE_IMAGE=ghcr.io/nvidia/openshell-community/sandboxes/base:latest
|
||||
ARG OPENSHELL_SANDBOX_BASE_IMAGE=nvcr.io/nvidia/base/ubuntu:24.04
|
||||
|
||||
FROM ${OPENSHELL_SANDBOX_BASE_IMAGE}
|
||||
|
||||
@@ -11,5 +11,5 @@ USER root
|
||||
COPY workload.sh /usr/local/bin/openshell-gpu-workload
|
||||
RUN chmod 0755 /usr/local/bin/openshell-gpu-workload
|
||||
|
||||
USER sandbox
|
||||
USER 1000:1000
|
||||
ENTRYPOINT ["/usr/local/bin/openshell-gpu-workload"]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
ARG OPENSHELL_SANDBOX_BASE_IMAGE=ghcr.io/nvidia/openshell-community/sandboxes/base:latest
|
||||
ARG OPENSHELL_SANDBOX_BASE_IMAGE=nvcr.io/nvidia/base/ubuntu:24.04
|
||||
|
||||
FROM ${OPENSHELL_SANDBOX_BASE_IMAGE}
|
||||
|
||||
@@ -11,5 +11,5 @@ USER root
|
||||
COPY workload.sh /usr/local/bin/openshell-gpu-workload
|
||||
RUN chmod 0755 /usr/local/bin/openshell-gpu-workload
|
||||
|
||||
USER sandbox
|
||||
USER 1000:1000
|
||||
ENTRYPOINT ["/usr/local/bin/openshell-gpu-workload"]
|
||||
|
||||
@@ -22,7 +22,7 @@ KUBE_CONTEXT="${OPENSHELL_PARITY_KUBE_CONTEXT:-}"
|
||||
HOST_GATEWAY_IP="${OPENSHELL_PARITY_HOST_GATEWAY_IP:-}"
|
||||
RUN_ID="${OPENSHELL_PARITY_RUN_ID:-$(date +%s)-$$}"
|
||||
OUT="${OPENSHELL_PARITY_OUTPUT_DIR:-${ROOT}/target/parity/step8-kubernetes-${CANDIDATE_SHA:0:8}}"
|
||||
SANDBOX_IMAGE="${OPENSHELL_PARITY_KUBERNETES_SANDBOX_IMAGE:-ghcr.io/nvidia/openshell-community/sandboxes/base:latest}"
|
||||
SANDBOX_IMAGE="${OPENSHELL_PARITY_KUBERNETES_SANDBOX_IMAGE:-nvcr.io/nvidia/base/ubuntu:24.04}"
|
||||
SUPERVISOR_IMAGE="${OPENSHELL_PARITY_KUBERNETES_SUPERVISOR_IMAGE:-ghcr.io/nvidia/openshell/supervisor:latest}"
|
||||
RUNTIME_CLASS="openshell-parity-runc-${RUN_ID}"
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ set -euo pipefail
|
||||
CLI="${OPENSHELL_BIN:?OPENSHELL_BIN is required}"
|
||||
RESULT="${OPENSHELL_PARITY_ORACLE_RESULT:?OPENSHELL_PARITY_ORACLE_RESULT is required}"
|
||||
VARIANT="${OPENSHELL_PARITY_VARIANT:?OPENSHELL_PARITY_VARIANT is required}"
|
||||
IMAGE="${OPENSHELL_E2E_PODMAN_SANDBOX_IMAGE:-${OPENSHELL_SANDBOX_IMAGE:-ghcr.io/nvidia/openshell-community/sandboxes/base:latest}}"
|
||||
IMAGE="${OPENSHELL_E2E_PODMAN_SANDBOX_IMAGE:-${OPENSHELL_SANDBOX_IMAGE:-nvcr.io/nvidia/base/ubuntu:24.04}}"
|
||||
GATEWAY_LOG="${OPENSHELL_E2E_GATEWAY_LOG:?OPENSHELL_E2E_GATEWAY_LOG is required}"
|
||||
NAME="po-${VARIANT:0:1}-${RANDOM}"
|
||||
WORKDIR="${TMPDIR:-/tmp}/openshell-parity-options-${NAME}"
|
||||
|
||||
+1
-9
@@ -21,10 +21,9 @@ PODMAN_OPTIONS_ORACLE="${OPENSHELL_PARITY_PODMAN_OPTIONS_ORACLE:-${ROOT}/e2e/par
|
||||
CONFORMANCE_TRACE_WRAPPER="${ROOT}/e2e/parity/trace-conformance.sh"
|
||||
RESULTS_VERIFIER="${ROOT}/e2e/parity/verify-results.py"
|
||||
PODMAN_BIN="${OPENSHELL_PARITY_PODMAN_BIN:-podman}"
|
||||
DEFAULT_SANDBOX_IMAGE="ghcr.io/nvidia/openshell-community/sandboxes/base:latest"
|
||||
DEFAULT_SANDBOX_IMAGE="nvcr.io/nvidia/base/ubuntu:24.04"
|
||||
SANDBOX_IMAGE_REQUEST="${OPENSHELL_E2E_PODMAN_SANDBOX_IMAGE:-${DEFAULT_SANDBOX_IMAGE}}"
|
||||
PARITY_SANDBOX_RUNTIME_IMAGE=""
|
||||
PARITY_SANDBOX_COMMUNITY_REGISTRY=""
|
||||
PARITY_SUPERVISOR_BASE_IMAGE=""
|
||||
PARITY_SUPERVISOR_BASE_RUNTIME_IMAGE=""
|
||||
TEMP_WORKTREE=""
|
||||
@@ -404,11 +403,6 @@ resolve_parity_sandbox_image() {
|
||||
echo "ERROR: could not resolve one immutable parity sandbox image from ${SANDBOX_IMAGE_REQUEST}." >&2
|
||||
exit 2
|
||||
fi
|
||||
PARITY_SANDBOX_COMMUNITY_REGISTRY="${repository%/base}"
|
||||
if [ "${PARITY_SANDBOX_COMMUNITY_REGISTRY}" = "${repository}" ]; then
|
||||
echo "ERROR: parity sandbox repository must end in /base for the conformance alias: ${repository}." >&2
|
||||
exit 2
|
||||
fi
|
||||
echo "Using one immutable parity sandbox image for both variants: ${PARITY_SANDBOX_RUNTIME_IMAGE} (ID ${image_id})"
|
||||
}
|
||||
|
||||
@@ -551,7 +545,6 @@ run_variant() {
|
||||
-u OPENSHELL_SANDBOX_PROXY_AUTH_FILE -u OPENSHELL_SANDBOX_PROXY_AUTH_ALLOW_INSECURE \
|
||||
-u OPENSHELL_SANDBOX_PROXY_CONNECT_BY_HOSTNAME -u OPENSHELL_SANDBOX_PROXY_CA_BUNDLE \
|
||||
-u OPENSHELL_OTLP_ENDPOINT -u OPENSHELL_GATEWAY_NAME -u OPENSHELL_COMPUTE_DRIVER_BIND \
|
||||
-u OPENSHELL_COMMUNITY_REGISTRY \
|
||||
OPENSHELL_PARITY_VARIANT="${variant}" \
|
||||
OPENSHELL_E2E_CONFIG_SCHEMA_VERSION="${schema}" \
|
||||
OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER="$([ "${SCENARIO}" = external-driver ] && printf 1 || printf 0)" \
|
||||
@@ -561,7 +554,6 @@ run_variant() {
|
||||
OPENSHELL_E2E_FORCE_TEMP_PODMAN_SERVICE=1 \
|
||||
OPENSHELL_E2E_REQUIRE_DIGEST_PINNED_SANDBOX_IMAGE=1 \
|
||||
OPENSHELL_E2E_PODMAN_SANDBOX_IMAGE="${PARITY_SANDBOX_RUNTIME_IMAGE}" \
|
||||
OPENSHELL_COMMUNITY_REGISTRY="${PARITY_SANDBOX_COMMUNITY_REGISTRY}" \
|
||||
OPENSHELL_E2E_SUPERVISOR_BASE_IMAGE="${PARITY_SUPERVISOR_BASE_IMAGE}" \
|
||||
OPENSHELL_E2E_SUPERVISOR_BASE_RUNTIME_IMAGE="${PARITY_SUPERVISOR_BASE_RUNTIME_IMAGE}" \
|
||||
OPENSHELL_E2E_EXPECTED_GATEWAY_SHA256="${gateway_digest}" \
|
||||
|
||||
+5
-7
@@ -138,10 +138,9 @@ for variable in \
|
||||
OPENSHELL_OTLP_ENDPOINT OPENSHELL_GATEWAY_NAME OPENSHELL_COMPUTE_DRIVER_BIND; do
|
||||
[ -z "${!variable:-}" ] || exit 23
|
||||
done
|
||||
expected_sandbox="ghcr.io/nvidia/openshell-community/sandboxes/base@sha256:$(printf '%064d' 0)"
|
||||
expected_sandbox="nvcr.io/nvidia/base/ubuntu@sha256:$(printf '%064d' 0)"
|
||||
[ "${OPENSHELL_E2E_REQUIRE_DIGEST_PINNED_SANDBOX_IMAGE:-0}" = 1 ] || exit 24
|
||||
[ "${OPENSHELL_E2E_PODMAN_SANDBOX_IMAGE:-}" = "${expected_sandbox}" ] || exit 25
|
||||
[ "${OPENSHELL_COMMUNITY_REGISTRY:-}" = "ghcr.io/nvidia/openshell-community/sandboxes" ] || exit 28
|
||||
expected_base="docker.io/library/debian@sha256:$(printf '%064d' 0)"
|
||||
[ "${OPENSHELL_E2E_SUPERVISOR_BASE_IMAGE:-}" = "${OPENSHELL_PARITY_TEST_SUPERVISOR_BASE}" ] || exit 26
|
||||
[ "${OPENSHELL_E2E_SUPERVISOR_BASE_RUNTIME_IMAGE:-}" = "${expected_base}" ] || exit 27
|
||||
@@ -159,7 +158,7 @@ schema = int(os.environ["OPENSHELL_E2E_CONFIG_SCHEMA_VERSION"])
|
||||
external = os.environ.get("OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER") == "1"
|
||||
zero = "0" * 64
|
||||
image_digest = f"sha256:{zero}"
|
||||
sandbox_runtime = f"ghcr.io/nvidia/openshell-community/sandboxes/base@{image_digest}"
|
||||
sandbox_runtime = f"nvcr.io/nvidia/base/ubuntu@{image_digest}"
|
||||
sandbox_boundary = "localhost/openshell/sandbox:dev"
|
||||
supervisor_runtime = f"localhost/openshell/supervisor@{image_digest}"
|
||||
base_runtime = f"docker.io/library/debian@{image_digest}"
|
||||
@@ -218,7 +217,7 @@ launch = {
|
||||
"sandbox_image_digest": image_digest,
|
||||
"sandbox_runtime_image": sandbox_runtime,
|
||||
"sandbox_boundary_image": sandbox_boundary,
|
||||
"sandbox_client_image_alias": "ghcr.io/nvidia/openshell-community/sandboxes/base:latest",
|
||||
"sandbox_client_image_alias": "nvcr.io/nvidia/base/ubuntu:24.04",
|
||||
"sandbox_client_image_alias_id": zero,
|
||||
"gateway_sha256_before_execution": os.environ[
|
||||
"OPENSHELL_E2E_EXPECTED_GATEWAY_SHA256"
|
||||
@@ -292,7 +291,7 @@ done
|
||||
printf '%s %064d sha256:%064d %s %s %s %s\n' \
|
||||
"${expected_sandbox}" 0 0 "${expected_base}" \
|
||||
"localhost/openshell/supervisor@sha256:$(printf '%064d' 0)" \
|
||||
"ghcr.io/nvidia/openshell-community/sandboxes/base:latest" \
|
||||
"nvcr.io/nvidia/base/ubuntu:24.04" \
|
||||
"${package_hash}" >&2
|
||||
if [ "${OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER:-0}" = 1 ]; then
|
||||
printf 'fixture external driver log\n' >"${OPENSHELL_PARITY_EXTERNAL_DRIVER_LOG_CAPTURE}"
|
||||
@@ -422,7 +421,6 @@ OPENSHELL_SANDBOX_PROXY_CA_BUNDLE=/tmp/untrusted-proxy-ca \
|
||||
OPENSHELL_OTLP_ENDPOINT=http://untrusted.invalid:4317 \
|
||||
OPENSHELL_GATEWAY_NAME=untrusted \
|
||||
OPENSHELL_COMPUTE_DRIVER_BIND=192.0.2.2:50061 \
|
||||
OPENSHELL_COMMUNITY_REGISTRY=untrusted.invalid/community \
|
||||
run_harness
|
||||
assert_contains "${WORKDIR}/calls" "baseline|1|${WORKDIR}/results/artifacts/baseline/gateway|${WORKDIR}/results/artifacts/baseline/cli|${WORKDIR}/results/artifacts/baseline/conformance"
|
||||
assert_contains "${WORKDIR}/calls" "candidate|2|${WORKDIR}/results/artifacts/candidate/gateway|${WORKDIR}/results/artifacts/candidate/cli|${WORKDIR}/results/artifacts/candidate/conformance"
|
||||
@@ -439,7 +437,7 @@ assert_contains "${WORKDIR}/results/semantic-verification.json" '"accepted": tru
|
||||
assert_not_contains "${WORKDIR}/results/baseline.json" '"scenarios"'
|
||||
assert_contains "${WORKDIR}/results/baseline.log" '"scenarios"'
|
||||
assert_contains "${WORKDIR}/results/baseline.conformance.json" '"passed":true'
|
||||
assert_contains "${WORKDIR}/podman-calls" 'pull ghcr.io/nvidia/openshell-community/sandboxes/base:latest'
|
||||
assert_contains "${WORKDIR}/podman-calls" 'pull nvcr.io/nvidia/base/ubuntu:24.04'
|
||||
assert_contains "${WORKDIR}/podman-calls" "pull ${TEST_SUPERVISOR_BASE}"
|
||||
assert_contains "${WORKDIR}/podman-calls" 'unshare rm -rf -- '
|
||||
assert_contains "${WORKDIR}/podman-calls" 'openshell-parity-run.'
|
||||
|
||||
@@ -56,6 +56,16 @@ def require(condition: bool, message: str) -> None:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def image_repository(reference: str) -> str:
|
||||
"""Return an OCI image repository without a tag or digest."""
|
||||
repository = reference.split("@", 1)[0]
|
||||
last_slash = repository.rfind("/")
|
||||
last_colon = repository.rfind(":")
|
||||
if last_colon > last_slash:
|
||||
repository = repository[:last_colon]
|
||||
return repository
|
||||
|
||||
|
||||
def verify_conformance_report(path: Path) -> None:
|
||||
report = load_json(path)
|
||||
require(report.get("passed") is True, f"{path}: conformance report did not pass")
|
||||
@@ -330,7 +340,8 @@ def verify_variant(
|
||||
sandbox_alias = launch.get("sandbox_client_image_alias")
|
||||
require(
|
||||
isinstance(sandbox_alias, str)
|
||||
and sandbox_alias == sandbox_runtime.rsplit("@", 1)[0] + ":latest"
|
||||
and "@" not in sandbox_alias
|
||||
and image_repository(sandbox_alias) == image_repository(sandbox_runtime)
|
||||
and launch.get("sandbox_client_image_alias_id") == sandbox_id,
|
||||
f"{launch_path}: sandbox client alias is not bound to the pinned image",
|
||||
)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
ARG WORKLOAD_BASE=nvcr.io/nvidia/base/ubuntu:24.04
|
||||
FROM ghcr.io/astral-sh/uv:0.12.17@sha256:10787c682e4184e4f290de1171fd4703dc63de99221f10fe1c99002ce7fa9acc AS uv
|
||||
FROM ${WORKLOAD_BASE}
|
||||
|
||||
COPY --from=uv /uv /uvx /usr/local/bin/
|
||||
# Supplied from .python-version by the build task for cloudpickle compatibility.
|
||||
ARG PYTHON_VERSION
|
||||
# curl and openssl also support the Rust Docker tests sharing this fixture.
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
bash ca-certificates curl git openssl python3 python3-pip python3-venv python-is-python3 \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& if getent passwd ubuntu >/dev/null; then userdel ubuntu; fi \
|
||||
&& if getent group ubuntu >/dev/null; then groupdel ubuntu; fi \
|
||||
&& groupadd --gid 1000 sandbox \
|
||||
&& useradd --uid 1000 --gid sandbox --create-home --shell /bin/bash sandbox \
|
||||
&& UV_PYTHON_INSTALL_DIR=/sandbox/.uv/python uv python install "${PYTHON_VERSION}" \
|
||||
&& UV_PYTHON_INSTALL_DIR=/sandbox/.uv/python uv venv --python "${PYTHON_VERSION}" --seed /sandbox/.venv \
|
||||
&& uv pip install --python /sandbox/.venv/bin/python cloudpickle==3.1.2 \
|
||||
&& chown -R sandbox:sandbox /sandbox \
|
||||
&& uv cache clean
|
||||
|
||||
ENV VIRTUAL_ENV=/sandbox/.venv
|
||||
ENV PATH="/sandbox/.venv/bin:${PATH}"
|
||||
WORKDIR /sandbox
|
||||
USER sandbox:sandbox
|
||||
@@ -93,8 +93,7 @@ def sandbox(cluster_name: str | None) -> Callable[..., Sandbox]:
|
||||
cluster=cluster_name,
|
||||
spec=spec,
|
||||
delete_on_exit=delete_on_exit,
|
||||
# The sandbox image is large (Python, Node.js, coding agents) so the
|
||||
# first pod in the cluster may need extra time for the image pull.
|
||||
# Allow time to pull an explicitly supplied workload fixture.
|
||||
ready_timeout_seconds=300.0,
|
||||
)
|
||||
|
||||
|
||||
@@ -44,5 +44,9 @@ if [ "${1:-}" = "${RUN_WITH_GATEWAY_COMMAND}" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -n "${E2E_FEATURES}" ]; then
|
||||
CONTAINER_ENGINE=docker bash "${ROOT}/tasks/scripts/e2e-build-workload.sh"
|
||||
fi
|
||||
|
||||
exec "${ROOT}/e2e/with-docker-gateway.sh" \
|
||||
bash "${BASH_SOURCE[0]}" "${RUN_WITH_GATEWAY_COMMAND}"
|
||||
|
||||
@@ -15,8 +15,7 @@ use std::time::Duration;
|
||||
use tokio::time::{interval, timeout};
|
||||
|
||||
use super::port::find_free_port;
|
||||
|
||||
const DEFAULT_TEST_SERVER_IMAGE: &str = "ghcr.io/nvidia/openshell-community/sandboxes/base:latest";
|
||||
use super::sandbox::E2E_WORKLOAD_IMAGE;
|
||||
|
||||
#[must_use]
|
||||
pub fn e2e_driver() -> Option<String> {
|
||||
@@ -197,7 +196,7 @@ impl ContainerHttpServer {
|
||||
]);
|
||||
}
|
||||
args.extend([
|
||||
DEFAULT_TEST_SERVER_IMAGE.to_string(),
|
||||
E2E_WORKLOAD_IMAGE.to_string(),
|
||||
"-c".to_string(),
|
||||
script.to_string(),
|
||||
]);
|
||||
@@ -325,7 +324,7 @@ impl HostSupportContainer {
|
||||
"python3",
|
||||
"-p",
|
||||
&format!("{port}:{container_port}"),
|
||||
DEFAULT_TEST_SERVER_IMAGE,
|
||||
E2E_WORKLOAD_IMAGE,
|
||||
"-c",
|
||||
script,
|
||||
])
|
||||
@@ -380,7 +379,7 @@ impl HostSupportContainer {
|
||||
.map(|capability| format!("--cap-add={capability}")),
|
||||
);
|
||||
args.extend([
|
||||
DEFAULT_TEST_SERVER_IMAGE.to_string(),
|
||||
E2E_WORKLOAD_IMAGE.to_string(),
|
||||
"-c".to_string(),
|
||||
script.to_string(),
|
||||
]);
|
||||
@@ -493,7 +492,7 @@ impl SupportContainer {
|
||||
.map(|capability| format!("--cap-add={capability}")),
|
||||
);
|
||||
args.extend([
|
||||
DEFAULT_TEST_SERVER_IMAGE.to_string(),
|
||||
E2E_WORKLOAD_IMAGE.to_string(),
|
||||
"-c".to_string(),
|
||||
script.to_string(),
|
||||
]);
|
||||
|
||||
@@ -17,6 +17,18 @@ use tokio::time::timeout;
|
||||
use super::binary::openshell_cmd;
|
||||
use super::output::{extract_field, strip_ansi};
|
||||
|
||||
/// Tool-capable workload image used by the E2E harness.
|
||||
///
|
||||
/// Product defaults remain on the minimal NVIDIA Ubuntu image. Tests that
|
||||
/// explicitly pass `--from` or `--template` retain their requested workload.
|
||||
/// Docker setup builds this Noble-based fixture before running the tests.
|
||||
#[cfg(feature = "e2e-docker")]
|
||||
pub const E2E_WORKLOAD_IMAGE: &str = "openshell/e2e-python:dev";
|
||||
|
||||
/// Preserve the existing pullable fixture for non-Docker E2E lanes.
|
||||
#[cfg(not(feature = "e2e-docker"))]
|
||||
pub const E2E_WORKLOAD_IMAGE: &str = "ghcr.io/astral-sh/uv:0.12.17-python3.12-trixie-slim@sha256:9a59bb7206905ccaae4f7dab222fbac47c125a21e5fc16f43f427cd6c940ade3";
|
||||
|
||||
/// Extract the sandbox name from CLI create output.
|
||||
///
|
||||
/// The CLI prints `Created sandbox: <name>` (current format). Falls back to
|
||||
@@ -26,10 +38,9 @@ fn extract_sandbox_name(output: &str) -> Option<String> {
|
||||
}
|
||||
|
||||
/// Default timeout for waiting for a sandbox to become ready.
|
||||
/// In VM mode, the overlayfs snapshotter re-extracts all image layers
|
||||
/// from the content store on every boot (~250s for the 1GB sandbox
|
||||
/// base image), so 600s accommodates extraction + workspace-init + pod
|
||||
/// startup.
|
||||
/// In VM mode, the overlayfs snapshotter re-extracts image layers from the
|
||||
/// content store on every boot, so 600s accommodates cold image preparation,
|
||||
/// workspace initialization, and sandbox startup.
|
||||
const SANDBOX_READY_TIMEOUT: Duration = Duration::from_secs(600);
|
||||
|
||||
static NEXT_SANDBOX_NAME: AtomicU64 = AtomicU64::new(1);
|
||||
@@ -39,6 +50,21 @@ fn has_explicit_sandbox_name(args: &[&str]) -> bool {
|
||||
.any(|arg| *arg == "--name" || arg.starts_with("--name="))
|
||||
}
|
||||
|
||||
fn has_explicit_sandbox_workload(args: &[&str]) -> bool {
|
||||
args.iter().take_while(|arg| **arg != "--").any(|arg| {
|
||||
*arg == "--from"
|
||||
|| arg.starts_with("--from=")
|
||||
|| *arg == "--template"
|
||||
|| arg.starts_with("--template=")
|
||||
})
|
||||
}
|
||||
|
||||
fn add_test_image_if_missing(command: &mut tokio::process::Command, args: &[&str]) {
|
||||
if !has_explicit_sandbox_workload(args) {
|
||||
command.arg("--from").arg(E2E_WORKLOAD_IMAGE);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_unique_name_if_missing(command: &mut tokio::process::Command, args: &[&str]) {
|
||||
if !has_explicit_sandbox_name(args) {
|
||||
command.arg("--name").arg(format!(
|
||||
@@ -96,6 +122,18 @@ impl SandboxGuard {
|
||||
/// Returns an error if the CLI exits with a non-zero status or the sandbox
|
||||
/// name cannot be parsed from the output.
|
||||
pub async fn create(args: &[&str]) -> Result<Self, String> {
|
||||
Self::create_inner(args, true).await
|
||||
}
|
||||
|
||||
/// Create a sandbox using the gateway's configured default image.
|
||||
///
|
||||
/// Most E2E tests use [`Self::create`], which supplies the tool-capable E2E
|
||||
/// image. This variant is reserved for coverage of the product default.
|
||||
pub async fn create_with_gateway_default(args: &[&str]) -> Result<Self, String> {
|
||||
Self::create_inner(args, false).await
|
||||
}
|
||||
|
||||
async fn create_inner(args: &[&str], use_test_image: bool) -> Result<Self, String> {
|
||||
let separator = args.iter().position(|arg| *arg == "--");
|
||||
let (create_args, command) = separator.map_or((args, &[][..]), |index| {
|
||||
(&args[..index], &args[index + 1..])
|
||||
@@ -111,6 +149,9 @@ impl SandboxGuard {
|
||||
let mut cmd = openshell_cmd();
|
||||
cmd.arg("sandbox").arg("create").arg("--detach");
|
||||
add_unique_name_if_missing(&mut cmd, create_args);
|
||||
if use_test_image {
|
||||
add_test_image_if_missing(&mut cmd, create_args);
|
||||
}
|
||||
for arg in create_args {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
@@ -187,6 +228,7 @@ impl SandboxGuard {
|
||||
let mut cmd = openshell_cmd();
|
||||
cmd.arg("sandbox").arg("create").arg("--detach");
|
||||
add_unique_name_if_missing(&mut cmd, &[]);
|
||||
add_test_image_if_missing(&mut cmd, &[]);
|
||||
cmd.arg("--")
|
||||
.args(command)
|
||||
.stdout(Stdio::piped())
|
||||
@@ -235,6 +277,7 @@ impl SandboxGuard {
|
||||
let mut create_cmd = openshell_cmd();
|
||||
create_cmd.arg("sandbox").arg("create").arg("--detach");
|
||||
add_unique_name_if_missing(&mut create_cmd, create_args);
|
||||
add_test_image_if_missing(&mut create_cmd, create_args);
|
||||
for arg in create_args {
|
||||
create_cmd.arg(arg);
|
||||
}
|
||||
@@ -374,6 +417,7 @@ impl SandboxGuard {
|
||||
let mut cmd = openshell_cmd();
|
||||
cmd.arg("sandbox").arg("create").arg("--detach");
|
||||
add_unique_name_if_missing(&mut cmd, &[]);
|
||||
add_test_image_if_missing(&mut cmd, &[]);
|
||||
for (local, dest) in uploads {
|
||||
cmd.arg("--upload").arg(format!("{local}:{dest}"));
|
||||
}
|
||||
@@ -703,7 +747,7 @@ impl Drop for SandboxGuard {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::has_explicit_sandbox_name;
|
||||
use super::{has_explicit_sandbox_name, has_explicit_sandbox_workload};
|
||||
|
||||
#[test]
|
||||
fn detects_explicit_sandbox_names() {
|
||||
@@ -711,4 +755,19 @@ mod tests {
|
||||
assert!(has_explicit_sandbox_name(&["--name=example"]));
|
||||
assert!(!has_explicit_sandbox_name(&["--policy", "policy.yaml"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_explicit_sandbox_workloads() {
|
||||
assert!(has_explicit_sandbox_workload(&["--from", "example:latest"]));
|
||||
assert!(has_explicit_sandbox_workload(&["--from=example:latest"]));
|
||||
assert!(has_explicit_sandbox_workload(&["--template", "example"]));
|
||||
assert!(has_explicit_sandbox_workload(&["--template=example"]));
|
||||
assert!(!has_explicit_sandbox_workload(&["--policy", "policy.yaml"]));
|
||||
assert!(!has_explicit_sandbox_workload(&["--", "echo", "--from=x"]));
|
||||
assert!(!has_explicit_sandbox_workload(&[
|
||||
"--",
|
||||
"echo",
|
||||
"--template=x"
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![cfg(feature = "e2e")]
|
||||
|
||||
//! E2E test: pull and launch a community sandbox image from GHCR.
|
||||
//!
|
||||
//! This test verifies that:
|
||||
//! 1. The `base` community sandbox image can be pulled from GHCR
|
||||
//! 2. A sandbox can be created and run using the community image
|
||||
//! 3. Basic command execution works inside the community sandbox
|
||||
//!
|
||||
//! Prerequisites:
|
||||
//! - A running openshell gateway (`mise run gateway:docker`)
|
||||
//! - Network access to ghcr.io/nvidia/openshell-community/sandboxes/
|
||||
|
||||
use openshell_e2e::harness::output::strip_ansi;
|
||||
use openshell_e2e::harness::sandbox::SandboxGuard;
|
||||
|
||||
/// Create a sandbox using the community `base` image and verify it works.
|
||||
///
|
||||
/// The `--from base` argument should resolve to:
|
||||
/// `ghcr.io/nvidia/openshell-community/sandboxes/base:latest`
|
||||
#[tokio::test]
|
||||
async fn sandbox_from_community_base_image() {
|
||||
// Create a sandbox using the community "base" image.
|
||||
// The CLI should expand "base" to the full GHCR path.
|
||||
let mut guard = SandboxGuard::create(&["--from", "base", "--", "echo", "community-image-ok"])
|
||||
.await
|
||||
.expect("sandbox create from community base image");
|
||||
|
||||
// Verify the command output contains our marker.
|
||||
let clean_output = strip_ansi(&guard.create_output);
|
||||
assert!(
|
||||
clean_output.contains("community-image-ok"),
|
||||
"expected 'community-image-ok' in sandbox output:\n{clean_output}"
|
||||
);
|
||||
|
||||
guard.cleanup().await;
|
||||
}
|
||||
|
||||
/// Create a sandbox using the full GHCR image path explicitly.
|
||||
///
|
||||
/// This tests that explicit image references work correctly.
|
||||
#[tokio::test]
|
||||
async fn sandbox_from_explicit_ghcr_image() {
|
||||
let image = "ghcr.io/nvidia/openshell-community/sandboxes/base:latest";
|
||||
|
||||
let mut guard = SandboxGuard::create(&["--from", image, "--", "cat", "/etc/os-release"])
|
||||
.await
|
||||
.expect("sandbox create from explicit GHCR image");
|
||||
|
||||
// The base image should have an /etc/os-release file.
|
||||
let clean_output = strip_ansi(&guard.create_output);
|
||||
assert!(
|
||||
clean_output.contains("ID=") || clean_output.contains("NAME="),
|
||||
"expected OS release info in sandbox output:\n{clean_output}"
|
||||
);
|
||||
|
||||
guard.cleanup().await;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![cfg(feature = "e2e")]
|
||||
|
||||
//! E2E coverage for the default NVIDIA Ubuntu workload image.
|
||||
|
||||
use openshell_e2e::harness::output::strip_ansi;
|
||||
use openshell_e2e::harness::sandbox::SandboxGuard;
|
||||
|
||||
#[tokio::test]
|
||||
async fn sandbox_from_default_image() {
|
||||
let mut guard = SandboxGuard::create_with_gateway_default(&["--", "cat", "/etc/os-release"])
|
||||
.await
|
||||
.expect("sandbox create from default image");
|
||||
|
||||
let clean_output = strip_ansi(&guard.create_output);
|
||||
assert!(
|
||||
clean_output.contains("ID=ubuntu"),
|
||||
"expected Ubuntu OS release info in sandbox output:\n{clean_output}"
|
||||
);
|
||||
|
||||
guard.cleanup().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sandbox_from_explicit_nvidia_ubuntu_image() {
|
||||
let image = "nvcr.io/nvidia/base/ubuntu:24.04";
|
||||
let mut guard = SandboxGuard::create(&["--from", image, "--", "cat", "/etc/os-release"])
|
||||
.await
|
||||
.expect("sandbox create from explicit NVIDIA Ubuntu image");
|
||||
|
||||
let clean_output = strip_ansi(&guard.create_output);
|
||||
assert!(
|
||||
clean_output.contains("ID=ubuntu"),
|
||||
"expected Ubuntu OS release info in sandbox output:\n{clean_output}"
|
||||
);
|
||||
|
||||
guard.cleanup().await;
|
||||
}
|
||||
@@ -21,7 +21,7 @@ use openshell_e2e::harness::container::{ImageGuard, e2e_driver};
|
||||
use openshell_e2e::harness::sandbox::SandboxGuard;
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
const TEST_IMAGE: &str = "ghcr.io/nvidia/openshell-community/sandboxes/base:latest";
|
||||
const TEST_IMAGE: &str = "nvcr.io/nvidia/base/ubuntu:24.04";
|
||||
const VOLUME_TARGET: &str = "/sandbox/e2e-volume";
|
||||
const BIND_TARGET: &str = "/sandbox/e2e-bind";
|
||||
#[cfg(feature = "e2e-docker")]
|
||||
|
||||
@@ -174,7 +174,7 @@ endpoints:
|
||||
protocol: rest
|
||||
access: full
|
||||
enforcement: enforce
|
||||
binaries: [/usr/bin/curl]
|
||||
binaries: [/usr/bin/bash]
|
||||
"#
|
||||
);
|
||||
file.write_all(profile.as_bytes())
|
||||
@@ -191,7 +191,7 @@ fn write_binding_policy(port: u16) -> Result<NamedTempFile, String> {
|
||||
|
||||
filesystem_policy:
|
||||
include_workdir: true
|
||||
read_only: [/usr, /lib, /proc, /dev/urandom, /app, /etc, /var/log]
|
||||
read_only: [/bin, /usr, /lib, /proc, /dev/urandom, /app, /etc, /var/log]
|
||||
read_write: [/sandbox, /tmp, /dev/null]
|
||||
|
||||
landlock:
|
||||
@@ -218,7 +218,7 @@ network_policies:
|
||||
access: full
|
||||
enforcement: enforce
|
||||
binaries:
|
||||
- path: /usr/bin/curl
|
||||
- path: /usr/bin/bash
|
||||
"#
|
||||
);
|
||||
file.write_all(policy.as_bytes())
|
||||
@@ -263,6 +263,7 @@ filesystem_policy:
|
||||
include_workdir: true
|
||||
read_only:
|
||||
- /usr
|
||||
- /bin
|
||||
- /lib
|
||||
- /proc
|
||||
- /dev/urandom
|
||||
@@ -293,7 +294,7 @@ network_policies:
|
||||
- "192.168.0.0/16"
|
||||
- "fc00::/7"
|
||||
binaries:
|
||||
- path: /usr/bin/curl
|
||||
- path: /usr/bin/bash
|
||||
"#
|
||||
);
|
||||
file.write_all(policy.as_bytes())
|
||||
@@ -315,20 +316,17 @@ async fn sandbox_reaches_host_openshell_internal_via_host_gateway_alias() {
|
||||
.expect("temp policy path should be utf-8")
|
||||
.to_string();
|
||||
|
||||
// The workload needs curl, which minimal default images such as the VM
|
||||
// driver's nvcr.io/nvidia/base/ubuntu do not ship.
|
||||
let command = format!(
|
||||
r#"exec 3<>/dev/tcp/host.openshell.internal/{0}; printf 'GET / HTTP/1.1\r\nHost: host.openshell.internal:{0}\r\nConnection: close\r\n\r\n' >&3; while IFS= read -r line <&3 || [[ -n $line ]]; do printf '%s\n' "$line"; done"#,
|
||||
server.port
|
||||
);
|
||||
let guard = SandboxGuard::create(&[
|
||||
"--from",
|
||||
"base",
|
||||
"--policy",
|
||||
&policy_path,
|
||||
"--",
|
||||
"curl",
|
||||
"--silent",
|
||||
"--show-error",
|
||||
"--max-time",
|
||||
"15",
|
||||
&format!("http://host.openshell.internal:{}/", server.port),
|
||||
"/usr/bin/bash",
|
||||
"-c",
|
||||
&command,
|
||||
])
|
||||
.await
|
||||
.expect("sandbox create with host.openshell.internal echo request");
|
||||
@@ -404,12 +402,31 @@ async fn static_provider_credentials_are_bound_to_profile_endpoints() {
|
||||
.expect("create endpoint-bound provider B");
|
||||
|
||||
let command = format!(
|
||||
r#"allowed=$(curl --silent --show-error --max-time 15 -H "Authorization: Bearer $BOUND_TOKEN_A" http://host.openshell.internal:{}/allowed/check); host_denied=$(curl --silent --show-error --max-time 15 -o /tmp/host-denied-body -w "%{{http_code}}" -H "Authorization: Bearer $BOUND_TOKEN_A" http://host.docker.internal:{}/allowed/check); path_denied=$(curl --silent --show-error --max-time 15 -o /tmp/path-denied-body -w "%{{http_code}}" -H "Authorization: Bearer $BOUND_TOKEN_A" http://host.openshell.internal:{}/other/check); printf 'ALLOWED=%s HOST_DENIED=%s PATH_DENIED=%s\n' "$allowed" "$host_denied" "$path_denied""#,
|
||||
server.port, server.port, server.port
|
||||
r#"
|
||||
http_request() {{
|
||||
local host="$1" path="$2" status_line line
|
||||
HTTP_STATUS= HTTP_BODY=
|
||||
exec 3<>"/dev/tcp/$host/{0}" || return 1
|
||||
printf 'GET %s HTTP/1.1\r\nHost: %s:{0}\r\nAuthorization: Bearer %s\r\nConnection: close\r\n\r\n' "$path" "$host" "$BOUND_TOKEN_A" >&3
|
||||
IFS= read -r status_line <&3 || return 1
|
||||
status_line="${{status_line%$'\r'}}"
|
||||
HTTP_STATUS="${{status_line#* }}"
|
||||
HTTP_STATUS="${{HTTP_STATUS%% *}}"
|
||||
while IFS= read -r line <&3; do
|
||||
line="${{line%$'\r'}}"
|
||||
[[ -z "$line" ]] && break
|
||||
done
|
||||
while IFS= read -r line <&3 || [[ -n "$line" ]]; do HTTP_BODY+="$line"; done
|
||||
exec 3>&- 3<&-
|
||||
}}
|
||||
http_request host.openshell.internal /allowed/check; allowed="$HTTP_BODY"
|
||||
http_request host.docker.internal /allowed/check; host_denied="$HTTP_STATUS"
|
||||
http_request host.openshell.internal /other/check; path_denied="$HTTP_STATUS"
|
||||
printf 'ALLOWED=%s HOST_DENIED=%s PATH_DENIED=%s\n' "$allowed" "$host_denied" "$path_denied"
|
||||
"#,
|
||||
server.port
|
||||
);
|
||||
let mut guard = SandboxGuard::create(&[
|
||||
"--from",
|
||||
"base",
|
||||
"--policy",
|
||||
&policy_path,
|
||||
"--provider",
|
||||
@@ -418,7 +435,7 @@ async fn static_provider_credentials_are_bound_to_profile_endpoints() {
|
||||
BINDING_PROVIDER_B_NAME,
|
||||
"--no-auto-providers",
|
||||
"--",
|
||||
"sh",
|
||||
"/usr/bin/bash",
|
||||
"-c",
|
||||
&command,
|
||||
])
|
||||
|
||||
@@ -18,7 +18,7 @@ use openshell_e2e::harness::container::{ContainerEngine, is_e2e_driver};
|
||||
use openshell_e2e::harness::output::strip_ansi;
|
||||
use openshell_e2e::harness::sandbox::SandboxGuard;
|
||||
|
||||
const BASE_IMAGE: &str = "ghcr.io/nvidia/openshell-community/sandboxes/base:latest";
|
||||
const BASE_IMAGE: &str = "nvcr.io/nvidia/base/ubuntu:24.04";
|
||||
const READY_MARKER: &str = "podman-oci-identity-ready";
|
||||
const OCI_UID: &str = "2345";
|
||||
const OCI_GID: &str = "2346";
|
||||
@@ -182,7 +182,7 @@ async fn podman_uses_oci_identity_and_inspected_image_id() {
|
||||
}
|
||||
|
||||
let image = ImageGuard::build().expect("build Podman OCI identity image");
|
||||
// The community base image contains a baked default policy with an
|
||||
// The fixture image contains a policy with an
|
||||
// explicit `sandbox` process identity. Supply a complete policy that
|
||||
// intentionally omits `process` so this test exercises OCI fallback.
|
||||
let policy = tempfile::NamedTempFile::new().expect("create OCI fallback policy");
|
||||
|
||||
@@ -22,6 +22,8 @@ use std::sync::Mutex;
|
||||
|
||||
use openshell_e2e::harness::binary::openshell_cmd;
|
||||
use openshell_e2e::harness::output::{extract_field, strip_ansi};
|
||||
#[cfg(feature = "e2e-docker")]
|
||||
use openshell_e2e::harness::sandbox::E2E_WORKLOAD_IMAGE;
|
||||
|
||||
const TEST_API_KEY: &str = "sk-e2e-auto-provider-test-key";
|
||||
static CLAUDE_PROVIDER_LOCK: Mutex<()> = Mutex::new(());
|
||||
@@ -108,9 +110,10 @@ network_policies: {}
|
||||
// Create a sandbox that prints the ANTHROPIC_API_KEY env var.
|
||||
// --auto-providers skips the interactive prompt.
|
||||
let mut cmd = openshell_cmd();
|
||||
cmd.arg("sandbox")
|
||||
.arg("create")
|
||||
.arg("--detach")
|
||||
cmd.arg("sandbox").arg("create");
|
||||
#[cfg(feature = "e2e-docker")]
|
||||
cmd.arg("--from").arg(E2E_WORKLOAD_IMAGE);
|
||||
cmd.arg("--detach")
|
||||
.arg("--policy")
|
||||
.arg(policy.path())
|
||||
.arg("--provider")
|
||||
|
||||
@@ -20,7 +20,7 @@ use std::time::Duration;
|
||||
use openshell_e2e::harness::binary::openshell_cmd;
|
||||
use openshell_e2e::harness::container::{ContainerEngine, e2e_network_name};
|
||||
use openshell_e2e::harness::gateway::ManagedGateway;
|
||||
use openshell_e2e::harness::sandbox::SandboxGuard;
|
||||
use openshell_e2e::harness::sandbox::{E2E_WORKLOAD_IMAGE, SandboxGuard};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use tempfile::TempDir;
|
||||
@@ -1312,18 +1312,6 @@ async fn acknowledged_provider_changes_apply_to_fresh_clients_and_revoke_retaine
|
||||
std::fs::create_dir(&context).map_err(|_| "could not allocate public image context")?;
|
||||
let backend_tls = directory.path().join("backend-tls");
|
||||
std::fs::create_dir(&backend_tls).map_err(|_| "could not allocate backend TLS directory")?;
|
||||
let base = std::env::var("OPENSHELL_E2E_DOCKER_SANDBOX_IMAGE")
|
||||
.unwrap_or_else(|_| "ghcr.io/nvidia/openshell-community/sandboxes/base:latest".to_string());
|
||||
if base.chars().any(char::is_whitespace) {
|
||||
return Err("fixture image reference contains whitespace".to_string());
|
||||
}
|
||||
let binaries = base_binaries(&base).await?;
|
||||
let python = binaries["python"]
|
||||
.as_str()
|
||||
.ok_or("Python executable was absent")?;
|
||||
let curl = binaries["curl"]
|
||||
.as_str()
|
||||
.ok_or("curl executable was absent")?;
|
||||
let image = FixtureImage::new()?;
|
||||
let supervisor_image = FixtureImage::new()?;
|
||||
// Each backend has its own network namespace, so fixed internal ports need
|
||||
@@ -1340,7 +1328,28 @@ async fn acknowledged_provider_changes_apply_to_fresh_clients_and_revoke_retaine
|
||||
let result = async {
|
||||
// Begin container mutation inside this scope so certificate, image,
|
||||
// and enrollment failures still reach explicit bounded teardown.
|
||||
let [host, other_host] = backend.spawn(&base, &backend_tls).await?;
|
||||
std::fs::write(context.join("client.py"), CLIENT)
|
||||
.map_err(|_| "could not write client source")?;
|
||||
let dockerfile = context.join("Dockerfile");
|
||||
std::fs::write(
|
||||
&dockerfile,
|
||||
format!(
|
||||
"FROM {E2E_WORKLOAD_IMAGE}\nCOPY client.py /opt/provider-readiness-client.py\nUSER 1000:1000\n",
|
||||
),
|
||||
)
|
||||
.map_err(|_| "could not write fixture Dockerfile")?;
|
||||
image
|
||||
.build(&dockerfile, &context, "build workload fixture image")
|
||||
.await?;
|
||||
let binaries = base_binaries(image.tag()).await?;
|
||||
let python = binaries["python"]
|
||||
.as_str()
|
||||
.ok_or("Python executable was absent")?;
|
||||
let curl = binaries["curl"]
|
||||
.as_str()
|
||||
.ok_or("curl executable was absent")?;
|
||||
|
||||
let [host, other_host] = backend.spawn(image.tag(), &backend_tls).await?;
|
||||
let (certificate, private_key) =
|
||||
generate_certificates(directory.path(), &host, &other_host).await?;
|
||||
std::fs::copy(&certificate, backend_tls.join("backend.crt"))
|
||||
@@ -1362,12 +1371,6 @@ async fn acknowledged_provider_changes_apply_to_fresh_clients_and_revoke_retaine
|
||||
.await?;
|
||||
std::fs::copy(directory.path().join("ca.crt"), context.join("fixture-ca.crt"))
|
||||
.map_err(|_| "could not copy public fixture CA")?;
|
||||
std::fs::write(context.join("client.py"), CLIENT)
|
||||
.map_err(|_| "could not write client source")?;
|
||||
let dockerfile = context.join("Dockerfile");
|
||||
std::fs::write(&dockerfile, format!(
|
||||
"FROM {base}\nUSER root\nCOPY client.py /opt/provider-readiness-client.py\nUSER sandbox\n"
|
||||
)).map_err(|_| "could not write fixture Dockerfile")?;
|
||||
let supervisor_dockerfile = context.join("Dockerfile.supervisor");
|
||||
// Outbound TLS belongs to the separate supervisor. Assemble its combined
|
||||
// public trust bundle in the shell-capable workload image because the
|
||||
@@ -1375,13 +1378,11 @@ async fn acknowledged_provider_changes_apply_to_fresh_clients_and_revoke_retaine
|
||||
// image's user setting: Docker's archive upload applies an explicit image
|
||||
// user to the supervisor's private bootstrap files.
|
||||
std::fs::write(&supervisor_dockerfile, format!(
|
||||
"FROM {} AS supervisor\nFROM {base} AS trust-bundle\nUSER 0\nCOPY --from=supervisor /etc/ssl/certs/ca-certificates.crt /tmp/ca-certificates.crt\nCOPY fixture-ca.crt /tmp/readiness-fixture-ca.crt\nRUN [\"/usr/bin/python3\", \"-c\", \"from pathlib import Path; bundle = Path('/tmp/ca-certificates.crt'); bundle.write_bytes(bundle.read_bytes() + Path('/tmp/readiness-fixture-ca.crt').read_bytes())\"]\nFROM {}\nCOPY --from=trust-bundle /tmp/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt\n",
|
||||
"FROM {} AS supervisor\nFROM {} AS trust-bundle\nUSER 0\nCOPY --from=supervisor /etc/ssl/certs/ca-certificates.crt /tmp/ca-certificates.crt\nCOPY fixture-ca.crt /tmp/readiness-fixture-ca.crt\nRUN [\"/usr/bin/python3\", \"-c\", \"from pathlib import Path; bundle = Path('/tmp/ca-certificates.crt'); bundle.write_bytes(bundle.read_bytes() + Path('/tmp/readiness-fixture-ca.crt').read_bytes())\"]\nFROM {}\nCOPY --from=trust-bundle /tmp/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt\n",
|
||||
gateway_config.supervisor_image,
|
||||
image.tag(),
|
||||
gateway_config.supervisor_image,
|
||||
)).map_err(|_| "could not write fixture supervisor Dockerfile")?;
|
||||
image
|
||||
.build(&dockerfile, &context, "build workload fixture image")
|
||||
.await?;
|
||||
supervisor_image
|
||||
.build(
|
||||
&supervisor_dockerfile,
|
||||
|
||||
@@ -106,7 +106,7 @@ fn write_profile(resource_port: u16, token_port: u16) -> Result<NamedTempFile, S
|
||||
.tempfile()
|
||||
.map_err(|error| format!("create profile: {error}"))?;
|
||||
let profile = format!(
|
||||
r#"id: {PROFILE_ID}
|
||||
r"id: {PROFILE_ID}
|
||||
display_name: Stable refresh handle E2E
|
||||
category: other
|
||||
credentials:
|
||||
@@ -139,8 +139,8 @@ endpoints:
|
||||
- 172.0.0.0/8
|
||||
- 192.168.0.0/16
|
||||
binaries:
|
||||
- /usr/bin/curl
|
||||
"#
|
||||
- /usr/local/bin/python3
|
||||
"
|
||||
);
|
||||
file.write_all(profile.as_bytes())
|
||||
.map_err(|error| format!("write profile: {error}"))?;
|
||||
@@ -155,7 +155,7 @@ fn write_policy(resource_port: u16) -> Result<NamedTempFile, String> {
|
||||
.tempfile()
|
||||
.map_err(|error| format!("create policy: {error}"))?;
|
||||
let policy = format!(
|
||||
r#"version: 1
|
||||
r"version: 1
|
||||
filesystem_policy:
|
||||
include_workdir: true
|
||||
read_only: [/usr, /lib, /proc, /etc, /dev/urandom]
|
||||
@@ -181,8 +181,8 @@ network_policies:
|
||||
- 172.0.0.0/8
|
||||
- 192.168.0.0/16
|
||||
binaries:
|
||||
- path: /usr/bin/curl
|
||||
"#
|
||||
- path: /usr/local/bin/python3
|
||||
"
|
||||
);
|
||||
file.write_all(policy.as_bytes())
|
||||
.map_err(|error| format!("write policy: {error}"))?;
|
||||
@@ -311,9 +311,7 @@ echo {READY_MARKER}
|
||||
while true; do
|
||||
if [ -f /sandbox/probe-trigger ]; then
|
||||
rm -f /sandbox/probe-trigger
|
||||
if curl --fail --silent --output /dev/null \
|
||||
--header "Authorization: Bearer $REFRESH_E2E_ACCESS_TOKEN" \
|
||||
{resource_url}; then
|
||||
if python3 -c 'import os, urllib.request; request = urllib.request.Request("{resource_url}", headers=dict(Authorization="Bearer " + os.environ["REFRESH_E2E_ACCESS_TOKEN"])); urllib.request.urlopen(request, timeout=5).read()'; then
|
||||
echo ok > /sandbox/probe-result
|
||||
else
|
||||
echo failed > /sandbox/probe-result
|
||||
@@ -343,7 +341,7 @@ done"#
|
||||
wait_for_probe_failure(&sandbox).await?;
|
||||
|
||||
let fresh_probe = format!(
|
||||
"curl --fail --silent --output /dev/null --header \"Authorization: Bearer $REFRESH_E2E_ACCESS_TOKEN\" {resource_url}"
|
||||
r#"python3 -c 'import os, urllib.request; request = urllib.request.Request("{resource_url}", headers=dict(Authorization="Bearer " + os.environ["REFRESH_E2E_ACCESS_TOKEN"])); urllib.request.urlopen(request, timeout=5).read()'"#
|
||||
);
|
||||
sandbox.exec(&["sh", "-c", &fresh_probe]).await?;
|
||||
Ok(())
|
||||
|
||||
@@ -554,7 +554,7 @@ fn write_profile(profile_type: &str, token_port: u16, target_port: u16) -> Named
|
||||
.tempfile()
|
||||
.expect("create provider profile temp file");
|
||||
let profile = format!(
|
||||
r#"id: {profile_type}
|
||||
r"id: {profile_type}
|
||||
display_name: Podman token exchange e2e
|
||||
description: Podman e2e provider profile for two-stage token exchange
|
||||
category: other
|
||||
@@ -592,9 +592,8 @@ endpoints:
|
||||
- 172.0.0.0/8
|
||||
- 192.168.0.0/16
|
||||
binaries:
|
||||
- /usr/bin/curl
|
||||
- /usr/local/bin/curl
|
||||
"#
|
||||
- /usr/local/bin/python3
|
||||
"
|
||||
);
|
||||
file.write_all(profile.as_bytes())
|
||||
.expect("write provider profile");
|
||||
@@ -604,10 +603,10 @@ binaries:
|
||||
|
||||
fn sandbox_script(token_port: u16) -> String {
|
||||
let _ = token_port;
|
||||
r#"set -eu
|
||||
r"set -eu
|
||||
echo token-server-ready
|
||||
while true; do sleep 60; done
|
||||
"#
|
||||
"
|
||||
.to_string()
|
||||
}
|
||||
|
||||
@@ -773,8 +772,11 @@ async fn container_loopback_port_ready(container_name: &str, token_port: u16) ->
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
async fn sandbox_exec_curl(sandbox_name: &str, target_port: u16) -> Result<String, String> {
|
||||
async fn sandbox_exec_http(sandbox_name: &str, target_port: u16) -> Result<String, String> {
|
||||
let url = format!("http://host.openshell.internal:{target_port}/resource");
|
||||
let script = format!(
|
||||
"import urllib.request; print(urllib.request.urlopen({url:?}, timeout=5).read().decode())"
|
||||
);
|
||||
let mut last_output = String::new();
|
||||
for _ in 0..20 {
|
||||
let output = openshell_cmd()
|
||||
@@ -785,11 +787,9 @@ async fn sandbox_exec_curl(sandbox_name: &str, target_port: u16) -> Result<Strin
|
||||
sandbox_name,
|
||||
"--no-tty",
|
||||
"--",
|
||||
"curl",
|
||||
"-fsS",
|
||||
"--max-time",
|
||||
"5",
|
||||
&url,
|
||||
"python3",
|
||||
"-c",
|
||||
&script,
|
||||
])
|
||||
.output()
|
||||
.await
|
||||
@@ -807,7 +807,7 @@ async fn sandbox_exec_curl(sandbox_name: &str, target_port: u16) -> Result<Strin
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
Err(format!(
|
||||
"curl to protected target did not succeed at {url}; last attempt:\n{last_output}"
|
||||
"HTTP request to protected target did not succeed at {url}; last attempt:\n{last_output}"
|
||||
))
|
||||
}
|
||||
|
||||
@@ -874,11 +874,11 @@ async fn podman_provider_token_exchange_injects_bearer_header() {
|
||||
start_container_token_endpoint(&sandbox.name, token_port)
|
||||
.await
|
||||
.expect("start container token endpoint");
|
||||
let curl_output = match sandbox_exec_curl(&sandbox.name, target_port).await {
|
||||
let request_output = match sandbox_exec_http(&sandbox.name, target_port).await {
|
||||
Ok(output) => output,
|
||||
Err(err) => {
|
||||
let debug = provider_token_debug(&sandbox.name, token_port, target_port).await;
|
||||
panic!("curl protected target from kept sandbox: {err}\n{debug}");
|
||||
panic!("request protected target from kept sandbox: {err}\n{debug}");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -887,8 +887,8 @@ async fn podman_provider_token_exchange_injects_bearer_header() {
|
||||
sandbox.cleanup().await;
|
||||
|
||||
assert!(
|
||||
curl_output.contains("token-exchange-ok"),
|
||||
request_output.contains("token-exchange-ok"),
|
||||
"protected target should receive the final exchanged bearer token:\n{}",
|
||||
curl_output
|
||||
request_output
|
||||
);
|
||||
}
|
||||
|
||||
@@ -738,20 +738,10 @@ async fn vm_corporate_proxy_routes_approved_tls_egress() {
|
||||
// ── Run the workload ──────────────────────────────────────────────
|
||||
let (_policy, policy_path) = temp_file_with(&policy_yaml(&ports), "policy file");
|
||||
let script = workload_script(&ports);
|
||||
// The workload runs python3, which the VM driver's default
|
||||
// nvcr.io/nvidia/base/ubuntu image does not ship.
|
||||
let mut sandbox = SandboxGuard::create(&[
|
||||
"--from",
|
||||
"base",
|
||||
"--policy",
|
||||
&policy_path,
|
||||
"--",
|
||||
"python3",
|
||||
"-c",
|
||||
&script,
|
||||
])
|
||||
.await
|
||||
.expect("create VM sandbox behind the corporate proxy");
|
||||
let mut sandbox =
|
||||
SandboxGuard::create(&["--policy", &policy_path, "--", "python3", "-c", &script])
|
||||
.await
|
||||
.expect("create VM sandbox behind the corporate proxy");
|
||||
|
||||
assert_proxied_egress(
|
||||
&sandbox.create_output,
|
||||
@@ -815,18 +805,10 @@ async fn vm_corporate_proxy_trusts_ca_bundle_for_https_proxy() {
|
||||
|
||||
let (_policy, policy_path) = temp_file_with(&policy_yaml(&ports), "policy file");
|
||||
let script = workload_script(&ports);
|
||||
let mut sandbox = SandboxGuard::create(&[
|
||||
"--from",
|
||||
"base",
|
||||
"--policy",
|
||||
&policy_path,
|
||||
"--",
|
||||
"python3",
|
||||
"-c",
|
||||
&script,
|
||||
])
|
||||
.await
|
||||
.expect("create VM sandbox behind the https corporate proxy");
|
||||
let mut sandbox =
|
||||
SandboxGuard::create(&["--policy", &policy_path, "--", "python3", "-c", &script])
|
||||
.await
|
||||
.expect("create VM sandbox behind the https corporate proxy");
|
||||
|
||||
let proxy_logs = proxy.logs().expect("read https proxy logs");
|
||||
assert!(
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
# OPENSHELL_E2E_DOCKER_SANDBOX_IMAGE=...
|
||||
# OPENSHELL_E2E_DOCKER_SANDBOX_IMAGE_PULL_POLICY=always|if_not_present|never
|
||||
#
|
||||
# The default community sandbox image uses :latest. This wrapper refreshes it
|
||||
# The default sandbox image uses a mutable tag. This wrapper refreshes it
|
||||
# before starting the gateway, while the Docker driver defaults to
|
||||
# if_not_present so local Dockerfile-built images remain usable.
|
||||
#
|
||||
@@ -516,7 +516,7 @@ build_local_docker_sandbox_runtime_image_if_required "${SANDBOX_RUNTIME_IMAGE}"
|
||||
ensure_docker_sandbox_runtime_image "${SANDBOX_RUNTIME_IMAGE}"
|
||||
echo "Using Docker sandbox runtime image: ${SANDBOX_RUNTIME_IMAGE}"
|
||||
|
||||
DEFAULT_SANDBOX_IMAGE="ghcr.io/nvidia/openshell-community/sandboxes/base:latest"
|
||||
DEFAULT_SANDBOX_IMAGE="nvcr.io/nvidia/base/ubuntu:24.04"
|
||||
SANDBOX_IMAGE="${OPENSHELL_E2E_DOCKER_SANDBOX_IMAGE:-${OPENSHELL_SANDBOX_IMAGE:-${DEFAULT_SANDBOX_IMAGE}}}"
|
||||
SANDBOX_IMAGE_PULL_POLICY="${OPENSHELL_E2E_DOCKER_SANDBOX_IMAGE_PULL_POLICY:-${OPENSHELL_SANDBOX_IMAGE_PULL_POLICY:-if_not_present}}"
|
||||
if ! ensure_sandbox_image_available "${SANDBOX_IMAGE}"; then
|
||||
|
||||
@@ -613,7 +613,7 @@ SANDBOX_BOUNDARY_IMAGE="$(resolve_podman_sandbox_runtime_image)"
|
||||
ensure_podman_sandbox_runtime_image "${SANDBOX_BOUNDARY_IMAGE}"
|
||||
echo "Using Podman sandbox runtime image: ${SANDBOX_BOUNDARY_IMAGE}"
|
||||
|
||||
DEFAULT_SANDBOX_IMAGE="ghcr.io/nvidia/openshell-community/sandboxes/base:latest"
|
||||
DEFAULT_SANDBOX_IMAGE="nvcr.io/nvidia/base/ubuntu:24.04"
|
||||
SANDBOX_IMAGE_REQUEST="${OPENSHELL_E2E_PODMAN_SANDBOX_IMAGE:-${OPENSHELL_SANDBOX_IMAGE:-${DEFAULT_SANDBOX_IMAGE}}}"
|
||||
if [ "${OPENSHELL_E2E_REQUIRE_DIGEST_PINNED_SANDBOX_IMAGE:-0}" = "1" ] \
|
||||
&& ! [[ "${SANDBOX_IMAGE_REQUEST}" =~ ^[^@]+@sha256:[0-9a-f]{64}$ ]]; then
|
||||
|
||||
Reference in New Issue
Block a user