#!/usr/bin/env bash # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # Run an e2e command against a Docker-backed OpenShell gateway. # # Modes: # - OPENSHELL_GATEWAY_ENDPOINT unset: # Build and start an ephemeral standalone gateway with the Docker compute # driver, then run the command against that gateway. # - OPENSHELL_GATEWAY_ENDPOINT=http://host:port: # Use the existing plaintext gateway endpoint and run the command. # # HTTPS endpoint-only mode is intentionally unsupported here. Use a named # gateway config when mTLS materials are needed. # # Sandbox image overrides: # OPENSHELL_E2E_DOCKER_SANDBOX_IMAGE=... # OPENSHELL_E2E_DOCKER_SANDBOX_IMAGE_PULL_POLICY=always|if_not_present|never # SANDBOX_IMAGE=... (trusted sandbox runtime override) # Supervisor image overrides: # SUPERVISOR_IMAGE=... (common test-wrapper override) # OPENSHELL_SUPERVISOR_IMAGE=... (existing compatibility override) # OPENSHELL_DOCKER_SUPERVISOR_IMAGE=... (Docker-specific override) # # 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. # set -euo pipefail if [ "$#" -eq 0 ]; then echo "Usage: e2e/with-docker-gateway.sh [args...]" >&2 exit 2 fi ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # shellcheck source=e2e/support/gateway-common.sh source "${ROOT}/e2e/support/gateway-common.sh" e2e_preserve_mise_dirs require_container_engine_lane() { local lane=$1 local label=$2 local selected_engine selected_driver if [ -n "${OPENSHELL_E2E_CONTAINER_ENGINE:-}" ]; then echo "ERROR: OPENSHELL_E2E_CONTAINER_ENGINE is no longer supported." >&2 echo " Set CONTAINER_ENGINE=${lane} for the ${label} e2e lane, or unset it." >&2 exit 2 fi selected_engine="$(printf '%s' "${CONTAINER_ENGINE:-}" | tr '[:upper:]' '[:lower:]')" selected_driver="$(printf '%s' "${OPENSHELL_E2E_DRIVER:-}" | tr '[:upper:]' '[:lower:]')" if [ -n "${selected_engine}" ] && [ "${selected_engine}" != "${lane}" ]; then echo "ERROR: CONTAINER_ENGINE=${CONTAINER_ENGINE} conflicts with the ${label} e2e lane." >&2 echo " Set CONTAINER_ENGINE=${lane} or unset CONTAINER_ENGINE." >&2 exit 2 fi if [ -n "${selected_driver}" ] && [ "${selected_driver}" != "${lane}" ]; then echo "ERROR: OPENSHELL_E2E_DRIVER=${OPENSHELL_E2E_DRIVER} conflicts with the ${label} e2e lane." >&2 echo " Set OPENSHELL_E2E_DRIVER=${lane} or unset OPENSHELL_E2E_DRIVER." >&2 exit 2 fi export CONTAINER_ENGINE="${lane}" export OPENSHELL_E2E_DRIVER="${lane}" } require_container_engine_lane docker Docker CONTAINER_ENGINE_QUIET="${CONTAINER_ENGINE_QUIET:-1}" # shellcheck source=tasks/scripts/container-engine.sh source "${ROOT}/tasks/scripts/container-engine.sh" github_actions_host_docker_tmpdir() { if [ "${GITHUB_ACTIONS:-}" != "true" ] \ || [ ! -S /var/run/docker.sock ] \ || [ ! -d /__w/_temp ]; then return 1 fi # Container jobs talk to the host Docker daemon. Bind mount source paths must # exist on the host, but the gateway also validates those same paths inside # the job container before handing them to Docker. This must be a real mount # rather than a symlink because the Docker driver canonicalizes file paths. if [ -d /home/runner/_work/_temp ]; then printf '%s\n' /home/runner/_work/_temp return 0 fi echo "ERROR: GitHub Actions Docker e2e requires /home/runner/_work mounted inside the job container." >&2 echo " Mount /home/runner/_work:/home/runner/_work so Docker bind paths resolve on both sides." >&2 return 2 } if WORKDIR_PARENT="$(github_actions_host_docker_tmpdir)"; then : else status=$? if [ "${status}" -eq 2 ]; then exit 2 fi WORKDIR_PARENT="${TMPDIR:-/tmp}" fi e2e_align_docker_host_with_cli_context WORKDIR_PARENT="${WORKDIR_PARENT%/}" WORKDIR="$(mktemp -d "${WORKDIR_PARENT}/openshell-e2e-gateway.XXXXXX")" GATEWAY_BIN="" CLI_BIN="" GATEWAY_PID="" GATEWAY_LOG="${WORKDIR}/gateway.log" GATEWAY_PID_FILE="${WORKDIR}/gateway.pid" GATEWAY_ARGS_FILE="${WORKDIR}/gateway.args" DRIVER_BIN="" DRIVER_PID="" DRIVER_LOG="${WORKDIR}/docker-driver.log" DRIVER_SOCKET="${WORKDIR}/compute-driver.sock" DRIVER_CONFIG="${WORKDIR}/docker-driver.toml" E2E_NAMESPACE="" DOCKER_NETWORK_NAME="" DOCKER_NETWORK_CONNECTED_CONTAINER="" DOCKER_NETWORK_MANAGED=0 GPU_MODE="${OPENSHELL_E2E_DOCKER_GPU:-0}" OIDC_MODE="${OPENSHELL_E2E_OIDC_GATEWAY:-0}" OIDC_ISSUER="${OPENSHELL_E2E_OIDC_ISSUER:-}" if [ "${OIDC_MODE}" = "1" ] && [ -z "${OIDC_ISSUER}" ]; then echo "ERROR: OPENSHELL_E2E_OIDC_ISSUER is required when OPENSHELL_E2E_OIDC_GATEWAY=1" >&2 exit 2 fi # Isolate CLI/SDK gateway metadata from the developer's real config. export XDG_CONFIG_HOME="${WORKDIR}/config" export XDG_DATA_HOME="${WORKDIR}/data" # Docker e2e runs in a GitHub Actions container while talking to the host # Docker daemon. Keep gateway state in the host-visible workdir so driver-owned # bind mounts, including sandbox JWT files, resolve on both sides. export XDG_STATE_HOME="${WORKDIR}/state" cleanup() { local exit_code=$? e2e_stop_gateway "${GATEWAY_PID}" "${GATEWAY_PID_FILE}" e2e_stop_process "${DRIVER_PID}" "external Docker compute driver" if [ "${exit_code}" -ne 0 ] \ && [ -n "${E2E_NAMESPACE}" ] \ && command -v docker >/dev/null 2>&1; then local ids ids=$(docker ps -aq \ --filter "label=openshell.ai/managed-by=openshell" \ --filter "label=openshell.ai/sandbox-namespace=${E2E_NAMESPACE}" \ 2>/dev/null || true) if [ -n "${ids}" ]; then echo "=== sandbox container logs (preserved for debugging) ===" for id in ${ids}; do echo "--- container ${id} (inspect) ---" docker inspect --format '{{.Name}} state={{.State.Status}} exit={{.State.ExitCode}} restarts={{.RestartCount}} error={{.State.Error}}' "${id}" 2>/dev/null || true echo "--- container ${id} (last 80 log lines) ---" docker logs --tail 80 "${id}" 2>&1 || true done echo "=== end sandbox container logs ===" fi fi if [ -n "${E2E_NAMESPACE}" ] && command -v docker >/dev/null 2>&1; then local stale stale=$(docker ps -aq \ --filter "label=openshell.ai/managed-by=openshell" \ --filter "label=openshell.ai/sandbox-namespace=${E2E_NAMESPACE}" \ 2>/dev/null || true) if [ -n "${stale}" ]; then # shellcheck disable=SC2086 docker rm -f ${stale} >/dev/null 2>&1 || true fi fi if [ -n "${DOCKER_NETWORK_CONNECTED_CONTAINER}" ] \ && [ -n "${DOCKER_NETWORK_NAME}" ] \ && command -v docker >/dev/null 2>&1; then docker network disconnect -f \ "${DOCKER_NETWORK_NAME}" \ "${DOCKER_NETWORK_CONNECTED_CONTAINER}" >/dev/null 2>&1 || true fi if [ "${DOCKER_NETWORK_MANAGED}" = "1" ] \ && [ -n "${DOCKER_NETWORK_NAME}" ] \ && command -v docker >/dev/null 2>&1; then docker network rm "${DOCKER_NETWORK_NAME}" >/dev/null 2>&1 || true fi e2e_print_gateway_log_on_failure "${exit_code}" "${GATEWAY_LOG}" if [ "${exit_code}" -ne 0 ] && [ -f "${DRIVER_LOG}" ]; then echo "=== external Docker compute driver log ===" cat "${DRIVER_LOG}" || true echo "=== end external Docker compute driver log ===" fi rm -rf "${WORKDIR}" 2>/dev/null || true } trap cleanup EXIT ensure_e2e_docker_network() { local network=$1 if docker network inspect "${network}" >/dev/null 2>&1; then return 0 fi docker network create \ --driver bridge \ --attachable \ --label openshell.ai/managed-by=openshell \ --label "openshell.ai/sandbox-namespace=${E2E_NAMESPACE}" \ "${network}" >/dev/null DOCKER_NETWORK_MANAGED=1 } github_actions_container_id() { if [ "${GITHUB_ACTIONS:-}" != "true" ] || [ ! -f /.dockerenv ]; then return 1 fi local container container="$(hostname)" if docker inspect "${container}" >/dev/null 2>&1; then printf '%s\n' "${container}" return 0 fi return 1 } connect_current_container_to_docker_network() { local network=$1 local container if ! container="$(github_actions_container_id)"; then return 1 fi local connect_err="${WORKDIR}/docker-network-connect.err" if ! docker network connect \ --alias host.openshell.internal \ "${network}" \ "${container}" 2>"${connect_err}"; then if ! grep -qi "already exists" "${connect_err}"; then cat "${connect_err}" >&2 return 1 fi fi DOCKER_NETWORK_CONNECTED_CONTAINER="${container}" local container_ip container_ip="$(docker inspect \ --format "{{with index .NetworkSettings.Networks \"${network}\"}}{{.IPAddress}}{{end}}" \ "${container}")" if [ -z "${container_ip}" ]; then echo "ERROR: failed to resolve current job container IP on Docker network ${network}" >&2 return 1 fi GATEWAY_HOST_ALIAS_IP="${container_ip}" } if [ -n "${OPENSHELL_GATEWAY_ENDPOINT:-}" ]; then case "${OPENSHELL_GATEWAY_ENDPOINT}" in http://*) ;; https://*) echo "ERROR: OPENSHELL_GATEWAY_ENDPOINT endpoint mode is HTTP-only for e2e." >&2 echo " Register a named gateway with mTLS config instead of using a raw HTTPS endpoint." >&2 exit 2 ;; *) echo "ERROR: OPENSHELL_GATEWAY_ENDPOINT must start with http:// for e2e endpoint mode." >&2 exit 2 ;; esac GATEWAY_NAME="${OPENSHELL_GATEWAY:-openshell-e2e-endpoint}" e2e_register_plaintext_gateway \ "${XDG_CONFIG_HOME}" \ "${GATEWAY_NAME}" \ "${OPENSHELL_GATEWAY_ENDPOINT}" \ "$(e2e_endpoint_port "${OPENSHELL_GATEWAY_ENDPOINT}")" export OPENSHELL_GATEWAY="${GATEWAY_NAME}" export OPENSHELL_PROVISION_TIMEOUT="${OPENSHELL_PROVISION_TIMEOUT:-180}" export OPENSHELL_E2E_DRIVER="docker" echo "Using existing e2e gateway endpoint: ${OPENSHELL_GATEWAY_ENDPOINT}" "$@" exit $? fi # ── Preflight for managed Docker gateway mode ──────────────────────── if ! command -v docker >/dev/null 2>&1; then echo "ERROR: docker CLI is required to run Docker-backed e2e tests" >&2 exit 2 fi if ! docker info >/dev/null 2>&1; then echo "ERROR: docker daemon is not reachable (docker info failed)" >&2 exit 2 fi if [ "${GPU_MODE}" = "1" ]; then DOCKER_CDI_SPEC_DIRS="$(docker info --format '{{json .CDISpecDirs}}' 2>/dev/null || true)" if [ -z "${DOCKER_CDI_SPEC_DIRS}" ] \ || [ "${DOCKER_CDI_SPEC_DIRS}" = "null" ] \ || [ "${DOCKER_CDI_SPEC_DIRS}" = "[]" ] \ || [ "${DOCKER_CDI_SPEC_DIRS}" = "" ]; then echo "ERROR: Docker GPU e2e requires Docker CDI support." >&2 echo " Generate CDI specs and restart Docker, then verify docker info reports CDISpecDirs." >&2 exit 2 fi fi resolve_docker_supervisor_image() { if [ -n "${OPENSHELL_DOCKER_SUPERVISOR_IMAGE:-}" ]; then printf '%s\n' "${OPENSHELL_DOCKER_SUPERVISOR_IMAGE}" return 0 fi if [ -n "${OPENSHELL_SUPERVISOR_IMAGE:-}" ]; then printf '%s\n' "${OPENSHELL_SUPERVISOR_IMAGE}" return 0 fi if [ -n "${SUPERVISOR_IMAGE:-}" ]; then if [ -n "${CI:-}" ] && [ -z "${IMAGE_TAG:-}" ] \ && ! e2e_image_reference_is_complete "${SUPERVISOR_IMAGE}"; then echo "ERROR: IMAGE_TAG must be set in CI when SUPERVISOR_IMAGE is repository-only." >&2 exit 2 fi printf '%s\n' "$(e2e_resolve_image_reference "${SUPERVISOR_IMAGE}" "${IMAGE_TAG:-dev}")" return 0 fi if [ -n "${CI:-}" ]; then if [ -z "${IMAGE_TAG:-}" ]; then echo "ERROR: IMAGE_TAG must be set in CI when no Docker supervisor image override is provided." >&2 exit 2 fi local registry="${OPENSHELL_REGISTRY:-ghcr.io/nvidia/openshell}" printf '%s/supervisor:%s\n' "${registry%/}" "${IMAGE_TAG}" return 0 fi printf '%s\n' "openshell/supervisor:dev" } resolve_docker_sandbox_runtime_image() { if [ -n "${OPENSHELL_DOCKER_SANDBOX_RUNTIME_IMAGE:-}" ]; then printf '%s\n' "${OPENSHELL_DOCKER_SANDBOX_RUNTIME_IMAGE}" return 0 fi if [ -n "${OPENSHELL_SANDBOX_RUNTIME_IMAGE:-}" ]; then printf '%s\n' "${OPENSHELL_SANDBOX_RUNTIME_IMAGE}" return 0 fi if [ -n "${SANDBOX_IMAGE:-}" ]; then printf '%s\n' "$(e2e_resolve_image_reference "${SANDBOX_IMAGE}" "${IMAGE_TAG:-dev}")" return 0 fi if [ -n "${CI:-}" ]; then if [ -z "${IMAGE_TAG:-}" ]; then echo "ERROR: IMAGE_TAG must be set in CI when no Docker sandbox runtime image override is provided." >&2 exit 2 fi local registry="${OPENSHELL_REGISTRY:-ghcr.io/nvidia/openshell}" printf '%s/sandbox:%s\n' "${registry%/}" "${IMAGE_TAG}" return 0 fi printf '%s\n' "openshell/sandbox:dev" } docker_pull_with_retry() { local image=$1 local attempts=4 local delay=10 local attempt=1 while [ "${attempt}" -le "${attempts}" ]; do if [ "${attempt}" -gt 1 ]; then echo "Retrying Docker pull for ${image} (attempt ${attempt}/${attempts})..." fi if docker pull "${image}"; then return 0 fi if [ "${attempt}" -lt "${attempts}" ]; then echo "Docker pull failed for ${image}; retrying in ${delay}s..." >&2 sleep "${delay}" fi attempt=$((attempt + 1)) done return 1 } build_local_docker_supervisor_image_if_required() { local image=$1 if [ "${image}" != "openshell/supervisor:dev" ]; then return 0 fi local daemon_arch daemon_arch="$(ce_info_arch)" echo "Building local Docker supervisor image ${image} for linux/${daemon_arch}..." CONTAINER_ENGINE=docker DOCKER_PLATFORM="linux/${daemon_arch}" IMAGE_TAG=dev \ bash "${ROOT}/tasks/scripts/docker-build-image.sh" supervisor if docker image inspect "${image}" >/dev/null 2>&1; then return 0 fi echo "ERROR: expected supervisor image '${image}' after local build." >&2 exit 2 } build_local_docker_sandbox_runtime_image_if_required() { local image=$1 if [ "${image}" != "openshell/sandbox:dev" ]; then return 0 fi local daemon_arch daemon_arch="$(ce_info_arch)" echo "Building local Docker sandbox runtime image ${image} for linux/${daemon_arch}..." CONTAINER_ENGINE=docker DOCKER_PLATFORM="linux/${daemon_arch}" IMAGE_TAG=dev \ bash "${ROOT}/tasks/scripts/docker-build-image.sh" sandbox if docker image inspect "${image}" >/dev/null 2>&1; then return 0 fi echo "ERROR: expected sandbox runtime image '${image}' after local build." >&2 exit 2 } ensure_docker_supervisor_image() { local image=$1 if docker image inspect "${image}" >/dev/null 2>&1; then return 0 fi echo "Pulling Docker supervisor image ${image}..." if docker_pull_with_retry "${image}"; then return 0 fi echo "ERROR: supervisor image '${image}' is not available." >&2 echo " Build it, push it, or set SUPERVISOR_IMAGE/OPENSHELL_SUPERVISOR_IMAGE to a pullable image." >&2 exit 2 } ensure_docker_sandbox_runtime_image() { local image=$1 if docker image inspect "${image}" >/dev/null 2>&1; then return 0 fi echo "Pulling Docker sandbox runtime image ${image}..." if docker_pull_with_retry "${image}"; then return 0 fi echo "ERROR: sandbox runtime image '${image}' is not available." >&2 echo " Build it, push it, or set OPENSHELL_SANDBOX_RUNTIME_IMAGE to a pullable image." >&2 exit 2 } image_uses_latest_tag() { local image=$1 local last_component # Digest references are immutable even if the tag portion says latest. if [[ "${image}" == *@* ]]; then return 1 fi last_component="${image##*/}" # Docker treats an omitted tag as :latest. if [[ "${last_component}" != *:* ]]; then return 0 fi [[ "${last_component}" == *:latest ]] } ensure_sandbox_image_available() { local image=$1 if image_uses_latest_tag "${image}"; then echo "Refreshing latest sandbox image ${image}..." docker_pull_with_retry "${image}" return fi if docker image inspect "${image}" >/dev/null 2>&1; then return fi echo "Pulling ${image}..." docker_pull_with_retry "${image}" } e2e_build_gateway_binaries "${ROOT}" TARGET_DIR GATEWAY_BIN CLI_BIN export OPENSHELL_BIN="${CLI_BIN}" if [ "${OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER:-0}" = "1" ]; then e2e_build_external_driver \ "${ROOT}" openshell-driver-docker openshell-driver-docker DRIVER_BIN fi SUPERVISOR_IMAGE="$(resolve_docker_supervisor_image)" export OPENSHELL_SUPERVISOR_IMAGE="${SUPERVISOR_IMAGE}" build_local_docker_supervisor_image_if_required "${SUPERVISOR_IMAGE}" ensure_docker_supervisor_image "${SUPERVISOR_IMAGE}" echo "Using Docker supervisor image: ${SUPERVISOR_IMAGE}" SANDBOX_RUNTIME_IMAGE="$(resolve_docker_sandbox_runtime_image)" 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="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 echo "ERROR: sandbox image '${SANDBOX_IMAGE}' is not available." >&2 exit 2 fi HOST_PORT=$(e2e_pick_port) HEALTH_PORT=$(e2e_pick_port) STATE_DIR="${XDG_STATE_HOME}" mkdir -p "${STATE_DIR}" JWT_DIR="${STATE_DIR}/jwt" E2E_NAMESPACE="e2e-docker-$$-${HOST_PORT}" DOCKER_NETWORK_NAME="${E2E_NAMESPACE}" GATEWAY_HOST_ALIAS_IP="" GATEWAY_BIND_IP="127.0.0.1" SUPERVISOR_GATEWAY_HOST="127.0.0.1" ensure_e2e_docker_network "${DOCKER_NETWORK_NAME}" export OPENSHELL_E2E_DOCKER_NETWORK_NAME="${DOCKER_NETWORK_NAME}" export OPENSHELL_E2E_NETWORK_NAME="${DOCKER_NETWORK_NAME}" export OPENSHELL_E2E_SANDBOX_NAMESPACE="${E2E_NAMESPACE}" export OPENSHELL_E2E_DRIVER="docker" if connect_current_container_to_docker_network "${DOCKER_NETWORK_NAME}"; then echo "Connected CI job container to Docker network ${DOCKER_NETWORK_NAME} (${GATEWAY_HOST_ALIAS_IP})." # Container jobs use the host Docker daemon. The host-networked supervisor # therefore cannot reach the gateway through the job container's loopback; # it reaches the gateway through the job container's address on this bridge. GATEWAY_BIND_IP="0.0.0.0" SUPERVISOR_GATEWAY_HOST="${GATEWAY_HOST_ALIAS_IP}" else GATEWAY_HOST_ALIAS_IP="" fi PKI_DIR="${WORKDIR}/pki" if [ -n "${GATEWAY_HOST_ALIAS_IP}" ]; then e2e_generate_pki \ "${GATEWAY_BIN}" \ "${PKI_DIR}" \ "${SUPERVISOR_GATEWAY_HOST}" else e2e_generate_pki "${GATEWAY_BIN}" "${PKI_DIR}" fi export OPENSHELL_E2E_GATEWAY_CA_CERT="${PKI_DIR}/ca.crt" GATEWAY_ENDPOINT="https://${SUPERVISOR_GATEWAY_HOST}:${HOST_PORT}" echo "Starting openshell-gateway on port ${HOST_PORT} (namespace: ${E2E_NAMESPACE})..." echo "Using sandbox image: ${SANDBOX_IMAGE} (pull policy: ${SANDBOX_IMAGE_PULL_POLICY})" e2e_generate_gateway_jwt "${JWT_DIR}" # Driver-specific options moved from CLI flags into a TOML config table # (commit 560550d2). Synthesize a minimal config here and pass --config. # Quote a value as a TOML basic string: wrap in double quotes and escape # any embedded backslashes / double quotes. Adequate for paths, image # refs, and namespace identifiers — none of which contain TOML special # characters in practice. toml_string() { local value="$1" value="${value//\\/\\\\}" value="${value//\"/\\\"}" printf '"%s"' "${value}" } GATEWAY_CONFIG="${STATE_DIR}/gateway.toml" { printf '[openshell]\nversion = 2\n\n' printf '[openshell.gateway]\nlog_level = "info"\n' printf 'guest_tls_ca = %s\n' "$(toml_string "${PKI_DIR}/ca.crt")" e2e_write_gateway_jwt_config "${JWT_DIR}" "openshell-e2e-docker-${HOST_PORT}" if [ "${OIDC_MODE}" != "1" ]; then e2e_write_gateway_mtls_auth_config if [ -n "${OPENSHELL_OIDC_ISSUER:-}" ]; then e2e_write_gateway_oidc_config "${OPENSHELL_OIDC_ISSUER}" fi fi printf '[openshell.drivers.docker]\n' if [ "${OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER:-0}" = "1" ]; then printf 'socket_path = %s\n' "$(toml_string "${DRIVER_SOCKET}")" else printf 'allow_driver_config = true\n' printf 'sandbox_label = %s\n' "$(toml_string "${E2E_NAMESPACE}")" # Container jobs need their bridge address; host runs exercise discovery. if [ -n "${GATEWAY_HOST_ALIAS_IP}" ]; then printf 'grpc_endpoint = %s\n' "$(toml_string "${GATEWAY_ENDPOINT}")" fi printf 'default_image = %s\n' "$(toml_string "${SANDBOX_IMAGE}")" printf 'image_pull_policy = %s\n' "$(toml_string "${SANDBOX_IMAGE_PULL_POLICY}")" printf 'enable_bind_mounts = true\n' printf 'sandbox_runtime_image = %s\n' "$(toml_string "${SANDBOX_RUNTIME_IMAGE}")" printf 'supervisor_image = %s\n' "$(toml_string "${SUPERVISOR_IMAGE}")" printf '\n[openshell.drivers.docker.resource_admission]\n' printf 'enabled = false\n' fi } > "${GATEWAY_CONFIG}" if [ "${OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER:-0}" = "1" ]; then { printf 'sandbox_label = %s\n' "$(toml_string "${E2E_NAMESPACE}")" if [ -n "${GATEWAY_HOST_ALIAS_IP}" ]; then printf 'grpc_endpoint = %s\n' "$(toml_string "${GATEWAY_ENDPOINT}")" fi printf 'default_image = %s\n' "$(toml_string "${SANDBOX_IMAGE}")" printf 'image_pull_policy = %s\n' "$(toml_string "${SANDBOX_IMAGE_PULL_POLICY}")" printf 'guest_tls_ca = %s\n' "$(toml_string "${PKI_DIR}/ca.crt")" printf 'enable_bind_mounts = true\n' printf 'sandbox_runtime_image = %s\n' "$(toml_string "${SANDBOX_RUNTIME_IMAGE}")" printf 'supervisor_image = %s\n' "$(toml_string "${SUPERVISOR_IMAGE}")" } >"${DRIVER_CONFIG}" "${DRIVER_BIN}" \ --bind-socket "${DRIVER_SOCKET}" \ --config "${DRIVER_CONFIG}" \ --gateway-bind "${GATEWAY_BIND_IP}:${HOST_PORT}" \ >"${DRIVER_LOG}" 2>&1 & DRIVER_PID=$! e2e_wait_for_socket \ "${DRIVER_SOCKET}" "${DRIVER_PID}" "external Docker compute driver" fi GATEWAY_ARGS=( --config "${GATEWAY_CONFIG}" --bind-address "${GATEWAY_BIND_IP}" --port "${HOST_PORT}" --health-port "${HEALTH_PORT}" --compute-driver docker --tls-cert "${PKI_DIR}/server/tls.crt" --tls-key "${PKI_DIR}/server/tls.key" # edge_tunnel_e2e exercises the opt-in WebSocket tunnel. --enable-websocket-tunnel true --db-url "sqlite:${STATE_DIR}/gateway.db?mode=rwc" ) if [ "${OIDC_MODE}" = "1" ]; then GATEWAY_ARGS+=( --oidc-issuer "${OIDC_ISSUER}" --oidc-audience openshell-cli --oidc-scopes-claim scope ) case "${OIDC_ISSUER}" in http://127.*|http://\[::1\]*) GATEWAY_ARGS+=(--oidc-dangerously-allow-insecure-http true) ;; esac else GATEWAY_ARGS+=( --tls-client-ca "${PKI_DIR}/ca.crt" ) fi e2e_write_gateway_args_file "${GATEWAY_ARGS_FILE}" "${GATEWAY_ARGS[@]}" e2e_export_gateway_restart_metadata \ "${GATEWAY_BIN}" \ "${GATEWAY_ARGS_FILE}" \ "${GATEWAY_LOG}" \ "${GATEWAY_PID_FILE}" "${GATEWAY_BIN}" "${GATEWAY_ARGS[@]}" >"${GATEWAY_LOG}" 2>&1 & GATEWAY_PID=$! printf '%s\n' "${GATEWAY_PID}" >"${GATEWAY_PID_FILE}" GATEWAY_NAME="openshell-e2e-docker-${HOST_PORT}" CLI_GATEWAY_ENDPOINT="https://127.0.0.1:${HOST_PORT}" if [ "${OIDC_MODE}" = "1" ]; then export OPENSHELL_E2E_OIDC_GATEWAY_ENDPOINT="${CLI_GATEWAY_ENDPOINT}" else e2e_register_mtls_gateway \ "${XDG_CONFIG_HOME}" \ "${GATEWAY_NAME}" \ "${CLI_GATEWAY_ENDPOINT}" \ "${HOST_PORT}" \ "${PKI_DIR}" \ "${OPENSHELL_OIDC_ISSUER:-}" fi export OPENSHELL_GATEWAY="${GATEWAY_NAME}" export OPENSHELL_PROVISION_TIMEOUT="${OPENSHELL_PROVISION_TIMEOUT:-180}" if [ "${OIDC_MODE}" = "1" ] || [ -n "${OPENSHELL_OIDC_ISSUER:-}" ]; then export OPENSHELL_E2E_OIDC=1 export OPENSHELL_E2E_OIDC_SCOPES=1 fi echo "Waiting for gateway to become healthy..." elapsed=0 timeout=120 while [ "${elapsed}" -lt "${timeout}" ]; do if ! kill -0 "${GATEWAY_PID}" 2>/dev/null; then echo "ERROR: openshell-gateway exited before becoming healthy" exit 1 fi if curl -sf "http://127.0.0.1:${HEALTH_PORT}/healthz" >/dev/null 2>&1; then echo "Gateway healthy after ${elapsed}s." break fi sleep 2 elapsed=$((elapsed + 2)) done if [ "${elapsed}" -ge "${timeout}" ]; then echo "ERROR: gateway did not become healthy within ${timeout}s" exit 1 fi # Seed the example profiles the provider tests rely on. The mTLS lanes already # have a registered gateway identity; the OIDC lanes deliberately skip # registration and have no token yet, so establish an administrator session # first rather than importing unauthenticated. if [ "${OIDC_MODE}" = "1" ]; then e2e_register_oidc_admin_session \ "${XDG_CONFIG_HOME}" \ "${GATEWAY_NAME}" \ "${CLI_GATEWAY_ENDPOINT}" \ "${HOST_PORT}" \ "${OIDC_ISSUER}" \ "${OPENSHELL_E2E_OIDC_USERNAME:-admin@test}" \ "${OPENSHELL_E2E_OIDC_PASSWORD:-admin}" \ "${PKI_DIR}" \ "${CLI_BIN}" || exit 1 fi e2e_import_example_provider_profiles "${CLI_BIN}" "${ROOT}" || exit 1 echo "Running e2e command against ${CLI_GATEWAY_ENDPOINT}: $*" "$@"