diff --git a/.agents/skills/test-release-canary/SKILL.md b/.agents/skills/test-release-canary/SKILL.md index 5e8bbf394c..ad000d25be 100644 --- a/.agents/skills/test-release-canary/SKILL.md +++ b/.agents/skills/test-release-canary/SKILL.md @@ -13,7 +13,7 @@ The Release Canary (`.github/workflows/release-canary.yml`) smoke-tests the arti |---|---|---| | `macos` | `macos-latest-xlarge` | `install.sh` resolves the Homebrew formula, brew installs the cask, and `openshell status` reaches the brew-services–backed local gateway with the VM driver. | | `ubuntu` | `ubuntu-latest` | `install.sh` installs the Debian package, the post-install systemd user service starts, and `openshell status` reaches the local gateway with the Docker driver. | -| `fedora` | `fedora:latest` container | `install.sh` installs the RPM packages, the local gateway starts under Podman, and `openshell status` succeeds. | +| `fedora` | `linux-amd64-cpu8` + Fedora Nix VM | `install.sh` installs the RPM packages, the root-owned local gateway starts with rootful Podman, and `openshell status` succeeds. | | `kubernetes` | `ubuntu-latest` + kind | `helm install oci://ghcr.io/nvidia/openshell/helm-chart --version 0.0.0-dev` succeeds in a kind cluster, the gateway pod becomes Ready, port-forward exposes 8080, and the released CLI registers the in-cluster gateway and runs `openshell status` against it. | All canary jobs disable anonymous OpenShell telemetry. Host package jobs inject @@ -116,6 +116,41 @@ Swap `0.0.0-dev` for `0.0.0-dev.` to pin to a specific dev build. Tear down Loopback registration auto-derives the gateway name to `openshell` if `--name` is omitted, which collides with the `install.sh`-installed local gateway — always pass `--name kind` (or another distinct name) when registering in addition to a local install. +## Local Fedora reproduction + +The `fedora` job uses the repository's Nix test-guest harness instead of +running Fedora inside Docker. It can be reproduced on a Linux host with Nix, +KVM, and the repository checkout: + +```shell +export INSTALL_SH_URL="https://raw.githubusercontent.com/NVIDIA/OpenShell/$(git rev-parse HEAD)/install.sh" +nix run .#test-guest -- \ + --distro fedora \ + --with podman-rootful \ + -- \ + sudo env \ + SUDO_USER=root \ + HOME=/root \ + XDG_RUNTIME_DIR=/run/user/0 \ + DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/0/bus \ + OPENSHELL_TELEMETRY_ENABLED=false \ + INSTALL_SH_URL="$INSTALL_SH_URL" \ + bash -s <<'EOF' +set -euo pipefail +mkdir -p "${XDG_RUNTIME_DIR}" +chmod 700 "${XDG_RUNTIME_DIR}" +systemctl start user-runtime-dir@0.service || true +systemctl start user@0.service +systemctl --user daemon-reload +mkdir -p "${HOME}/.config/openshell" +printf 'OPENSHELL_DRIVERS=podman\nOPENSHELL_PODMAN_SOCKET=/run/podman/podman.sock\nOPENSHELL_TELEMETRY_ENABLED=%s\n' \ + "$OPENSHELL_TELEMETRY_ENABLED" > "${HOME}/.config/openshell/gateway.env" +podman --url unix:///run/podman/podman.sock info +curl -LsSf "${INSTALL_SH_URL}" | sh +openshell status +EOF +``` + ## Diagnosing failures | Symptom | Likely cause | Where to look | diff --git a/.github/actions/setup-e2e-sandbox/action.yml b/.github/actions/setup-e2e-sandbox/action.yml new file mode 100644 index 0000000000..11e33cbd96 --- /dev/null +++ b/.github/actions/setup-e2e-sandbox/action.yml @@ -0,0 +1,29 @@ +name: Setup E2E Sandbox +description: Download an architecture-matched prebuilt OpenShell sandbox binary for E2E tests + +inputs: + artifact-prefix: + description: Artifact name prefix; linux- is appended automatically + required: true + +runs: + using: composite + steps: + - name: Download prebuilt sandbox + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ format('{0}-linux-{1}', inputs.artifact-prefix, runner.arch == 'X64' && 'amd64' || 'arm64') }} + path: .e2e/prebuilt-sandbox + + - name: Configure prebuilt sandbox + shell: bash + run: | + set -euo pipefail + sandbox="$GITHUB_WORKSPACE/.e2e/prebuilt-sandbox/openshell-sandbox" + if [[ ! -f "$sandbox" ]]; then + echo "downloaded artifact is missing $sandbox" >&2 + exit 1 + fi + chmod +x "$sandbox" + "$sandbox" --version + echo "OPENSHELL_SANDBOX_BIN=$sandbox" >> "$GITHUB_ENV" diff --git a/.github/workflows/branch-e2e.yml b/.github/workflows/branch-e2e.yml index 2b4d9d5d46..49cf303fe9 100644 --- a/.github/workflows/branch-e2e.yml +++ b/.github/workflows/branch-e2e.yml @@ -136,6 +136,8 @@ jobs: runner: linux-arm64-cpu8 cli-artifact-prefix: rust-binary-cli gateway-artifact-prefix: rust-binary-gateway + # The supervisor build uploads the openshell-sandbox binary artifact. + sandbox-artifact-prefix: rust-binary-supervisor vm-driver-artifact-name: driver-vm-linux-amd64 gpu-e2e: diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index d3e4230636..6e12be530a 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -27,6 +27,11 @@ on: required: false type: string default: "" + sandbox-artifact-prefix: + description: "Optional prebuilt sandbox artifact prefix (artifact suffix is linux-)" + required: false + type: string + default: "" vm-driver-artifact-name: description: "Optional prebuilt VM driver artifact name" required: false @@ -129,6 +134,154 @@ jobs: OPENSHELL_MCP_CONFORMANCE_CLIENT_IMAGE: ${{ format('openshell-mcp-conformance-client:{0}', inputs.image-tag) }} run: ${{ matrix.cmd }} + e2e-podman-rootless: + name: E2E (rust-podman-rootless, Ubuntu 26.04 Nix VM) + # Run rootless Podman inside a Nix-managed Ubuntu guest so Podman, pasta, + # and user-namespace setup are provisioned by versioned repository tooling + # rather than mutable hosted-runner packages. + runs-on: ubuntu-26.04 + timeout-minutes: 60 + env: + MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs['checkout-ref'] || github.sha }} + persist-credentials: false + + - name: Use prebuilt OpenShell CLI + uses: ./.github/actions/setup-e2e-cli + with: + artifact-prefix: ${{ inputs.cli-artifact-prefix }} + + - name: Use prebuilt OpenShell gateway + uses: ./.github/actions/setup-e2e-gateway + with: + artifact-prefix: ${{ inputs.gateway-artifact-prefix }} + + - name: Use prebuilt OpenShell sandbox + uses: ./.github/actions/setup-e2e-sandbox + with: + artifact-prefix: ${{ inputs.sandbox-artifact-prefix }} + + - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1 + with: + github_access_token: ${{ secrets.GITHUB_TOKEN }} + + - uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 + with: + name: openshell + + - name: Install mise + run: | + curl https://mise.run | MISE_VERSION=v2026.4.25 sh + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + echo "$HOME/.local/share/mise/shims" >> "$GITHUB_PATH" + + - name: Install tools + run: mise install --locked + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + build-essential \ + clang \ + cmake \ + libclang-dev \ + libssl-dev \ + libz3-dev \ + openssh-client \ + pkg-config + + - name: Run rootless Podman E2E + run: | + set -euo pipefail + + mise x -- e2e/run.sh \ + --vm ubuntu-26-04 \ + --with podman-rootless \ + --host-cli-bin "$OPENSHELL_BIN" \ + --gateway-bin "$OPENSHELL_GATEWAY_BIN" \ + --sandbox-bin "$OPENSHELL_SANDBOX_BIN" \ + --gateway-config e2e/configs/gateway/podman.toml \ + --features e2e-podman + + e2e-podman-fedora-rootful: + name: E2E (rust-podman-rootful, Fedora Nix VM) + # Run rootful Podman inside a Nix-managed Fedora guest. This exercises the + # RPM-family rootful bridge/listener topology without nesting Podman inside + # a Docker job container. + runs-on: ubuntu-26.04 + timeout-minutes: 60 + env: + MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs['checkout-ref'] || github.sha }} + persist-credentials: false + + - name: Use prebuilt OpenShell CLI + uses: ./.github/actions/setup-e2e-cli + with: + artifact-prefix: ${{ inputs.cli-artifact-prefix }} + + - name: Use prebuilt OpenShell gateway + uses: ./.github/actions/setup-e2e-gateway + with: + artifact-prefix: ${{ inputs.gateway-artifact-prefix }} + + - name: Use prebuilt OpenShell sandbox + uses: ./.github/actions/setup-e2e-sandbox + with: + artifact-prefix: ${{ inputs.sandbox-artifact-prefix }} + + - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1 + with: + github_access_token: ${{ secrets.GITHUB_TOKEN }} + + - uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 + with: + name: openshell + + - name: Install mise + run: | + curl https://mise.run | MISE_VERSION=v2026.4.25 sh + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + echo "$HOME/.local/share/mise/shims" >> "$GITHUB_PATH" + + - name: Install tools + run: mise install --locked + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + build-essential \ + clang \ + cmake \ + libclang-dev \ + libssl-dev \ + libz3-dev \ + openssh-client \ + pkg-config + + - name: Run Fedora rootful Podman E2E + run: | + set -euo pipefail + + mise x -- e2e/run.sh \ + --vm fedora \ + --with podman-rootful \ + --guest-gateway-user root \ + --host-cli-bin "$OPENSHELL_BIN" \ + --gateway-bin "$OPENSHELL_GATEWAY_BIN" \ + --sandbox-bin "$OPENSHELL_SANDBOX_BIN" \ + --gateway-config e2e/configs/gateway/podman-rootful.toml \ + --features e2e-podman \ + --suite sandbox_lifecycle + e2e-vm: name: E2E (rust-vm-${{ matrix.suite }}) # libkrun needs KVM, so this job must run directly on a GitHub-hosted diff --git a/.github/workflows/release-canary.yml b/.github/workflows/release-canary.yml index 937e774db7..1507e41e4a 100644 --- a/.github/workflows/release-canary.yml +++ b/.github/workflows/release-canary.yml @@ -61,53 +61,43 @@ jobs: name: Fedora RPM if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} runs-on: linux-amd64-cpu8 - timeout-minutes: 20 + timeout-minutes: 30 env: - FEDORA_CANARY_CONTAINER: openshell-fedora-canary-${{ github.run_id }}-${{ github.run_attempt }} + INSTALL_SH_URL: https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh steps: - - name: Start Fedora systemd container and root user manager - run: | - set -euo pipefail + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - docker run --detach \ - --name "${FEDORA_CANARY_CONTAINER}" \ - --privileged \ - --cgroupns=host \ - --tmpfs /run \ - --tmpfs /tmp \ - --volume /sys/fs/cgroup:/sys/fs/cgroup:rw \ - fedora:latest \ - bash -lc 'dnf install -y curl dbus-daemon podman systemd && exec /usr/sbin/init' - - for _ in $(seq 1 120); do - if docker exec "${FEDORA_CANARY_CONTAINER}" systemctl list-units --no-pager >/dev/null 2>&1; then - break - fi - if [ "$(docker inspect -f '{{.State.Running}}' "${FEDORA_CANARY_CONTAINER}")" != "true" ]; then - echo "::error::Fedora systemd container exited before systemd became reachable" - docker logs "${FEDORA_CANARY_CONTAINER}" >&2 || true - exit 1 - fi - sleep 1 - done + - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1 + with: + github_access_token: ${{ secrets.GITHUB_TOKEN }} - if ! docker exec "${FEDORA_CANARY_CONTAINER}" systemctl list-units --no-pager >/dev/null 2>&1; then - echo "::error::Fedora systemd container did not become reachable within 120s" - docker logs "${FEDORA_CANARY_CONTAINER}" >&2 || true - exit 1 - fi + - uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 + with: + name: openshell + + - name: Install RPM in Fedora rootful Podman VM and check status + run: | + set -euo pipefail - docker exec --interactive "${FEDORA_CANARY_CONTAINER}" env \ + nix run .#test-guest -- \ + --distro fedora \ + --with podman-rootful \ + -- \ + sudo env \ + SUDO_USER=root \ HOME=/root \ XDG_RUNTIME_DIR=/run/user/0 \ DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/0/bus \ + OPENSHELL_TELEMETRY_ENABLED="$OPENSHELL_TELEMETRY_ENABLED" \ + INSTALL_SH_URL="$INSTALL_SH_URL" \ bash -s <<'EOF' set -euo pipefail - # install.sh manages the RPM gateway as a systemd user unit. This - # container is booted with systemd as PID 1, but it still has no - # login session. Start root's user manager explicitly so the - # installer can test service restart and gateway registration - # instead of its "restart later" fallback. + + # install.sh manages the RPM gateway as a systemd user unit. Start + # root's user manager explicitly so the canary exercises the rootful + # service path on a real Fedora VM. mkdir -p "${XDG_RUNTIME_DIR}" chmod 700 "${XDG_RUNTIME_DIR}" systemctl start user-runtime-dir@0.service || true @@ -125,33 +115,15 @@ jobs: systemctl --user status --no-pager >&2 || true exit 1 fi - EOF - - - name: Install and check status - run: | - set -euo pipefail - docker exec --interactive "${FEDORA_CANARY_CONTAINER}" env \ - HOME=/root \ - XDG_RUNTIME_DIR=/run/user/0 \ - DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/0/bus \ - OPENSHELL_TELEMETRY_ENABLED="$OPENSHELL_TELEMETRY_ENABLED" \ - INSTALL_SH_URL="https://raw.githubusercontent.com/NVIDIA/OpenShell/${{ github.event.workflow_run.head_sha || github.sha }}/install.sh" \ - bash -s <<'EOF' - set -euo pipefail mkdir -p "${HOME}/.config/openshell" - printf 'OPENSHELL_DRIVERS=podman\nOPENSHELL_TELEMETRY_ENABLED=%s\n' \ + printf 'OPENSHELL_DRIVERS=podman\nOPENSHELL_PODMAN_SOCKET=/run/podman/podman.sock\nOPENSHELL_TELEMETRY_ENABLED=%s\n' \ "$OPENSHELL_TELEMETRY_ENABLED" > "${HOME}/.config/openshell/gateway.env" - podman info + podman --url unix:///run/podman/podman.sock info curl -LsSf "${INSTALL_SH_URL}" | sh openshell status EOF - - name: Stop Fedora systemd container - if: always() - run: | - docker rm -f "${FEDORA_CANARY_CONTAINER}" >/dev/null 2>&1 || true - ubuntu-snap: name: Ubuntu Snap if: ${{ github.event.workflow_run.conclusion == 'success' }} diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml index 500fcb4be6..8b677ff07c 100644 --- a/.github/workflows/release-dev.yml +++ b/.github/workflows/release-dev.yml @@ -69,8 +69,16 @@ jobs: cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} auditable: true + build-cli: + needs: [compute-versions] + uses: ./.github/workflows/docker-build.yml + with: + component: cli + cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} + auditable: true + e2e: - needs: [build-gateway, build-supervisor] + needs: [build-gateway, build-supervisor, build-cli] permissions: actions: read contents: read @@ -79,6 +87,10 @@ jobs: with: image-tag: ${{ github.sha }} runner: linux-arm64-cpu8 + cli-artifact-prefix: rust-binary-cli + gateway-artifact-prefix: rust-binary-gateway + # The supervisor build uploads the openshell-sandbox binary artifact. + sandbox-artifact-prefix: rust-binary-supervisor tag-ghcr-dev: name: Tag GHCR Images as Dev diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 74be300b0c..0b3a9bc625 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -93,8 +93,18 @@ jobs: checkout-ref: ${{ inputs.tag || github.ref }} auditable: true + build-cli: + needs: [compute-versions] + uses: ./.github/workflows/docker-build.yml + with: + component: cli + cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} + image-tag: ${{ needs.compute-versions.outputs.source_sha }} + checkout-ref: ${{ inputs.tag || github.ref }} + auditable: true + e2e: - needs: [compute-versions, build-gateway, build-supervisor] + needs: [compute-versions, build-gateway, build-supervisor, build-cli] permissions: actions: read contents: read @@ -104,6 +114,10 @@ jobs: image-tag: ${{ needs.compute-versions.outputs.source_sha }} checkout-ref: ${{ inputs.tag || github.ref }} runner: linux-arm64-cpu8 + cli-artifact-prefix: rust-binary-cli + gateway-artifact-prefix: rust-binary-gateway + # The supervisor build uploads the openshell-sandbox binary artifact. + sandbox-artifact-prefix: rust-binary-supervisor tag-ghcr-release: name: Tag GHCR Images for Release diff --git a/TESTING.md b/TESTING.md index 6c0829060d..3643ca7d22 100644 --- a/TESTING.md +++ b/TESTING.md @@ -175,6 +175,18 @@ Run the Podman-backed Rust CLI e2e suite: mise run e2e:podman ``` +Run the rootless Podman suite in an Ubuntu 26.04 Nix test guest: + +```shell +mise run e2e:podman:rootless +``` + +Run the focused rootful Podman suite in a Fedora Nix test guest: + +```shell +mise run e2e:podman:fedora-rootful +``` + Run the VM-backed Rust CLI e2e suite: ```shell diff --git a/e2e/configs/gateway/podman-rootful.toml b/e2e/configs/gateway/podman-rootful.toml new file mode 100644 index 0000000000..57bf093ed0 --- /dev/null +++ b/e2e/configs/gateway/podman-rootful.toml @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +[openshell] +version = 1 + +[openshell.gateway] +bind_address = "127.0.0.1:8080" +log_level = "info" +compute_drivers = ["podman"] +disable_tls = true + +[openshell.gateway.auth] +allow_unauthenticated_users = true + +[openshell.gateway.gateway_jwt] +signing_key_path = ".cache/openshell-e2e/gateway-jwt/signing.pem" +public_key_path = ".cache/openshell-e2e/gateway-jwt/public.pem" +kid_path = ".cache/openshell-e2e/gateway-jwt/kid" +gateway_id = "openshell-e2e" +ttl_secs = 0 + +[openshell.drivers.podman] +socket_path = "/run/podman/podman.sock" +default_image = "ghcr.io/nvidia/openshell-community/sandboxes/base:latest" +image_pull_policy = "missing" +network_name = "openshell-e2e" +grpc_endpoint = "http://host.containers.internal:8080" +supervisor_image = "localhost/openshell/supervisor:e2e-vm" diff --git a/e2e/run.sh b/e2e/run.sh index 0505730f05..d3abdd39a4 100755 --- a/e2e/run.sh +++ b/e2e/run.sh @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 # Build the current checkout, run its gateway on the host or in a disposable -# Nix test guest, and execute one named host-side E2E suite against that gateway. +# Nix test guest, and execute host-side E2E tests against that gateway. set -Eeuo pipefail @@ -20,13 +20,19 @@ usage() { cat <<'EOF' Usage: e2e/run.sh [--vm DISTRO] [--with CONFIG ...] \ - --gateway-config PATH --suite NAME + --gateway-config PATH [--features FEATURES] [--suite NAME] Options: --vm DISTRO Run the gateway in a Nix test guest --with CONFIG Apply a Nix test-guest configuration; repeatable + --host-cli-bin PATH Use a prebuilt host openshell CLI instead of building it + --gateway-bin PATH Use a prebuilt openshell-gateway instead of building it + --sandbox-bin PATH Use a prebuilt openshell-sandbox instead of building it + --guest-gateway-user USER + Run the VM guest gateway as USER (openshell or root) --gateway-config PATH Fully resolved gateway TOML + --features FEATURES Rust e2e feature set to enable (default: e2e) --suite NAME Rust suite at e2e/rust/tests/NAME.rs -h, --help Show this help @@ -92,6 +98,11 @@ catalog_has_entry() { vm= gateway_config= +gateway_bin= +host_cli_bin= +sandbox_bin= +guest_gateway_user=openshell +e2e_features=e2e suite_name= with_configurations=() @@ -107,11 +118,36 @@ while [ "$#" -gt 0 ]; do with_configurations+=("$2") shift 2 ;; + --host-cli-bin) + require_value "$1" "$#" "${2:-}" + host_cli_bin="$(resolve_file "$2")" || die "--host-cli-bin does not name a file: $2" + shift 2 + ;; + --gateway-bin) + require_value "$1" "$#" "${2:-}" + gateway_bin="$(resolve_file "$2")" || die "--gateway-bin does not name a file: $2" + shift 2 + ;; + --sandbox-bin) + require_value "$1" "$#" "${2:-}" + sandbox_bin="$(resolve_file "$2")" || die "--sandbox-bin does not name a file: $2" + shift 2 + ;; + --guest-gateway-user) + require_value "$1" "$#" "${2:-}" + guest_gateway_user=$2 + shift 2 + ;; --gateway-config) require_value "$1" "$#" "${2:-}" gateway_config=$2 shift 2 ;; + --features) + require_value "$1" "$#" "${2:-}" + e2e_features=$2 + shift 2 + ;; --suite) require_value "$1" "$#" "${2:-}" suite_name=$2 @@ -130,9 +166,6 @@ done if [ -z "${gateway_config}" ]; then die "--gateway-config is required" fi -if [ -z "${suite_name}" ]; then - die "--suite is required" -fi if ! command -v python3 >/dev/null 2>&1; then die "python3 is required" fi @@ -144,12 +177,21 @@ gateway_driver="$(python3 -c ' import sys, tomllib print(tomllib.load(open(sys.argv[1], "rb"))["openshell"]["gateway"]["compute_drivers"][0]) ' "${gateway_config}")" -if [[ ! ${suite_name} =~ ^[a-z0-9][a-z0-9-]*$ ]]; then - die "suite name must contain only lowercase letters, digits, and hyphens: ${suite_name}" +if [ -z "${e2e_features}" ]; then + die "--features must not be empty" fi -suite_path="${ROOT}/e2e/rust/tests/${suite_name}.rs" -if [ ! -f "${suite_path}" ]; then - die "unknown suite: ${suite_name}" +case "${guest_gateway_user}" in +openshell | root) ;; +*) die "--guest-gateway-user must be 'openshell' or 'root'" ;; +esac +if [ -n "${suite_name}" ]; then + if [[ ! ${suite_name} =~ ^[a-z0-9][a-z0-9_-]*$ ]]; then + die "suite name must contain only lowercase letters, digits, underscores, and hyphens: ${suite_name}" + fi + suite_path="${ROOT}/e2e/rust/tests/${suite_name}.rs" + if [ ! -f "${suite_path}" ]; then + die "unknown suite: ${suite_name}" + fi fi mode=host if [ -n "${vm}" ] || [ "${#with_configurations[@]}" -gt 0 ]; then @@ -158,7 +200,7 @@ if [ -n "${vm}" ] || [ "${#with_configurations[@]}" -gt 0 ]; then if [ "${gateway_driver}" = podman ]; then vm=fedora else - vm=ubuntu + vm=ubuntu-24-04 fi fi fi @@ -171,8 +213,8 @@ if [ "${mode}" = vm ]; then die "invalid VM configuration name: ${configuration}" fi done - if [ "${gateway_driver}" = podman ] && [ "${vm}" = ubuntu ]; then - die "the Ubuntu 24.04 guest lacks the Podman 5 pasta helper required for sandbox callbacks; use --vm fedora --with podman" + if [ "${gateway_driver}" = podman ] && [ "${vm}" = ubuntu-24-04 ]; then + die "the Ubuntu 24.04 guest lacks the Podman 5 pasta helper required for sandbox callbacks; use --vm ubuntu-26-04 --with podman" fi if ! command -v nix >/dev/null 2>&1; then die "Nix is required for VM mode" @@ -230,49 +272,68 @@ target_dir="$(e2e_cargo_target_dir "${ROOT}" mise x -- cargo)" ensure_build_nofile_limit -echo "==> Building native host openshell CLI" -mise x -- cargo build "${cargo_jobs[@]}" -p openshell-cli --bin openshell -host_cli_bin="${target_dir}/debug/openshell" - -echo "==> Preparing ${linux_musl_target} build target" -mise x -- rustup target add "${linux_musl_target}" >/dev/null +if [ -n "${host_cli_bin}" ]; then + echo "==> Using host openshell CLI: ${host_cli_bin}" +else + echo "==> Building native host openshell CLI" + mise x -- cargo build "${cargo_jobs[@]+"${cargo_jobs[@]}"}" -p openshell-cli --bin openshell + host_cli_bin="${target_dir}/debug/openshell" +fi -echo "==> Building Linux openshell-sandbox (${linux_musl_target})" -mise x -- cargo zigbuild "${cargo_jobs[@]}" \ - --release \ - --target "${linux_musl_target}" \ - -p openshell-sandbox \ - --bin openshell-sandbox -linux_sandbox_bin="${target_dir}/${linux_musl_target}/release/openshell-sandbox" +if [ -n "${sandbox_bin}" ]; then + echo "==> Using Linux openshell-sandbox: ${sandbox_bin}" + linux_sandbox_bin="${sandbox_bin}" +else + echo "==> Preparing ${linux_musl_target} build target" + mise x -- rustup target add "${linux_musl_target}" >/dev/null + + echo "==> Building Linux openshell-sandbox (${linux_musl_target})" + mise x -- cargo zigbuild "${cargo_jobs[@]+"${cargo_jobs[@]}"}" \ + --release \ + --target "${linux_musl_target}" \ + -p openshell-sandbox \ + --bin openshell-sandbox + linux_sandbox_bin="${target_dir}/${linux_musl_target}/release/openshell-sandbox" +fi host_gateway_bin= guest_gateway_bin= if [ "${mode}" = host ]; then - echo "==> Building native host openshell-gateway" - mise x -- cargo build "${cargo_jobs[@]}" \ - -p openshell-server \ - --bin openshell-gateway \ - --features bundled-z3 - host_gateway_bin="${target_dir}/debug/openshell-gateway" -else - echo "==> Preparing ${linux_gateway_rust_target} build target" - mise x -- rustup target add "${linux_gateway_rust_target}" >/dev/null - echo "==> Building Linux openshell-gateway (${linux_gateway_zig_target})" - ( - eval "$( - "${ROOT}/tasks/scripts/setup-zig-cc-wrapper.sh" \ - "${linux_gateway_zig_target}" \ - "${linux_gateway_zig_target}" \ - "${target_dir}/zig-gnu-wrapper/e2e" - )" - mise x -- cargo zigbuild "${cargo_jobs[@]}" \ - --release \ - --target "${linux_gateway_zig_target}" \ + if [ -n "${gateway_bin}" ]; then + echo "==> Using host openshell-gateway: ${gateway_bin}" + host_gateway_bin="${gateway_bin}" + else + echo "==> Building native host openshell-gateway" + mise x -- cargo build "${cargo_jobs[@]+"${cargo_jobs[@]}"}" \ -p openshell-server \ --bin openshell-gateway \ --features bundled-z3 - ) - guest_gateway_bin="${target_dir}/${linux_gateway_rust_target}/release/openshell-gateway" + host_gateway_bin="${target_dir}/debug/openshell-gateway" + fi +else + if [ -n "${gateway_bin}" ]; then + echo "==> Using Linux openshell-gateway: ${gateway_bin}" + guest_gateway_bin="${gateway_bin}" + else + echo "==> Preparing ${linux_gateway_rust_target} build target" + mise x -- rustup target add "${linux_gateway_rust_target}" >/dev/null + echo "==> Building Linux openshell-gateway (${linux_gateway_zig_target})" + ( + eval "$( + "${ROOT}/tasks/scripts/setup-zig-cc-wrapper.sh" \ + "${linux_gateway_zig_target}" \ + "${linux_gateway_zig_target}" \ + "${target_dir}/zig-gnu-wrapper/e2e" + )" + mise x -- cargo zigbuild "${cargo_jobs[@]+"${cargo_jobs[@]}"}" \ + --release \ + --target "${linux_gateway_zig_target}" \ + -p openshell-server \ + --bin openshell-gateway \ + --features bundled-z3 + ) + guest_gateway_bin="${target_dir}/${linux_gateway_rust_target}/release/openshell-gateway" + fi fi expected_binaries=("${host_cli_bin}" "${linux_sandbox_bin}") @@ -391,6 +452,7 @@ gateway_endpoint="http://127.0.0.1:${host_port}" export OPENSHELL_GATEWAY_ENDPOINT="${gateway_endpoint}" export OPENSHELL_GATEWAY="${gateway_name}" export OPENSHELL_BIN="${host_cli_bin}" +export OPENSHELL_E2E_DRIVER="${gateway_driver}" if [ "${mode}" = host ]; then case "${gateway_driver}" in @@ -467,19 +529,37 @@ docker) "${supervisor_image}" >/dev/null ;; podman) - podman --url "unix:///run/user/\$(id -u)/podman/podman.sock" import \ - --change 'ENTRYPOINT ["/openshell-sandbox"]' \ - "${guest_supervisor_archive_path}" \ - "${supervisor_image}" >/dev/null + if [ '${guest_gateway_user}' = root ]; then + sudo podman --url "unix:///run/podman/podman.sock" import \ + --change 'ENTRYPOINT ["/openshell-sandbox"]' \ + "${guest_supervisor_archive_path}" \ + "${supervisor_image}" >/dev/null + else + podman --url "unix:///run/user/\$(id -u)/podman/podman.sock" import \ + --change 'ENTRYPOINT ["/openshell-sandbox"]' \ + "${guest_supervisor_archive_path}" \ + "${supervisor_image}" >/dev/null + fi ;; esac report_timing "${gateway_driver} supervisor import" "\${phase_started_at}" cd /home/openshell -exec /usr/local/bin/openshell-gateway \ - --config "\${config_path}" \ - --bind-address 127.0.0.1 \ - --port ${guest_port} \ +gateway_args=( + /usr/local/bin/openshell-gateway + --config "\${config_path}" + --bind-address 127.0.0.1 + --port ${guest_port} --disable-tls +) +if [ '${guest_gateway_user}' = root ]; then + exec sudo env \ + XDG_CONFIG_HOME="\${XDG_CONFIG_HOME}" \ + XDG_CACHE_HOME="\${XDG_CACHE_HOME}" \ + XDG_DATA_HOME="\${XDG_DATA_HOME}" \ + XDG_STATE_HOME="\${XDG_STATE_HOME}" \ + "\${gateway_args[@]}" +fi +exec "\${gateway_args[@]}" EOF chmod 0700 "${guest_launcher}" @@ -588,10 +668,17 @@ wait_for_gateway() { wait_for_gateway -echo "==> Running E2E suite: ${suite_name}" +test_args=( + cargo test + --manifest-path e2e/rust/Cargo.toml + --features "${e2e_features}" +) +echo "==> Running E2E features: ${e2e_features}" +if [ -n "${suite_name}" ]; then + echo "==> Running E2E suite: ${suite_name}" + test_args+=(--test "${suite_name}") +fi +test_args+=(-- --nocapture) + cd "${ROOT}" -cargo test \ - --manifest-path e2e/rust/Cargo.toml \ - --features e2e \ - --test "${suite_name}" \ - -- --nocapture +"${test_args[@]}" diff --git a/e2e/support/capbset-probe.c b/e2e/support/capbset-probe.c deleted file mode 100644 index bc93d766db..0000000000 --- a/e2e/support/capbset-probe.c +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Verify the capability-bounding-set condition behind issue #2069 from -// inside a rootless Podman container. - -#include -#include -#include -#include -#include -#include - -static unsigned long long status_capability(const char *field) { - FILE *status = fopen("/proc/self/status", "r"); - if (status == NULL) { - perror("fopen(/proc/self/status)"); - exit(EXIT_FAILURE); - } - - char line[256]; - unsigned long long value = 0; - int found = 0; - while (fgets(line, sizeof(line), status) != NULL) { - char name[32]; - unsigned long long candidate; - if (sscanf(line, "%31[^:]:%llx", name, &candidate) == 2 && - strcmp(name, field) == 0) { - value = candidate; - found = 1; - break; - } - } - fclose(status); - - if (!found) { - fprintf(stderr, "missing %s in /proc/self/status\n", field); - exit(EXIT_FAILURE); - } - return value; -} - -static void print_apparmor_profile(void) { - FILE *profile = fopen("/proc/self/attr/current", "r"); - if (profile == NULL) { - perror("fopen(/proc/self/attr/current)"); - return; - } - - char line[256]; - if (fgets(line, sizeof(line), profile) != NULL) { - printf("apparmor_profile=%s", line); - if (strchr(line, '\n') == NULL) { - putchar('\n'); - } - } - fclose(profile); -} - -int main(int argc, char **argv) { - if (argc != 1) { - fprintf(stderr, "usage: %s\n", argv[0]); - return EXIT_FAILURE; - } - - const unsigned long long setpcap_mask = 1ULL << CAP_SETPCAP; - const unsigned long long cap_bnd_before = status_capability("CapBnd"); - const unsigned long long cap_eff_before = status_capability("CapEff"); - const int setpcap_before = prctl(PR_CAPBSET_READ, CAP_SETPCAP, 0, 0, 0); - if (setpcap_before == -1) { - perror("prctl(PR_CAPBSET_READ) before drop"); - return EXIT_FAILURE; - } - - print_apparmor_profile(); - printf("cap_bnd_before=%016llx\n", cap_bnd_before); - printf("cap_eff_before=%016llx\n", cap_eff_before); - printf("setpcap_bounding_before=%d\n", setpcap_before); - - if (cap_bnd_before == 0 || (cap_bnd_before & setpcap_mask) == 0 || - (cap_eff_before & setpcap_mask) == 0 || setpcap_before != 1) { - fprintf(stderr, "CAP_SETPCAP must be effective and present in a non-empty bounding set\n"); - return EXIT_FAILURE; - } - - errno = 0; - const int drop_result = prctl(PR_CAPBSET_DROP, CAP_SETPCAP, 0, 0, 0); - const int drop_errno = errno; - const unsigned long long cap_bnd_after = status_capability("CapBnd"); - const int setpcap_after = prctl(PR_CAPBSET_READ, CAP_SETPCAP, 0, 0, 0); - - printf("drop_result=%d\n", drop_result); - printf("drop_errno=%d (%s)\n", drop_errno, strerror(drop_errno)); - printf("cap_bnd_after=%016llx\n", cap_bnd_after); - printf("setpcap_bounding_after=%d\n", setpcap_after); - - if (drop_result == 0) { - if (setpcap_after != 0 || (cap_bnd_after & setpcap_mask) != 0) { - fprintf(stderr, "CAP_SETPCAP remained in the bounding set after a successful drop\n"); - return EXIT_FAILURE; - } - } else if (drop_errno == EPERM) { - if (setpcap_after != 1 || (cap_bnd_after & setpcap_mask) == 0) { - fprintf(stderr, "CAP_SETPCAP changed in the bounding set after EPERM\n"); - return EXIT_FAILURE; - } - } else { - fprintf(stderr, "unexpected PR_CAPBSET_DROP result\n"); - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/flake.nix b/flake.nix index 8e9fb728b1..47513fb6c1 100644 --- a/flake.nix +++ b/flake.nix @@ -56,6 +56,13 @@ lcov uv ]; + darwinDevShellPackages = pkgs.lib.optionals pkgs.stdenv.hostPlatform.isDarwin ( + with pkgs; + [ + cargo-zigbuild + zig + ] + ); treefmtEval = treefmt-nix.lib.evalModule pkgs { projectRootFile = "flake.nix"; programs.nixfmt.enable = true; @@ -88,7 +95,8 @@ z3-static aws-lc-static ] - ++ commonDevShellPackages; + ++ commonDevShellPackages + ++ darwinDevShellPackages; }; } // pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux { diff --git a/nix/test-guest/README.md b/nix/test-guest/README.md index ca62f893e6..3e55d732c0 100644 --- a/nix/test-guest/README.md +++ b/nix/test-guest/README.md @@ -33,12 +33,15 @@ nix/test-guest/ ├── cache-lib.sh ├── cache-seal.sh ├── distros/ -│ ├── ubuntu.nix +│ ├── ubuntu-24-04.nix +│ ├── ubuntu-26-04.nix │ ├── centos.nix │ ├── fedora.nix │ └── rocky.nix └── configuration/ ├── docker.yml + ├── podman-rootful.yml + ├── podman-rootless.yml ├── podman.yml └── selinux.yml ``` @@ -56,17 +59,18 @@ The root [`flake.nix`](../../flake.nix) exposes this directory as the `test-gues ## Supported configurations -| Distro | Docker | Podman | SELinux | Package format | -| --- | --- | --- | --- | --- | -| Ubuntu 24.04 | Yes | Yes | No | `.deb` | -| CentOS Stream 10 | No | Yes | Yes | `.rpm` | -| Fedora 44 | No | Yes | Yes | `.rpm` | -| Rocky Linux 9 | Yes | Yes | Yes | `.rpm` | +| Distro | Docker | Podman | Rootful Podman | Rootless Podman | SELinux | Package format | +| --- | --- | --- | --- | --- | --- | --- | +| Ubuntu 24.04 | Yes | Yes | No | No | No | `.deb` | +| Ubuntu 26.04 | Yes | Yes | No | Yes | No | `.deb` | +| CentOS Stream 10 | No | Yes | Yes | No | Yes | `.rpm` | +| Fedora 44 | No | Yes | Yes | No | Yes | `.rpm` | +| Rocky Linux 9 | Yes | Yes | Yes | No | Yes | `.rpm` | The Ubuntu 24.04 Podman configuration is available for runtime and packaging checks, but its Podman 4 release does not provide the `pasta` rootless network -helper required by OpenShell sandbox callbacks. OpenShell Podman E2E runs use -the Fedora guest, which provides Podman 5 and `pasta`. +helper required by OpenShell sandbox callbacks. Rootless Podman E2E uses the +Ubuntu 26.04 guest with `--with podman-rootless`. List the available distros and configurations: @@ -79,13 +83,13 @@ nix run .#test-guest -- --list Boot a base Ubuntu VM: ```shell -nix run .#test-guest -- --distro ubuntu +nix run .#test-guest -- --distro ubuntu-24-04 ``` Apply the Docker configuration before opening the SSH session: ```shell -nix run .#test-guest -- --distro ubuntu --with docker +nix run .#test-guest -- --distro ubuntu-24-04 --with docker ``` Other combinations use the same interface: @@ -94,13 +98,15 @@ Other combinations use the same interface: nix run .#test-guest -- --distro rocky --with docker nix run .#test-guest -- --distro centos --with podman nix run .#test-guest -- --distro fedora --with podman +nix run .#test-guest -- --distro fedora --with podman-rootful +nix run .#test-guest -- --distro ubuntu-26-04 --with podman-rootless ``` Configurations are repeatable: ```shell nix run .#test-guest -- \ - --distro ubuntu \ + --distro ubuntu-24-04 \ --with docker \ --with podman ``` @@ -131,7 +137,7 @@ The `test-guest-cache` app ensures a prepared disk exists for one exact distro, ```shell nix run .#test-guest-cache -- \ - --distro ubuntu \ + --distro ubuntu-24-04 \ --with docker ``` @@ -140,7 +146,7 @@ backing cache: ```shell nix run .#test-guest-cache -- \ - --distro ubuntu \ + --distro ubuntu-24-04 \ --with docker \ --repository ghcr.io/nvidia/openshell/test-guest-cache \ --digest sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef @@ -150,7 +156,7 @@ The command never publishes implicitly. Add `--push` after authenticating ORAS t ```shell nix run .#test-guest-cache -- \ - --distro ubuntu \ + --distro ubuntu-24-04 \ --with docker \ --repository ghcr.io/nvidia/openshell/test-guest-cache \ --push @@ -177,8 +183,8 @@ The default cache directory is `${XDG_CACHE_HOME:-$HOME/.cache}/openshell/test-g Cache command options: ```text ---distro NAME Base distro: ubuntu, centos, fedora, or rocky ---with NAME Apply docker, podman, or selinux; repeatable +--distro NAME Base distro: ubuntu-24-04, ubuntu-26-04, centos, fedora, or rocky +--with NAME Apply docker, podman, podman-rootful, podman-rootless, or selinux; repeatable --repository REF OCI repository without a tag --digest DIGEST Trusted OCI manifest digest required for pulls --cache-dir PATH Override the local prepared-disk cache directory @@ -202,7 +208,7 @@ Install the package in an Ubuntu VM and run a command: ```shell nix run .#test-guest -- \ - --distro ubuntu \ + --distro ubuntu-24-04 \ --with docker \ --install artifacts/openshell_0.0.0-local_arm64.deb \ -- openshell --version @@ -218,7 +224,7 @@ Use `--copy SOURCE:DEST` to install an executable without creating a package: ```shell nix run .#test-guest -- \ - --distro ubuntu \ + --distro ubuntu-24-04 \ --copy ./openshell:/usr/local/bin/openshell \ -- openshell --version ``` @@ -228,8 +234,8 @@ The destination must be an absolute guest path. Copied files are installed with ## Runner options ```text ---distro NAME Base distro: ubuntu, centos, fedora, or rocky ---with NAME Apply docker, podman, or selinux; repeatable +--distro NAME Base distro: ubuntu-24-04, ubuntu-26-04, centos, fedora, or rocky +--with NAME Apply docker, podman, podman-rootful, podman-rootless, or selinux; repeatable --install PATH Install a .deb or .rpm package; repeatable --copy SRC:DEST Copy an executable into the guest; repeatable --ssh-port PORT Use a specific loopback SSH forwarding port diff --git a/nix/test-guest/cache.sh b/nix/test-guest/cache.sh index 1103b3c289..f229143e47 100644 --- a/nix/test-guest/cache.sh +++ b/nix/test-guest/cache.sh @@ -12,8 +12,8 @@ Usage: nix run .#test-guest-cache -- --distro DISTRO [OPTIONS] Options: - --distro NAME Base distro: ubuntu, centos, fedora, or rocky - --with NAME Apply a configuration; repeatable (docker, podman, selinux) + --distro NAME Base distro: ubuntu-24-04, ubuntu-26-04, centos, fedora, or rocky + --with NAME Apply a configuration; repeatable (docker, podman, podman-rootful, podman-rootless, selinux) --repository REF OCI repository without a tag --digest DIGEST Trusted OCI manifest digest required for pulls --cache-dir PATH Override the local prepared-disk cache directory diff --git a/nix/test-guest/configuration/podman-rootful.yml b/nix/test-guest/configuration/podman-rootful.yml new file mode 100644 index 0000000000..9947635f1a --- /dev/null +++ b/nix/test-guest/configuration/podman-rootful.yml @@ -0,0 +1,52 @@ +--- +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# PROTOTYPE: Configure rootful Podman in a disposable test guest. + +- name: Configure rootful Podman + hosts: test_vm + become: true + gather_facts: true + + tasks: + - name: Validate rootful Podman support + ansible.builtin.assert: + that: + - ansible_facts.distribution in ["CentOS", "Fedora", "Rocky"] + fail_msg: >- + Rootful Podman guest coverage is supported on RPM-family guests, not + {{ ansible_facts.distribution }}. + + - name: Install rootful Podman + ansible.builtin.package: + name: podman + state: present + + - name: Enable the rootful Podman API socket + ansible.builtin.systemd_service: + name: podman.socket + enabled: true + state: started + + - name: Verify rootful Podman + ansible.builtin.command: + argv: + - podman + - --url + - unix:///run/podman/podman.sock + - info + changed_when: false + + - name: Verify rootful Podman mode + ansible.builtin.command: + argv: + - podman + - --url + - unix:///run/podman/podman.sock + - info + - --format + - "{% raw %}{{.Host.Security.Rootless}}{% endraw %}" + changed_when: false + register: podman_rootless + failed_when: podman_rootless.stdout != "false" diff --git a/nix/test-guest/configuration/podman-rootless.yml b/nix/test-guest/configuration/podman-rootless.yml new file mode 100644 index 0000000000..defff095e8 --- /dev/null +++ b/nix/test-guest/configuration/podman-rootless.yml @@ -0,0 +1,71 @@ +--- +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# PROTOTYPE: Configure rootless Podman in a disposable test guest. + +- name: Configure rootless Podman + hosts: test_vm + become: true + gather_facts: true + + tasks: + - name: Validate rootless Podman support + ansible.builtin.assert: + that: + - ansible_facts.distribution == "Ubuntu" + - ansible_facts.distribution_version is version("26.04", ">=") + fail_msg: >- + Rootless Podman requires Ubuntu 26.04 or newer, not + {{ ansible_facts.distribution }} {{ ansible_facts.distribution_version }}. + + - name: Refresh Ubuntu package metadata + ansible.builtin.apt: + update_cache: true + + - name: Install Ubuntu rootless Podman + ansible.builtin.apt: + name: + - fuse-overlayfs + - passt + - podman + - uidmap + state: present + + - name: Enable the rootless Podman API socket + ansible.builtin.systemd_service: + name: podman.socket + scope: user + enabled: true + state: started + become: false + + - name: Verify rootless Podman + ansible.builtin.command: + cmd: podman info + become: false + changed_when: false + + - name: Verify rootless Podman mode + ansible.builtin.command: + argv: + - podman + - info + - --format + - "{% raw %}{{.Host.Security.Rootless}}{% endraw %}" + become: false + changed_when: false + register: podman_rootless + failed_when: podman_rootless.stdout != "true" + + - name: Verify rootless Podman uses pasta + ansible.builtin.command: + argv: + - podman + - info + - --format + - "{% raw %}{{.Host.RootlessNetworkCmd}}{% endraw %}" + become: false + changed_when: false + register: podman_rootless_network + failed_when: podman_rootless_network.stdout != "pasta" diff --git a/nix/test-guest/default.nix b/nix/test-guest/default.nix index 72937cfec2..3df811b2a0 100644 --- a/nix/test-guest/default.nix +++ b/nix/test-guest/default.nix @@ -18,7 +18,8 @@ let if isAarch64 then "${qemu}/bin/qemu-system-aarch64" else "${qemu}/bin/qemu-system-x86_64"; distros = { - ubuntu = import ./distros/ubuntu.nix { inherit pkgs architecture; }; + ubuntu-24-04 = import ./distros/ubuntu-24-04.nix { inherit pkgs architecture; }; + ubuntu-26-04 = import ./distros/ubuntu-26-04.nix { inherit pkgs architecture; }; centos = import ./distros/centos.nix { inherit pkgs architecture; }; fedora = import ./distros/fedora.nix { inherit pkgs architecture; }; rocky = import ./distros/rocky.nix { inherit pkgs architecture; }; @@ -27,6 +28,8 @@ let configurations = { docker = ./configuration/docker.yml; podman = ./configuration/podman.yml; + podman-rootful = ./configuration/podman-rootful.yml; + podman-rootless = ./configuration/podman-rootless.yml; selinux = ./configuration/selinux.yml; }; diff --git a/nix/test-guest/distros/ubuntu.nix b/nix/test-guest/distros/ubuntu-24-04.nix similarity index 100% rename from nix/test-guest/distros/ubuntu.nix rename to nix/test-guest/distros/ubuntu-24-04.nix diff --git a/nix/test-guest/distros/ubuntu-26-04.nix b/nix/test-guest/distros/ubuntu-26-04.nix new file mode 100644 index 0000000000..9a9023ce8e --- /dev/null +++ b/nix/test-guest/distros/ubuntu-26-04.nix @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +{ pkgs, architecture }: + +let + imageArchitecture = if architecture == "aarch64" then "arm64" else "amd64"; + imageUrl = "https://cloud-images.ubuntu.com/releases/releases/26.04/release/ubuntu-26.04-server-cloudimg-${imageArchitecture}.img"; + imageHash = + if architecture == "aarch64" then + "sha256-PhE/3UHznhNyk3UXO7KueT+H3G20KU5SUf8kdpcXiLo=" + else + "sha256-ncfFNjwBRqCLoMmqg02CwsbfuxxHGtmi8KuhGJ4hvgU="; +in +{ + osId = "ubuntu"; + osVersion = "26.04"; + packageFamily = "deb"; + inherit imageUrl imageHash; + image = pkgs.fetchurl { + name = "ubuntu-26.04-server-cloudimg-${imageArchitecture}.img"; + url = imageUrl; + hash = imageHash; + }; +} diff --git a/nix/test-guest/run.sh b/nix/test-guest/run.sh index c999679492..65d27c6083 100644 --- a/nix/test-guest/run.sh +++ b/nix/test-guest/run.sh @@ -12,8 +12,8 @@ Usage: nix run .#test-guest -- --distro DISTRO [OPTIONS] [-- COMMAND...] Options: - --distro NAME Base distro: ubuntu, centos, fedora, or rocky - --with NAME Apply a configuration; repeatable (docker, podman, selinux) + --distro NAME Base distro: ubuntu-24-04, ubuntu-26-04, centos, fedora, or rocky + --with NAME Apply a configuration; repeatable (docker, podman, podman-rootful, podman-rootless, selinux) --install PATH Install a .deb or .rpm package; repeatable --copy SRC:DEST Copy an executable to an absolute guest path; repeatable --ssh-port PORT Use a specific loopback SSH forwarding port diff --git a/tasks/test.toml b/tasks/test.toml index 29e06b826a..e71615e2bf 100644 --- a/tasks/test.toml +++ b/tasks/test.toml @@ -136,6 +136,14 @@ run = [ "CONTAINER_RUNTIME=docker e2e/with-keycloak.sh env OPENSHELL_E2E_OIDC_GATEWAY=1 e2e/with-docker-gateway.sh uv run pytest -m 'not gpu' e2e/python/oidc", ] +["e2e:podman:rootless"] +description = "Run Podman e2e against a rootless Ubuntu 26.04 Nix test guest" +run = "e2e/run.sh --vm ubuntu-26-04 --with podman-rootless --gateway-config e2e/configs/gateway/podman.toml --features e2e-podman" + +["e2e:podman:fedora-rootful"] +description = "Run focused Podman e2e against a rootful Fedora Nix test guest" +run = "e2e/run.sh --vm fedora --with podman-rootful --guest-gateway-user root --gateway-config e2e/configs/gateway/podman-rootful.toml --features e2e-podman --suite sandbox_lifecycle" + ["e2e:podman:gpu"] description = "Run GPU e2e against a standalone gateway with the Podman compute driver" env = { OPENSHELL_E2E_PODMAN_GPU = "1", OPENSHELL_E2E_PODMAN_TEST = "gpu", OPENSHELL_E2E_PODMAN_FEATURES = "e2e-podman-gpu" }