From 516adcdedb62bca666ecd607f65191683841d3b9 Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 09:39:10 +0200 Subject: [PATCH 01/17] ci: build release binaries with Nix Refs #1683 Signed-off-by: Simon Scatton --- .github/workflows/build-binaries.yml | 141 +++++++++++++++++++++++++++ flake.nix | 3 + 2 files changed, 144 insertions(+) create mode 100644 .github/workflows/build-binaries.yml diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml new file mode 100644 index 0000000000..71bb0e03a8 --- /dev/null +++ b/.github/workflows/build-binaries.yml @@ -0,0 +1,141 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Build Binaries + +on: + workflow_call: + inputs: + checkout-ref: + required: false + type: string + default: "" + secrets: + CACHIX_AUTH_TOKEN: + required: true + workflow_dispatch: + +permissions: + contents: read + +jobs: + version: + name: Version + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + cargo: ${{ steps.version.outputs.cargo }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs['checkout-ref'] || github.sha }} + fetch-depth: 0 + + - name: Compute version + id: version + shell: bash + run: | + cargo="$(python3 tasks/scripts/release.py get-version --cargo)" + echo "cargo=$cargo" >> "$GITHUB_OUTPUT" + + binary: + name: ${{ matrix.binary }} (${{ matrix.triple }}) + needs: version + strategy: + matrix: + include: + - binary: openshell + triple: x86_64-unknown-linux-musl + runner: linux-amd64-cpu8 + dev_shell: .#devShells.x86_64-linux.musl + - binary: openshell + triple: aarch64-unknown-linux-musl + runner: linux-arm64-cpu8 + dev_shell: .#devShells.aarch64-linux.musl + - binary: openshell + triple: aarch64-apple-darwin + runner: macos-15-xlarge + dev_shell: .#devShells.aarch64-darwin.default + - binary: openshell-gateway + triple: x86_64-unknown-linux-gnu + runner: linux-amd64-cpu8 + dev_shell: .#devShells.x86_64-linux.glibc-2-28 + interpreter: /lib64/ld-linux-x86-64.so.2 + - binary: openshell-gateway + triple: aarch64-unknown-linux-gnu + runner: linux-arm64-cpu8 + dev_shell: .#devShells.aarch64-linux.glibc-2-28 + interpreter: /lib/ld-linux-aarch64.so.1 + - binary: openshell-gateway + triple: aarch64-apple-darwin + runner: macos-15-xlarge + dev_shell: .#devShells.aarch64-darwin.default + runs-on: ${{ matrix.runner }} + timeout-minutes: 60 + defaults: + run: + shell: nix develop ${{ matrix.dev_shell }} -c bash -euo pipefail {0} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs['checkout-ref'] || github.sha }} + + - 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 + authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} + + - name: Cache Rust artifacts + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + with: + shared-key: binaries-${{ matrix.binary }}-${{ matrix.triple }} + cache-on-failure: "true" + cache-workspace-crates: "true" + cache-bin: "false" + cmd-format: nix develop ${{ matrix.dev_shell }} -c {0} + + - name: Set version + run: sed -i 's/^version = "0\.0\.0"$/version = "${{ needs.version.outputs.cargo }}"/' Cargo.toml + + - name: Build ${{ matrix.binary }} + run: GIT_DIR=/nonexistent cargo auditable build --release --bin "${{ matrix.binary }}" + + - name: Verify ${{ matrix.binary }} + run: | + # Confirm the binary runs and reports the expected name. + target/release/${{ matrix.binary }} --version | grep -q '^${{ matrix.binary }} ' + # Confirm Syft can decode the embedded cargo-auditable metadata. + SYFT_CHECK_FOR_APP_UPDATE=false syft "file:target/release/${{ matrix.binary }}" -o cyclonedx-json | grep 'pkg:cargo/' > /dev/null + + - name: Verify static linkage + if: endsWith(matrix.triple, '-linux-musl') + run: tasks/scripts/verify-static-binary.sh target/release/${{ matrix.binary }} + + - name: Normalize Linux dynamic binary + if: endsWith(matrix.triple, '-linux-gnu') + run: | + binary=target/release/${{ matrix.binary }} + # Remove Nix store paths so the binary can run on other distributions. + patchelf --set-interpreter "${{ matrix.interpreter }}" --remove-rpath "$binary" + # Z3 must be embedded instead of loaded from the target system. + test -z "$(patchelf --print-needed "$binary" | grep '^libz3')" + # Reject symbols introduced after glibc 2.28. + tasks/scripts/verify-glibc-symbols.sh 2.28 "$binary" + + - name: Package ${{ matrix.binary }} + run: | + mkdir -p artifacts + install -m0755 target/release/${{ matrix.binary }} artifacts/${{ matrix.binary }} + tar -czf artifacts/${{ matrix.binary }}-${{ matrix.triple }}.tar.gz -C artifacts ${{ matrix.binary }} + + - name: Upload ${{ matrix.binary }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: ${{ matrix.binary }}-${{ matrix.triple }} + path: artifacts/*.tar.gz + retention-days: 5 + if-no-files-found: error diff --git a/flake.nix b/flake.nix index 68e6156e3b..af26a5a544 100644 --- a/flake.nix +++ b/flake.nix @@ -47,14 +47,17 @@ testGuestPkgs = import nixpkgs-test-guest { inherit system; }; commonDevShellPackages = with pkgs; [ actionlint + cargo-auditable cargo-deny cargo-nextest # Assemble Debian artifacts on macOS and Linux. dpkg + git # Required to find packages. pkg-config # Coverage. lcov + syft uv zizmor ]; From 3d2bbae6c60513f8edfd9886ead4c1ab0af3aeea Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 10:47:30 +0200 Subject: [PATCH 02/17] ci: build VM artifacts with Nix Refs #1683 Signed-off-by: Simon Scatton --- .github/actions/build-rust-binary/action.yml | 82 +++++++++++ .github/workflows/build-binaries.yml | 135 ++++++++++++------- flake.nix | 4 + nix/pkgs/vm-runtime.nix | 46 +++++++ 4 files changed, 217 insertions(+), 50 deletions(-) create mode 100644 .github/actions/build-rust-binary/action.yml create mode 100644 nix/pkgs/vm-runtime.nix diff --git a/.github/actions/build-rust-binary/action.yml b/.github/actions/build-rust-binary/action.yml new file mode 100644 index 0000000000..616b0b14c5 --- /dev/null +++ b/.github/actions/build-rust-binary/action.yml @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Build Rust Binary +description: Build, verify, package, and upload an auditable Rust binary + +inputs: + binary: + description: Cargo binary name + required: true + triple: + description: Rust target triple + required: true + dev-shell: + description: Nix development shell used to build the binary + required: true + cargo-version: + description: Cargo package version embedded in the binary + required: true + interpreter: + description: ELF interpreter for a dynamically linked Linux binary + required: false + default: "" + +runs: + using: composite + steps: + - name: Cache Rust artifacts + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + with: + shared-key: binaries-${{ inputs.binary }}-${{ inputs.triple }} + cache-on-failure: "true" + cache-workspace-crates: "true" + cache-bin: "false" + cmd-format: nix develop ${{ inputs.dev-shell }} -c {0} + + - name: Set version + shell: nix develop ${{ inputs.dev-shell }} -c bash -euo pipefail {0} + run: sed -i 's/^version = "0\.0\.0"$/version = "${{ inputs.cargo-version }}"/' Cargo.toml + + - name: Build ${{ inputs.binary }} + shell: nix develop ${{ inputs.dev-shell }} -c bash -euo pipefail {0} + run: GIT_DIR=/nonexistent cargo auditable build --release --bin "${{ inputs.binary }}" + + - name: Verify ${{ inputs.binary }} + shell: nix develop ${{ inputs.dev-shell }} -c bash -euo pipefail {0} + run: | + # Confirm the binary runs and reports the expected name. + target/release/${{ inputs.binary }} --version | grep -q '^${{ inputs.binary }} ' + # Confirm Syft can decode the embedded cargo-auditable metadata. + SYFT_CHECK_FOR_APP_UPDATE=false syft file:target/release/${{ inputs.binary }} -o cyclonedx-json | grep 'pkg:cargo/' > /dev/null + + - name: Verify static linkage + if: endsWith(inputs.triple, '-linux-musl') + shell: nix develop ${{ inputs.dev-shell }} -c bash -euo pipefail {0} + run: tasks/scripts/verify-static-binary.sh target/release/${{ inputs.binary }} + + - name: Normalize Linux dynamic binary + if: endsWith(inputs.triple, '-linux-gnu') + shell: nix develop ${{ inputs.dev-shell }} -c bash -euo pipefail {0} + run: | + binary=target/release/${{ inputs.binary }} + # Remove Nix store paths so the binary can run on other distributions. + patchelf --set-interpreter "${{ inputs.interpreter }}" --remove-rpath "$binary" + # Z3 must be embedded instead of loaded from the target system. + test -z "$(patchelf --print-needed "$binary" | grep '^libz3')" + # Reject symbols introduced after glibc 2.28. + tasks/scripts/verify-glibc-symbols.sh 2.28 "$binary" + + - name: Package ${{ inputs.binary }} + shell: nix develop ${{ inputs.dev-shell }} -c bash -euo pipefail {0} + run: | + install -Dm0755 target/release/${{ inputs.binary }} artifacts/${{ inputs.binary }} + tar -czf artifacts/${{ inputs.binary }}-${{ inputs.triple }}.tar.gz -C artifacts ${{ inputs.binary }} + + - name: Upload ${{ inputs.binary }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: ${{ inputs.binary }}-${{ inputs.triple }} + path: artifacts/*.tar.gz + retention-days: 5 + if-no-files-found: error diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 71bb0e03a8..f23c293c86 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -38,7 +38,7 @@ jobs: cargo="$(python3 tasks/scripts/release.py get-version --cargo)" echo "cargo=$cargo" >> "$GITHUB_OUTPUT" - binary: + rust-binary: name: ${{ matrix.binary }} (${{ matrix.triple }}) needs: version strategy: @@ -56,6 +56,14 @@ jobs: triple: aarch64-apple-darwin runner: macos-15-xlarge dev_shell: .#devShells.aarch64-darwin.default + - binary: openshell-sandbox + triple: x86_64-unknown-linux-musl + runner: linux-amd64-cpu8 + dev_shell: .#devShells.x86_64-linux.musl + - binary: openshell-sandbox + triple: aarch64-unknown-linux-musl + runner: linux-arm64-cpu8 + dev_shell: .#devShells.aarch64-linux.musl - binary: openshell-gateway triple: x86_64-unknown-linux-gnu runner: linux-amd64-cpu8 @@ -72,70 +80,97 @@ jobs: dev_shell: .#devShells.aarch64-darwin.default runs-on: ${{ matrix.runner }} timeout-minutes: 60 - defaults: - run: - shell: nix develop ${{ matrix.dev_shell }} -c bash -euo pipefail {0} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ inputs['checkout-ref'] || github.sha }} - - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1 + - uses: ./.github/actions/setup-nix with: - github_access_token: ${{ secrets.GITHUB_TOKEN }} + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - - uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 + - name: Build ${{ matrix.binary }} + uses: ./.github/actions/build-rust-binary + with: + binary: ${{ matrix.binary }} + triple: ${{ matrix.triple }} + dev-shell: ${{ matrix.dev_shell }} + cargo-version: ${{ needs.version.outputs.cargo }} + interpreter: ${{ matrix.interpreter }} + + vm-driver: + name: openshell-driver-vm (${{ matrix.triple }}) + needs: [version, rust-binary] + strategy: + matrix: + include: + - arch: x86_64 + triple: x86_64-unknown-linux-gnu + runner: linux-amd64-cpu8 + dev_shell: .#devShells.x86_64-linux.glibc-2-28 + interpreter: /lib64/ld-linux-x86-64.so.2 + - arch: aarch64 + triple: aarch64-unknown-linux-gnu + runner: linux-arm64-cpu8 + dev_shell: .#devShells.aarch64-linux.glibc-2-28 + interpreter: /lib/ld-linux-aarch64.so.1 + - arch: aarch64 + triple: aarch64-apple-darwin + runner: macos-15-xlarge + dev_shell: .#devShells.aarch64-darwin.default + runs-on: ${{ matrix.runner }} + timeout-minutes: 60 + defaults: + run: + shell: nix develop ${{ matrix.dev_shell }} -c bash -euo pipefail {0} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - name: openshell - authToken: ${{ secrets.CACHIX_AUTH_TOKEN }} + ref: ${{ inputs['checkout-ref'] || github.sha }} - - name: Cache Rust artifacts - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + - uses: ./.github/actions/setup-nix with: - shared-key: binaries-${{ matrix.binary }}-${{ matrix.triple }} - cache-on-failure: "true" - cache-workspace-crates: "true" - cache-bin: "false" - cmd-format: nix develop ${{ matrix.dev_shell }} -c {0} + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - - name: Set version - run: sed -i 's/^version = "0\.0\.0"$/version = "${{ needs.version.outputs.cargo }}"/' Cargo.toml + - name: Download openshell-sandbox + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: openshell-sandbox-${{ matrix.arch }}-unknown-linux-musl + path: sandbox - - name: Build ${{ matrix.binary }} - run: GIT_DIR=/nonexistent cargo auditable build --release --bin "${{ matrix.binary }}" + - name: Build VM runtime + run: nix build .#vm-runtime - - name: Verify ${{ matrix.binary }} + - name: Compress Linux VM runtime + if: runner.os == 'Linux' run: | - # Confirm the binary runs and reports the expected name. - target/release/${{ matrix.binary }} --version | grep -q '^${{ matrix.binary }} ' - # Confirm Syft can decode the embedded cargo-auditable metadata. - SYFT_CHECK_FOR_APP_UPDATE=false syft "file:target/release/${{ matrix.binary }}" -o cyclonedx-json | grep 'pkg:cargo/' > /dev/null - - - name: Verify static linkage - if: endsWith(matrix.triple, '-linux-musl') - run: tasks/scripts/verify-static-binary.sh target/release/${{ matrix.binary }} - - - name: Normalize Linux dynamic binary - if: endsWith(matrix.triple, '-linux-gnu') + install -d target/vm-runtime-compressed + zstd -19 -T0 result/libkrun.so -o target/vm-runtime-compressed/libkrun.so.zst + zstd -19 -T0 result/libkrunfw.so.5 -o target/vm-runtime-compressed/libkrunfw.so.5.zst + zstd -19 -T0 result/gvproxy -o target/vm-runtime-compressed/gvproxy.zst + zstd -19 -T0 result/umoci -o target/vm-runtime-compressed/umoci.zst + + - name: Compress macOS VM runtime + if: runner.os == 'macOS' run: | - binary=target/release/${{ matrix.binary }} - # Remove Nix store paths so the binary can run on other distributions. - patchelf --set-interpreter "${{ matrix.interpreter }}" --remove-rpath "$binary" - # Z3 must be embedded instead of loaded from the target system. - test -z "$(patchelf --print-needed "$binary" | grep '^libz3')" - # Reject symbols introduced after glibc 2.28. - tasks/scripts/verify-glibc-symbols.sh 2.28 "$binary" - - - name: Package ${{ matrix.binary }} + install -d target/vm-runtime-compressed + zstd -19 -T0 result/libkrun.dylib -o target/vm-runtime-compressed/libkrun.dylib.zst + zstd -19 -T0 result/libkrunfw.5.dylib -o target/vm-runtime-compressed/libkrunfw.5.dylib.zst + zstd -19 -T0 result/gvproxy -o target/vm-runtime-compressed/gvproxy.zst + zstd -19 -T0 result/umoci -o target/vm-runtime-compressed/umoci.zst + + - name: Add openshell-sandbox to VM runtime run: | - mkdir -p artifacts - install -m0755 target/release/${{ matrix.binary }} artifacts/${{ matrix.binary }} - tar -czf artifacts/${{ matrix.binary }}-${{ matrix.triple }}.tar.gz -C artifacts ${{ matrix.binary }} + tar -xzf sandbox/openshell-sandbox-${{ matrix.arch }}-unknown-linux-musl.tar.gz -C sandbox + zstd -19 -T0 sandbox/openshell-sandbox -o target/vm-runtime-compressed/openshell-sandbox.zst - - name: Upload ${{ matrix.binary }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + - name: Build openshell-driver-vm + uses: ./.github/actions/build-rust-binary + env: + OPENSHELL_VM_RUNTIME_COMPRESSED_DIR: ${{ github.workspace }}/target/vm-runtime-compressed with: - name: ${{ matrix.binary }}-${{ matrix.triple }} - path: artifacts/*.tar.gz - retention-days: 5 - if-no-files-found: error + binary: openshell-driver-vm + triple: ${{ matrix.triple }} + dev-shell: ${{ matrix.dev_shell }} + cargo-version: ${{ needs.version.outputs.cargo }} + interpreter: ${{ matrix.interpreter }} diff --git a/flake.nix b/flake.nix index af26a5a544..e361fd1597 100644 --- a/flake.nix +++ b/flake.nix @@ -60,6 +60,7 @@ syft uv zizmor + zstd ]; treefmtEval = treefmt-nix.lib.evalModule pkgs { projectRootFile = "flake.nix"; @@ -68,6 +69,7 @@ rustToolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; z3-static = pkgs.callPackage ./nix/pkgs/z3-static.nix { }; aws-lc-static = pkgs.callPackage ./nix/pkgs/aws-lc-static.nix { }; + vmRuntime = pkgs.callPackage ./nix/pkgs/vm-runtime.nix { }; testGuest = import ./nix/test-guest { inherit pkgs; qemuPkgs = testGuestPkgs; @@ -78,6 +80,8 @@ apps.test-guest = testGuest.app; apps.test-guest-cache = testGuest.cacheApp; + packages.vm-runtime = vmRuntime; + devShells = { default = (pkgs.mkShell.override { diff --git a/nix/pkgs/vm-runtime.nix b/nix/pkgs/vm-runtime.nix new file mode 100644 index 0000000000..49a051e276 --- /dev/null +++ b/nix/pkgs/vm-runtime.nix @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +{ + fetchurl, + stdenv, + zstd, +}: + +let + runtime = + { + x86_64-linux = { + platform = "linux-x86_64"; + hash = "sha256-dw3Lc7IapCyNeE7j6dnlgd/b8Yc91/7IOi3XJORyILQ="; + }; + aarch64-linux = { + platform = "linux-aarch64"; + hash = "sha256-aJDuDb7AsuH9R+AyXA/JIxE9fJmZ5kP0Lkhg6F0Ot5A="; + }; + aarch64-darwin = { + platform = "darwin-aarch64"; + hash = "sha256-BDSeY5XGDozaBZzHTiQQX90jzsSc6shJZs5zdzludX0="; + }; + } + .${stdenv.hostPlatform.system}; + archive = fetchurl { + url = "https://github.com/NVIDIA/OpenShell/releases/download/vm-runtime/vm-runtime-${runtime.platform}.tar.zst"; + inherit (runtime) hash; + }; +in +stdenv.mkDerivation { + name = "openshell-vm-runtime-${runtime.platform}"; + + nativeBuildInputs = [ zstd ]; + dontUnpack = true; + + installPhase = '' + runHook preInstall + + mkdir -p "$out" + tar --extract --file ${archive} --directory "$out" + + runHook postInstall + ''; +} From 7894e095b076a78e93d014652fffbeb152cee070 Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 11:53:24 +0200 Subject: [PATCH 03/17] ci: build images from Nix artifacts Refs #1683 Signed-off-by: Simon Scatton --- .github/actions/setup-e2e-cli/action.yml | 13 +- .github/actions/setup-e2e-gateway/action.yml | 13 +- .../actions/setup-e2e-vm-driver/action.yml | 26 +- .github/workflows/branch-e2e.yml | 132 ++----- .github/workflows/docker-build.yml | 339 ++++-------------- .github/workflows/e2e-gpu-test.yaml | 16 - .github/workflows/e2e-kubernetes-test.yml | 21 -- .github/workflows/e2e-test.yml | 213 +++++++++-- deploy/docker/Dockerfile.gateway | 15 +- deploy/docker/Dockerfile.supervisor | 24 +- 10 files changed, 294 insertions(+), 518 deletions(-) diff --git a/.github/actions/setup-e2e-cli/action.yml b/.github/actions/setup-e2e-cli/action.yml index fb894194c8..f41c883f99 100644 --- a/.github/actions/setup-e2e-cli/action.yml +++ b/.github/actions/setup-e2e-cli/action.yml @@ -1,29 +1,20 @@ name: Setup E2E CLI description: Download an architecture-matched prebuilt OpenShell CLI for E2E tests -inputs: - artifact-prefix: - description: Artifact name prefix; linux- is appended automatically - required: true - runs: using: composite steps: - name: Download prebuilt CLI uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: ${{ format('{0}-linux-{1}', inputs.artifact-prefix, runner.arch == 'X64' && 'amd64' || 'arm64') }} + name: ${{ runner.arch == 'X64' && 'openshell-x86_64-unknown-linux-musl' || 'openshell-aarch64-unknown-linux-musl' }} path: .e2e/prebuilt-cli - name: Configure prebuilt CLI shell: bash run: | - set -euo pipefail + tar -xzf "$GITHUB_WORKSPACE"/.e2e/prebuilt-cli/*.tar.gz -C "$GITHUB_WORKSPACE/.e2e/prebuilt-cli" cli="$GITHUB_WORKSPACE/.e2e/prebuilt-cli/openshell" - if [[ ! -f "$cli" ]]; then - echo "downloaded artifact is missing $cli" >&2 - exit 1 - fi chmod +x "$cli" "$cli" --version echo "OPENSHELL_BIN=$cli" >> "$GITHUB_ENV" diff --git a/.github/actions/setup-e2e-gateway/action.yml b/.github/actions/setup-e2e-gateway/action.yml index b0ee52392b..f77c338235 100644 --- a/.github/actions/setup-e2e-gateway/action.yml +++ b/.github/actions/setup-e2e-gateway/action.yml @@ -1,29 +1,20 @@ name: Setup E2E Gateway description: Download an architecture-matched prebuilt OpenShell gateway for E2E tests -inputs: - artifact-prefix: - description: Artifact name prefix; linux- is appended automatically - required: true - runs: using: composite steps: - name: Download prebuilt gateway uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: ${{ format('{0}-linux-{1}', inputs.artifact-prefix, runner.arch == 'X64' && 'amd64' || 'arm64') }} + name: ${{ runner.arch == 'X64' && 'openshell-gateway-x86_64-unknown-linux-gnu' || 'openshell-gateway-aarch64-unknown-linux-gnu' }} path: .e2e/prebuilt-gateway - name: Configure prebuilt gateway shell: bash run: | - set -euo pipefail + tar -xzf "$GITHUB_WORKSPACE"/.e2e/prebuilt-gateway/*.tar.gz -C "$GITHUB_WORKSPACE/.e2e/prebuilt-gateway" gateway="$GITHUB_WORKSPACE/.e2e/prebuilt-gateway/openshell-gateway" - if [[ ! -f "$gateway" ]]; then - echo "downloaded artifact is missing $gateway" >&2 - exit 1 - fi chmod +x "$gateway" "$gateway" --version echo "OPENSHELL_GATEWAY_BIN=$gateway" >> "$GITHUB_ENV" diff --git a/.github/actions/setup-e2e-vm-driver/action.yml b/.github/actions/setup-e2e-vm-driver/action.yml index 20c43cf98f..191f001dec 100644 --- a/.github/actions/setup-e2e-vm-driver/action.yml +++ b/.github/actions/setup-e2e-vm-driver/action.yml @@ -1,10 +1,5 @@ name: Setup E2E VM Driver -description: Download a prebuilt OpenShell VM driver for E2E tests - -inputs: - artifact-name: - description: Artifact name to download, such as driver-vm-linux-amd64 - required: true +description: Download an architecture-matched prebuilt OpenShell VM driver for E2E tests runs: using: composite @@ -12,27 +7,14 @@ runs: - name: Download prebuilt VM driver uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: ${{ inputs.artifact-name }} + name: ${{ runner.arch == 'X64' && 'openshell-driver-vm-x86_64-unknown-linux-gnu' || 'openshell-driver-vm-aarch64-unknown-linux-gnu' }} path: .e2e/prebuilt-vm-driver - name: Configure prebuilt VM driver shell: bash run: | - set -euo pipefail - extract_dir="$GITHUB_WORKSPACE/.e2e/prebuilt-vm-driver/bin" - mkdir -p "$extract_dir" - archive="$(find "$GITHUB_WORKSPACE/.e2e/prebuilt-vm-driver" -maxdepth 1 -type f -name 'openshell-driver-vm-*.tar.gz' -print -quit)" - if [[ -z "$archive" ]]; then - echo "downloaded artifact is missing openshell-driver-vm-*.tar.gz" >&2 - find "$GITHUB_WORKSPACE/.e2e/prebuilt-vm-driver" -maxdepth 2 -type f -print >&2 || true - exit 1 - fi - tar -xzf "$archive" -C "$extract_dir" - driver="$extract_dir/openshell-driver-vm" - if [[ ! -f "$driver" ]]; then - echo "downloaded artifact is missing $driver" >&2 - exit 1 - fi + tar -xzf "$GITHUB_WORKSPACE"/.e2e/prebuilt-vm-driver/*.tar.gz -C "$GITHUB_WORKSPACE/.e2e/prebuilt-vm-driver" + driver="$GITHUB_WORKSPACE/.e2e/prebuilt-vm-driver/openshell-driver-vm" chmod +x "$driver" "$driver" --version echo "OPENSHELL_VM_DRIVER_BIN=$driver" >> "$GITHUB_ENV" diff --git a/.github/workflows/branch-e2e.yml b/.github/workflows/branch-e2e.yml index 659027a8d6..fed4342684 100644 --- a/.github/workflows/branch-e2e.yml +++ b/.github/workflows/branch-e2e.yml @@ -76,57 +76,25 @@ jobs: echo "run_any_e2e=$run_any_e2e" } >> "$GITHUB_OUTPUT" - build-gateway: + build-binaries: needs: [pr_metadata] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_any_e2e == 'true' permissions: contents: read - packages: write - uses: ./.github/workflows/docker-build.yml - with: - component: gateway - image-tag: ${{ github.sha }} - auditable: true - - build-supervisor: - needs: [pr_metadata] - if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_any_e2e == 'true' - permissions: - contents: read - packages: write - uses: ./.github/workflows/docker-build.yml - with: - component: supervisor - image-tag: ${{ github.sha }} - auditable: true + uses: ./.github/workflows/build-binaries.yml + secrets: inherit - build-cli: - needs: [pr_metadata] + build-images: + needs: [pr_metadata, build-binaries] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_any_e2e == 'true' permissions: contents: read packages: write uses: ./.github/workflows/docker-build.yml - with: - component: cli - platform: linux/amd64,linux/arm64 - - build-driver-vm-linux: - needs: [pr_metadata] - if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' - permissions: - contents: read - packages: read - uses: ./.github/workflows/driver-vm-linux.yml - with: - image-tag: ${{ github.sha }} - checkout-ref: ${{ github.sha }} - runtime-platforms: linux-x86_64 - driver-targets: >- - [{"arch":"amd64","runner":"linux-amd64-cpu8","target":"x86_64-unknown-linux-gnu","zig_target":"x86_64-unknown-linux-gnu.2.28","platform":"linux-x86_64","guest_arch":"x86_64"}] + secrets: inherit e2e: - needs: [pr_metadata, build-gateway, build-supervisor, build-cli, build-driver-vm-linux] + needs: [pr_metadata, build-binaries, build-images] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' permissions: actions: read @@ -136,12 +104,9 @@ jobs: with: image-tag: ${{ github.sha }} runner: linux-arm64-cpu8 - cli-artifact-prefix: rust-binary-cli - gateway-artifact-prefix: rust-binary-gateway - vm-driver-artifact-name: driver-vm-linux-amd64 gpu-e2e: - needs: [pr_metadata, build-gateway, build-supervisor, build-cli] + needs: [pr_metadata, build-binaries, build-images] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_gpu_e2e == 'true' permissions: actions: read @@ -150,11 +115,9 @@ jobs: uses: ./.github/workflows/e2e-gpu-test.yaml with: image-tag: ${{ github.sha }} - cli-artifact-prefix: rust-binary-cli - gateway-artifact-prefix: rust-binary-gateway kubernetes-e2e: - needs: [pr_metadata, build-gateway, build-supervisor, build-cli] + needs: [pr_metadata, build-binaries, build-images] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' strategy: fail-fast: false @@ -182,10 +145,9 @@ jobs: job-name: Kubernetes E2E (Rust smoke, ${{ matrix.topology }}, Agent Sandbox ${{ matrix.agent_sandbox_api }}) agent-sandbox-version: ${{ matrix.agent_sandbox_version }} extra-helm-values: ${{ matrix.extra_helm_values }} - cli-artifact-prefix: rust-binary-cli kubernetes-workspace-managed-e2e: - needs: [pr_metadata, build-gateway, build-supervisor, build-cli] + needs: [pr_metadata, build-binaries, build-images] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' permissions: actions: read @@ -196,24 +158,12 @@ jobs: image-tag: ${{ github.sha }} job-name: Kubernetes E2E (workspace managed mode) e2e-task: e2e:kubernetes:workspace-managed - cli-artifact-prefix: rust-binary-cli - kubernetes-external-driver-e2e: - needs: [pr_metadata, build-gateway, build-supervisor, build-cli] - if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' - permissions: - actions: read - contents: read - packages: read - uses: ./.github/workflows/e2e-kubernetes-test.yml - with: - image-tag: ${{ github.sha }} - job-name: Kubernetes E2E (external compute driver) - e2e-task: e2e:kubernetes:external-driver - cli-artifact-prefix: rust-binary-cli + # External Kubernetes driver E2E stays disabled while its standalone binary + # is not a release artifact. kubernetes-workspace-operator-e2e: - needs: [pr_metadata, build-gateway, build-supervisor, build-cli] + needs: [pr_metadata, build-binaries, build-images] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' permissions: actions: read @@ -224,10 +174,9 @@ jobs: image-tag: ${{ github.sha }} job-name: Kubernetes E2E (workspace operator mode) e2e-task: e2e:kubernetes:workspace-operator - cli-artifact-prefix: rust-binary-cli kubernetes-ha-e2e: - needs: [pr_metadata, build-gateway, build-supervisor, build-cli] + needs: [pr_metadata, build-binaries, build-images] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_kubernetes_ha_e2e == 'true' permissions: actions: read @@ -239,10 +188,9 @@ jobs: job-name: Kubernetes HA E2E (Rust smoke) extra-helm-values: deploy/helm/openshell/ci/values-high-availability.yaml external-postgres-secret: openshell-ha-pg - cli-artifact-prefix: rust-binary-cli kubernetes-credential-drivers-e2e: - needs: [pr_metadata, build-gateway, build-supervisor] + needs: [pr_metadata, build-binaries, build-images] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_kubernetes_credential_drivers_e2e == 'true' permissions: actions: read @@ -256,32 +204,26 @@ jobs: core-e2e-result: name: Core E2E result - needs: [pr_metadata, build-gateway, build-supervisor, build-cli, build-driver-vm-linux, e2e, kubernetes-e2e, kubernetes-external-driver-e2e, kubernetes-workspace-managed-e2e, kubernetes-workspace-operator-e2e] + needs: [pr_metadata, build-binaries, build-images, e2e, kubernetes-e2e, kubernetes-workspace-managed-e2e, kubernetes-workspace-operator-e2e] if: always() && needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' runs-on: ubuntu-latest steps: - name: Verify core E2E jobs env: - BUILD_GATEWAY_RESULT: ${{ needs.build-gateway.result }} - BUILD_SUPERVISOR_RESULT: ${{ needs.build-supervisor.result }} - BUILD_CLI_RESULT: ${{ needs.build-cli.result }} - BUILD_DRIVER_VM_RESULT: ${{ needs.build-driver-vm-linux.result }} + BUILD_BINARIES_RESULT: ${{ needs.build-binaries.result }} + BUILD_IMAGES_RESULT: ${{ needs.build-images.result }} E2E_RESULT: ${{ needs.e2e.result }} KUBERNETES_E2E_RESULT: ${{ needs.kubernetes-e2e.result }} - KUBERNETES_EXTERNAL_DRIVER_E2E_RESULT: ${{ needs.kubernetes-external-driver-e2e.result }} KUBERNETES_WORKSPACE_MANAGED_E2E_RESULT: ${{ needs.kubernetes-workspace-managed-e2e.result }} KUBERNETES_WORKSPACE_OPERATOR_E2E_RESULT: ${{ needs.kubernetes-workspace-operator-e2e.result }} run: | set -euo pipefail failed=0 for item in \ - "build-gateway:$BUILD_GATEWAY_RESULT" \ - "build-supervisor:$BUILD_SUPERVISOR_RESULT" \ - "build-cli:$BUILD_CLI_RESULT" \ - "build-driver-vm-linux:$BUILD_DRIVER_VM_RESULT" \ + "build-binaries:$BUILD_BINARIES_RESULT" \ + "build-images:$BUILD_IMAGES_RESULT" \ "e2e:$E2E_RESULT" \ "kubernetes-e2e:$KUBERNETES_E2E_RESULT" \ - "kubernetes-external-driver-e2e:$KUBERNETES_EXTERNAL_DRIVER_E2E_RESULT" \ "kubernetes-workspace-managed-e2e:$KUBERNETES_WORKSPACE_MANAGED_E2E_RESULT" \ "kubernetes-workspace-operator-e2e:$KUBERNETES_WORKSPACE_OPERATOR_E2E_RESULT"; do name="${item%%:*}" @@ -295,23 +237,21 @@ jobs: gpu-e2e-result: name: GPU E2E result - needs: [pr_metadata, build-gateway, build-supervisor, build-cli, gpu-e2e] + needs: [pr_metadata, build-binaries, build-images, gpu-e2e] if: always() && needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_gpu_e2e == 'true' runs-on: ubuntu-latest steps: - name: Verify GPU E2E jobs env: - BUILD_GATEWAY_RESULT: ${{ needs.build-gateway.result }} - BUILD_SUPERVISOR_RESULT: ${{ needs.build-supervisor.result }} - BUILD_CLI_RESULT: ${{ needs.build-cli.result }} + BUILD_BINARIES_RESULT: ${{ needs.build-binaries.result }} + BUILD_IMAGES_RESULT: ${{ needs.build-images.result }} GPU_E2E_RESULT: ${{ needs.gpu-e2e.result }} run: | set -euo pipefail failed=0 for item in \ - "build-gateway:$BUILD_GATEWAY_RESULT" \ - "build-supervisor:$BUILD_SUPERVISOR_RESULT" \ - "build-cli:$BUILD_CLI_RESULT" \ + "build-binaries:$BUILD_BINARIES_RESULT" \ + "build-images:$BUILD_IMAGES_RESULT" \ "gpu-e2e:$GPU_E2E_RESULT"; do name="${item%%:*}" result="${item#*:}" @@ -324,23 +264,21 @@ jobs: kubernetes-ha-e2e-result: name: Kubernetes HA E2E result - needs: [pr_metadata, build-gateway, build-supervisor, build-cli, kubernetes-ha-e2e] + needs: [pr_metadata, build-binaries, build-images, kubernetes-ha-e2e] if: always() && needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_kubernetes_ha_e2e == 'true' runs-on: ubuntu-latest steps: - name: Verify Kubernetes HA E2E jobs env: - BUILD_GATEWAY_RESULT: ${{ needs.build-gateway.result }} - BUILD_SUPERVISOR_RESULT: ${{ needs.build-supervisor.result }} - BUILD_CLI_RESULT: ${{ needs.build-cli.result }} + BUILD_BINARIES_RESULT: ${{ needs.build-binaries.result }} + BUILD_IMAGES_RESULT: ${{ needs.build-images.result }} KUBERNETES_HA_E2E_RESULT: ${{ needs.kubernetes-ha-e2e.result }} run: | set -euo pipefail failed=0 for item in \ - "build-gateway:$BUILD_GATEWAY_RESULT" \ - "build-supervisor:$BUILD_SUPERVISOR_RESULT" \ - "build-cli:$BUILD_CLI_RESULT" \ + "build-binaries:$BUILD_BINARIES_RESULT" \ + "build-images:$BUILD_IMAGES_RESULT" \ "kubernetes-ha-e2e:$KUBERNETES_HA_E2E_RESULT"; do name="${item%%:*}" result="${item#*:}" @@ -353,21 +291,21 @@ jobs: kubernetes-credential-drivers-e2e-result: name: Kubernetes Credential Drivers E2E result - needs: [pr_metadata, build-gateway, build-supervisor, kubernetes-credential-drivers-e2e] + needs: [pr_metadata, build-binaries, build-images, kubernetes-credential-drivers-e2e] if: always() && needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_kubernetes_credential_drivers_e2e == 'true' runs-on: ubuntu-latest steps: - name: Verify Kubernetes credential drivers E2E jobs env: - BUILD_GATEWAY_RESULT: ${{ needs.build-gateway.result }} - BUILD_SUPERVISOR_RESULT: ${{ needs.build-supervisor.result }} + BUILD_BINARIES_RESULT: ${{ needs.build-binaries.result }} + BUILD_IMAGES_RESULT: ${{ needs.build-images.result }} KUBERNETES_CREDENTIAL_DRIVERS_E2E_RESULT: ${{ needs.kubernetes-credential-drivers-e2e.result }} run: | set -euo pipefail failed=0 for item in \ - "build-gateway:$BUILD_GATEWAY_RESULT" \ - "build-supervisor:$BUILD_SUPERVISOR_RESULT" \ + "build-binaries:$BUILD_BINARIES_RESULT" \ + "build-images:$BUILD_IMAGES_RESULT" \ "kubernetes-credential-drivers-e2e:$KUBERNETES_CREDENTIAL_DRIVERS_E2E_RESULT"; do name="${item%%:*}" result="${item#*:}" diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 73cb678e78..e77fae85f1 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -1,200 +1,53 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + name: Docker Build on: workflow_call: inputs: - component: - description: "Component to build (gateway, supervisor, cli)" - required: true - type: string - timeout-minutes: - description: "Per-arch Docker image job timeout in minutes" - required: false - type: number - default: 20 - push: - description: "Push image to registry" - required: false - type: boolean - default: true - platform: - description: "Target platform(s) for Docker build" - required: false - type: string - default: "linux/amd64,linux/arm64" - runner: - description: "Deprecated; per-arch native runners are selected automatically" - required: false - type: string - default: "linux-amd64-cpu8" - cargo-version: - description: "Pre-computed cargo version (skips internal git-based computation)" - required: false - type: string - default: "" - auditable: - description: "Embed cargo-auditable dependency metadata in image binaries" - required: false - type: boolean - default: false - image-tag: - description: "Image tag base to build/push (defaults to the GitHub SHA)" - required: false - type: string - default: "" checkout-ref: - description: "Git ref to check out for build inputs (defaults to the workflow SHA)" required: false type: string default: "" - publish-manifest: - description: "Push the bare-SHA manifest. Set false for single-arch branch workflows." - required: false - type: boolean - default: true - -env: - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} permissions: contents: read packages: write -defaults: - run: - shell: bash - jobs: - resolve: - name: Resolve build plan - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.resolve.outputs.matrix }} - platform_count: ${{ steps.resolve.outputs.platform_count }} - arches: ${{ steps.resolve.outputs.arches }} - binary_component: ${{ steps.resolve.outputs.binary_component }} - binary_name: ${{ steps.resolve.outputs.binary_name }} - artifact_prefix: ${{ steps.resolve.outputs.artifact_prefix }} - image_tag_base: ${{ steps.resolve.outputs.image_tag_base }} - features: ${{ steps.resolve.outputs.features }} - has_image: ${{ steps.resolve.outputs.has_image }} - steps: - - name: Resolve component and platform matrix - id: resolve - run: | - set -euo pipefail - - component="${{ inputs.component }}" - case "$component" in - gateway) - binary_component=gateway - binary_name=openshell-gateway - features="bundled-z3" - has_image=true - ;; - supervisor) - binary_component=sandbox - binary_name=openshell-sandbox - features="" - has_image=true - ;; - cli) - binary_component=cli - binary_name=openshell - features="" - has_image=false - ;; - *) - echo "unsupported component: $component" >&2 - exit 1 - ;; - esac - - image_tag_base="${{ inputs['image-tag'] }}" - if [[ -z "$image_tag_base" ]]; then - image_tag_base="$GITHUB_SHA" - fi - - platform_input="${{ inputs.platform }}" - platform_input="${platform_input//[[:space:]]/}" - if [[ -z "$platform_input" ]]; then - echo "platform input must not be empty" >&2 - exit 1 - fi - - IFS=',' read -r -a platforms <<< "$platform_input" - matrix='{"include":[' - arches=() - count=0 - - for platform in "${platforms[@]}"; do - case "$platform" in - linux/amd64) - arch=amd64 - runner=linux-amd64-cpu8 - ;; - linux/arm64) - arch=arm64 - runner=linux-arm64-cpu8 - ;; - *) - echo "unsupported platform: $platform" >&2 - echo "supported platforms: linux/amd64, linux/arm64" >&2 - exit 1 - ;; - esac - - if [[ $count -gt 0 ]]; then - matrix+=',' - fi - matrix+='{"platform":"'"$platform"'","arch":"'"$arch"'","runner":"'"$runner"'"}' - arches+=("$arch") - count=$((count + 1)) - done - - matrix+=']}' - { - echo "matrix=$matrix" - echo "platform_count=$count" - echo "arches=${arches[*]}" - echo "binary_component=$binary_component" - echo "binary_name=$binary_name" - echo "artifact_prefix=rust-binary-${component}" - echo "image_tag_base=$image_tag_base" - echo "features=$features" - echo "has_image=$has_image" - } >> "$GITHUB_OUTPUT" - - rust-binary: - name: Rust ${{ needs.resolve.outputs.binary_component }} (${{ matrix.arch }}) - needs: resolve - permissions: - contents: read - packages: read - strategy: - fail-fast: false - matrix: ${{ fromJSON(needs.resolve.outputs.matrix) }} - uses: ./.github/workflows/rust-native-build.yml - with: - component: ${{ needs.resolve.outputs.binary_component }} - arch: ${{ matrix.arch }} - cargo-version: ${{ inputs['cargo-version'] }} - image-tag: ${{ needs.resolve.outputs.image_tag_base }} - checkout-ref: ${{ inputs['checkout-ref'] }} - features: ${{ needs.resolve.outputs.features }} - auditable: ${{ inputs.auditable }} - artifact-name: ${{ needs.resolve.outputs.artifact_prefix }}-linux-${{ matrix.arch }} - secrets: inherit - build: - name: Build ${{ inputs.component }} (${{ matrix.arch }}) - needs: [resolve, rust-binary] - if: needs.resolve.outputs.has_image == 'true' - runs-on: ${{ matrix.runner }} - timeout-minutes: ${{ inputs['timeout-minutes'] }} + name: ${{ matrix.component }} (${{ matrix.platform }}) strategy: fail-fast: false - matrix: ${{ fromJSON(needs.resolve.outputs.matrix) }} + matrix: + include: + - component: gateway + binary: openshell-gateway + triple: x86_64-unknown-linux-gnu + arch: amd64 + platform: linux/amd64 + runner: linux-amd64-cpu8 + - component: gateway + binary: openshell-gateway + triple: aarch64-unknown-linux-gnu + arch: arm64 + platform: linux/arm64 + runner: linux-arm64-cpu8 + - component: supervisor + binary: openshell-sandbox + triple: x86_64-unknown-linux-musl + arch: amd64 + platform: linux/amd64 + runner: linux-amd64-cpu8 + - component: supervisor + binary: openshell-sandbox + triple: aarch64-unknown-linux-musl + arch: arm64 + platform: linux/arm64 + runner: linux-arm64-cpu8 + runs-on: ${{ matrix.runner }} + timeout-minutes: 20 container: image: ghcr.io/nvidia/openshell/ci:latest credentials: @@ -203,87 +56,54 @@ jobs: options: --privileged volumes: - /var/run/docker.sock:/var/run/docker.sock - # Expose the nv-gha-runners buildkitd.toml registry mirror config - # inside the container so setup-buildx can read it. - /etc/buildkit:/etc/buildkit:ro - env: - IMAGE_TAG: ${{ format('{0}-{1}', needs.resolve.outputs.image_tag_base, matrix.arch) }} - IMAGE_REGISTRY: ghcr.io/nvidia/openshell - DOCKER_PUSH: ${{ inputs.push && '1' || '0' }} - DOCKER_PLATFORM: ${{ matrix.platform }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ inputs['checkout-ref'] || github.sha }} - fetch-depth: 0 - - - name: Mark workspace safe for git - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - name: Install tools - run: mise install --locked + - uses: ./.github/actions/setup-buildx + with: + buildkitd-config: /etc/buildkit/buildkitd.toml - name: Log in to GHCR - if: ${{ inputs.push }} + shell: bash run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - - name: Set up buildx (local driver) - uses: ./.github/actions/setup-buildx - with: - buildkitd-config: /etc/buildkit/buildkitd.toml - - - name: Download Rust binary artifact + - name: Download ${{ matrix.binary }} uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: ${{ needs.resolve.outputs.artifact_prefix }}-linux-${{ matrix.arch }} - path: prebuilt-rust-binary - - - name: Stage Rust binary in Docker build context - run: | - set -euo pipefail - binary="${{ needs.resolve.outputs.binary_name }}" - download_dir="prebuilt-rust-binary" - stage="deploy/docker/.build/prebuilt-binaries/${{ matrix.arch }}" - found="$(find "$download_dir" -type f -name "$binary" -print -quit)" - if [[ -z "$found" ]]; then - echo "missing downloaded artifact file: $binary" >&2 - find "$download_dir" -maxdepth 4 -type f -print >&2 || true - exit 1 - fi - mkdir -p "$stage" - install -m 0755 "$found" "$stage/$binary" - ls -lh "$stage/" + name: ${{ matrix.binary }}-${{ matrix.triple }} + path: artifact - - name: Build ${{ inputs.component }} image - env: - DOCKER_BUILDER: openshell + - name: Stage ${{ matrix.binary }} + shell: bash run: | - set -euo pipefail - mise exec -- tasks/scripts/docker-build-image.sh "${{ inputs.component }}" \ - --cache-from "type=gha,scope=${{ inputs.component }}-${{ matrix.arch }}" \ - --cache-to "type=gha,mode=max,scope=${{ inputs.component }}-${{ matrix.arch }}" + install -d deploy/docker/.build/prebuilt-binaries/${{ matrix.arch }} + tar -xzf artifact/${{ matrix.binary }}-${{ matrix.triple }}.tar.gz -C artifact + install -m 0755 artifact/${{ matrix.binary }} deploy/docker/.build/prebuilt-binaries/${{ matrix.arch }}/${{ matrix.binary }} - - name: Smoke check ${{ inputs.component }} image + - name: Build ${{ matrix.component }} image + shell: bash run: | - set -euo pipefail - image="${IMAGE_REGISTRY}/${{ inputs.component }}:${IMAGE_TAG}" - case "${{ inputs.component }}" in - gateway) - output="$(docker run --rm --platform "${{ matrix.platform }}" "$image" --version)" - echo "$output" - grep -q '^openshell-gateway ' <<<"$output" - ;; - supervisor) - output="$(docker run --rm --platform "${{ matrix.platform }}" --entrypoint /openshell-sandbox "$image" --version)" - echo "$output" - grep -q '^openshell-sandbox ' <<<"$output" - ;; - esac - - merge: - name: Merge ${{ inputs.component }} manifest - needs: [resolve, build] - if: ${{ inputs.push && inputs['publish-manifest'] && needs.resolve.outputs.has_image == 'true' }} + docker buildx build \ + --builder openshell \ + --platform ${{ matrix.platform }} \ + --file deploy/docker/Dockerfile.${{ matrix.component }} \ + --target ${{ matrix.component }} \ + --tag ghcr.io/nvidia/openshell/${{ matrix.component }}:${{ github.sha }}-${{ matrix.arch }} \ + --cache-from type=gha,scope=${{ matrix.component }}-${{ matrix.arch }} \ + --cache-to type=gha,mode=max,scope=${{ matrix.component }}-${{ matrix.arch }} \ + --provenance=false \ + --push \ + . + + manifest: + name: ${{ matrix.component }} manifest + needs: build + strategy: + matrix: + component: [gateway, supervisor] runs-on: linux-amd64-cpu8 timeout-minutes: 10 container: @@ -294,33 +114,16 @@ jobs: volumes: - /var/run/docker.sock:/var/run/docker.sock steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ inputs['checkout-ref'] || github.sha }} - - name: Log in to GHCR + shell: bash run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - - name: Create multi-arch manifest + - name: Create manifest + shell: bash run: | - set -euo pipefail - image="ghcr.io/nvidia/openshell/${{ inputs.component }}" - refs=() - for arch in ${{ needs.resolve.outputs.arches }}; do - refs+=("${image}:${{ needs.resolve.outputs.image_tag_base }}-${arch}") - done + image=ghcr.io/nvidia/openshell/${{ matrix.component }} docker buildx imagetools create \ --prefer-index=false \ - -t "${image}:${{ needs.resolve.outputs.image_tag_base }}" \ - "${refs[@]}" - - - name: Verify merged manifest SBOM attestation - env: - IMAGE_REF: ghcr.io/nvidia/openshell/${{ inputs.component }}:${{ needs.resolve.outputs.image_tag_base }} - REQUIRE_CARGO: ${{ inputs.auditable }} - run: | - args=("${IMAGE_REF}") - if [[ "${REQUIRE_CARGO}" == "true" ]]; then - args+=(--require-cargo) - fi - tasks/scripts/verify-image-sbom.sh "${args[@]}" + --tag "$image:${{ github.sha }}" \ + "$image:${{ github.sha }}-amd64" \ + "$image:${{ github.sha }}-arm64" diff --git a/.github/workflows/e2e-gpu-test.yaml b/.github/workflows/e2e-gpu-test.yaml index be2b66e1d4..1a8275a274 100644 --- a/.github/workflows/e2e-gpu-test.yaml +++ b/.github/workflows/e2e-gpu-test.yaml @@ -7,16 +7,6 @@ on: description: "Image tag to test (typically the commit SHA)" required: true type: string - cli-artifact-prefix: - description: "Optional prebuilt CLI artifact prefix (artifact suffix is linux-)" - required: false - type: string - default: "" - gateway-artifact-prefix: - description: "Optional prebuilt gateway artifact prefix (artifact suffix is linux-)" - required: false - type: string - default: "" permissions: actions: read @@ -70,16 +60,10 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Use prebuilt OpenShell CLI - if: inputs.cli-artifact-prefix != '' uses: ./.github/actions/setup-e2e-cli - with: - artifact-prefix: ${{ inputs.cli-artifact-prefix }} - name: Use prebuilt OpenShell gateway - if: inputs.gateway-artifact-prefix != '' uses: ./.github/actions/setup-e2e-gateway - with: - artifact-prefix: ${{ inputs.gateway-artifact-prefix }} - name: Log in to GHCR run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin diff --git a/.github/workflows/e2e-kubernetes-test.yml b/.github/workflows/e2e-kubernetes-test.yml index 3e36570144..6e76da59fc 100644 --- a/.github/workflows/e2e-kubernetes-test.yml +++ b/.github/workflows/e2e-kubernetes-test.yml @@ -47,11 +47,6 @@ on: required: false type: string default: "v2026.4.25" - cli-artifact-prefix: - description: "Optional prebuilt CLI artifact prefix (artifact suffix is linux-)" - required: false - type: string - default: "" permissions: actions: read @@ -81,10 +76,7 @@ jobs: ref: ${{ inputs['checkout-ref'] || github.sha }} - name: Use prebuilt OpenShell CLI - if: inputs.cli-artifact-prefix != '' uses: ./.github/actions/setup-e2e-cli - with: - artifact-prefix: ${{ inputs.cli-artifact-prefix }} - name: Install mise run: | @@ -95,19 +87,6 @@ jobs: - name: Install tools run: mise install --locked - # The openshell-policy crate transitively pulls in z3-sys, whose - # build script needs the z3 C/C++ headers, clang/bindgen, and CMake to - # compile both system-linked and bundled-Z3 builds. The bare runner - # doesn't ship them; the CI container - # image used by other Rust e2e jobs does, but we can't run this job - # there (the runner's container handler injects its own --network - # bridge, which conflicts with the --network host we need so kind's - # API server is reachable from the test process). - - name: Install z3 build deps - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends libz3-dev clang cmake - - name: Log in to GHCR run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index d3e4230636..da374fe522 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -17,21 +17,6 @@ on: required: false type: string default: "" - cli-artifact-prefix: - description: "Optional prebuilt CLI artifact prefix (artifact suffix is linux-)" - required: false - type: string - default: "" - gateway-artifact-prefix: - description: "Optional prebuilt gateway artifact prefix (artifact suffix is linux-)" - required: false - type: string - default: "" - vm-driver-artifact-name: - description: "Optional prebuilt VM driver artifact name" - required: false - type: string - default: "" permissions: actions: read @@ -59,9 +44,8 @@ jobs: - suite: rust-docker cmd: "mise run --no-deps --skip-deps e2e:rust" apt_packages: "openssh-client" - - suite: rust-docker-external-driver - cmd: "env -u OPENSHELL_GATEWAY_BIN mise run --no-deps --skip-deps e2e:docker:external-driver" - apt_packages: "openssh-client" + # External first-party driver lanes stay disabled while their + # standalone binaries are not release artifacts. - suite: mcp cmd: "mise run --no-deps --skip-deps e2e:mcp" apt_packages: "" @@ -89,16 +73,10 @@ jobs: persist-credentials: false - name: Use prebuilt OpenShell CLI - if: inputs.cli-artifact-prefix != '' uses: ./.github/actions/setup-e2e-cli - with: - artifact-prefix: ${{ inputs.cli-artifact-prefix }} - name: Use prebuilt OpenShell gateway - if: inputs.gateway-artifact-prefix != '' uses: ./.github/actions/setup-e2e-gateway - with: - artifact-prefix: ${{ inputs.gateway-artifact-prefix }} - name: Check out MCP conformance tests if: matrix.suite == 'mcp' @@ -129,6 +107,176 @@ 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-${{ matrix.suite }}, ${{ matrix.runner }}) + # Run directly on the Ubuntu host so the test observes the host's AppArmor + # and unprivileged-user-namespace policy. A privileged job container masks + # the restrictions that production rootless Podman installations enforce. + # Ubuntu 26.04 provides the supported Podman 5.x and pasta combination. + # Re-add older/slirp4netns environments when direct callbacks through a + # rootless-network namespace relay are supported. + runs-on: ${{ matrix.runner }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + # Keep package versions explicit so hosted-runner tool overrides + # cannot silently change the supported test environment. + - suite: rootless + runner: ubuntu-26.04 + podman_major: "5" + podman_package_version: "5.7.0+ds2-3build1" + conmon_package_version: "2.1.13+ds1-2" + cmd: "mise run --no-deps --skip-deps e2e:podman:rootless" + # The external-driver suite stays disabled while the standalone + # Podman driver is not a release artifact. + - suite: provider-refresh-keycloak + runner: ubuntu-26.04 + podman_major: "5" + podman_package_version: "5.7.0+ds2-3build1" + conmon_package_version: "2.1.13+ds1-2" + cmd: "mise run --no-deps --skip-deps e2e:provider-refresh-keycloak" + env: + IMAGE_TAG: ${{ inputs.image-tag }} + MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OPENSHELL_REGISTRY: ghcr.io/nvidia/openshell + OPENSHELL_REGISTRY_HOST: ghcr.io + OPENSHELL_REGISTRY_NAMESPACE: nvidia/openshell + OPENSHELL_REGISTRY_USERNAME: ${{ github.actor }} + OPENSHELL_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + OPENSHELL_SUPERVISOR_IMAGE: ${{ format('ghcr.io/nvidia/openshell/supervisor:{0}', inputs.image-tag) }} + 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 + + - name: Use prebuilt OpenShell gateway + uses: ./.github/actions/setup-e2e-gateway + + - 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 Podman and build dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + apparmor \ + build-essential \ + fuse-overlayfs \ + libssl-dev \ + openssh-client \ + passt \ + pkg-config \ + "conmon=${{ matrix.conmon_package_version }}" \ + "podman=${{ matrix.podman_package_version }}" \ + uidmap + # Hosted runners can place newer Podman and conmon binaries under + # /usr/local ahead of Ubuntu's packages. Select the distro CLI and + # use Podman's supported final config override for its conmon path. + podman_config="${RUNNER_TEMP}/openshell-containers.conf" + printf '%s\n' \ + '[engine]' \ + 'conmon_path = ["/usr/bin/conmon"]' \ + > "${podman_config}" + echo "/usr/bin" >> "${GITHUB_PATH}" + echo "CONTAINERS_CONF_OVERRIDE=${podman_config}" >> "${GITHUB_ENV}" + + - name: Allow pasta to receive Podman stop signals + # Ubuntu's packaged pasta profile currently blocks this signal, forcing + # Podman to wait for its SIGKILL fallback. Keep this narrow allowance + # until the distribution package includes the upstream profile fix. + run: | + set -euo pipefail + profile=/etc/apparmor.d/usr.bin.pasta + rule=' signal (receive) peer=podman,' + if ! sudo grep -Fqx "${rule}" "${profile}"; then + sudo sed -i '\|^ include $|a\ signal (receive) peer=podman,' "${profile}" + fi + sudo grep -Fqx "${rule}" "${profile}" + sudo apparmor_parser --replace "${profile}" + + - name: Configure rootless Podman + run: | + set -euo pipefail + if ! grep -q "^${USER}:" /etc/subuid; then + sudo usermod --add-subuids 100000-165535 "$USER" + fi + if ! grep -q "^${USER}:" /etc/subgid; then + sudo usermod --add-subgids 100000-165535 "$USER" + fi + runtime_dir="/run/user/$(id -u)" + sudo install -d -m 0700 -o "$(id -u)" -g "$(id -g)" "$runtime_dir" + echo "XDG_RUNTIME_DIR=$runtime_dir" >> "$GITHUB_ENV" + + - name: Verify rootless Podman environment + run: | + set -euo pipefail + podman_version="$(podman version --format '{{.Client.Version}}')" + case "$podman_version" in + "${{ matrix.podman_major }}".*) ;; + *) echo "ERROR: expected Podman ${{ matrix.podman_major }}.x, found $podman_version" >&2; exit 1 ;; + esac + test "$(dpkg-query -W -f='${Version}' podman)" = "${{ matrix.podman_package_version }}" + test "$(dpkg-query -W -f='${Version}' conmon)" = "${{ matrix.conmon_package_version }}" + test "$(command -v podman)" = "/usr/bin/podman" + test "$(podman info --format '{{.Host.Conmon.Path}}')" = "/usr/bin/conmon" + test "$(podman info --format '{{.Host.Security.Rootless}}')" = "true" + test "$(podman info --format '{{.Host.RootlessNetworkCmd}}')" = "pasta" + test "$(sudo sysctl -n kernel.apparmor_restrict_unprivileged_userns)" = "1" + echo "=== host ===" + uname -a + echo "=== AppArmor ===" + cat /proc/self/attr/current + sudo aa-status || true + echo "=== Podman ===" + podman version + podman info --debug + + - name: Probe rootless capability bounding set + run: | + set -euo pipefail + probe="$RUNNER_TEMP/openshell-capbset-probe" + cc -static -O2 -Wall -Wextra -Werror \ + e2e/support/capbset-probe.c \ + -o "$probe" + podman run --rm \ + --cap-add=SETPCAP \ + --volume "$probe:/openshell-capbset-probe:ro" \ + docker.io/library/alpine:3.22 \ + /openshell-capbset-probe + + - name: Log in to GHCR with Podman + run: echo "${{ secrets.GITHUB_TOKEN }}" | podman login ghcr.io -u "${{ github.actor }}" --password-stdin + + - name: Run Podman E2E + run: ${{ matrix.cmd }} + + - name: Print AppArmor denials + if: always() + run: sudo dmesg | grep -E 'apparmor=.*DENIED|profile="unprivileged_userns"' | tail -100 || true + + - name: Fail on pasta SIGTERM AppArmor denial + if: always() + run: | + set -euo pipefail + denials="$(sudo dmesg | grep -E 'profile="pasta".*requested_mask="receive".*signal=term.*peer="podman"' || true)" + if [ -n "${denials}" ]; then + echo "::error::pasta denied Podman's SIGTERM; Podman will use its SIGKILL fallback" + printf '%s\n' "${denials}" + exit 1 + fi + e2e-vm: name: E2E (rust-vm-${{ matrix.suite }}) # libkrun needs KVM, so this job must run directly on a GitHub-hosted @@ -142,8 +290,8 @@ jobs: include: - suite: managed cmd: "mise run --no-deps --skip-deps e2e:vm" - - suite: external-driver - cmd: "env -u OPENSHELL_GATEWAY_BIN mise run --no-deps --skip-deps e2e:vm:external-driver" + # The unmanaged external-driver suite stays disabled; the released + # VM driver is exercised through the managed gateway path above. env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -154,33 +302,20 @@ jobs: persist-credentials: false - name: Use prebuilt OpenShell CLI - if: inputs.cli-artifact-prefix != '' uses: ./.github/actions/setup-e2e-cli - with: - artifact-prefix: ${{ inputs.cli-artifact-prefix }} - name: Use prebuilt OpenShell gateway - if: inputs.gateway-artifact-prefix != '' uses: ./.github/actions/setup-e2e-gateway - with: - artifact-prefix: ${{ inputs.gateway-artifact-prefix }} - name: Use prebuilt OpenShell VM driver - if: inputs.vm-driver-artifact-name != '' uses: ./.github/actions/setup-e2e-vm-driver - with: - artifact-name: ${{ inputs.vm-driver-artifact-name }} - 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 \ socat \ diff --git a/deploy/docker/Dockerfile.gateway b/deploy/docker/Dockerfile.gateway index 62a55334ba..cf68dc967a 100644 --- a/deploy/docker/Dockerfile.gateway +++ b/deploy/docker/Dockerfile.gateway @@ -3,21 +3,10 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -# Gateway image build. -# -# The Rust binary is built natively before this image build runs and staged at: +# The GNU-linked gateway binary is staged at: # deploy/docker/.build/prebuilt-binaries//openshell-gateway # -# Use tasks/scripts/docker-build-image.sh gateway (or `mise run build:docker:gateway`) -# to stage the binary and build the image in one step. CI builds the binary -# per-architecture via the `rust-native-build.yml` workflow and uploads it as -# an artifact, which is downloaded into the same staging directory before the -# image build job runs. -# -# The runtime is distroless Debian 13, which provides glibc and the dynamic -# loader needed by the GNU-linked gateway binary while keeping the attack -# surface small. The default digest currently carries Debian glibc -# 2.41-12+deb13u3. +# Distroless Debian provides the glibc runtime required by the binary. ARG GATEWAY_BASE_IMAGE=gcr.io/distroless/cc-debian13:nonroot@sha256:d97bc0a941b8d4be647dc0ee75b264ddbb772f1ac5ba690a4309c00723b23775 FROM ${GATEWAY_BASE_IMAGE} AS gateway diff --git a/deploy/docker/Dockerfile.supervisor b/deploy/docker/Dockerfile.supervisor index c77c5c0aff..d515fd70b1 100644 --- a/deploy/docker/Dockerfile.supervisor +++ b/deploy/docker/Dockerfile.supervisor @@ -3,24 +3,10 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -# Supervisor image build. -# -# The final image carries the static `openshell-sandbox` binary used by Docker -# extraction, Podman image volumes, and the Kubernetes init container copy-self -# path. It also includes nftables so the Kubernetes supervisor sidecar can -# install pod-namespace egress enforcement rules. -# -# The Rust binary is built natively before this image build runs and staged at: +# The static sandbox binary is staged at: # deploy/docker/.build/prebuilt-binaries//openshell-sandbox # -# Use tasks/scripts/docker-build-image.sh supervisor (or `mise run build:docker:supervisor`) -# to stage the binary and build the image in one step. CI builds the binary -# per-architecture via the `rust-native-build.yml` workflow and uploads it as an -# artifact, which is downloaded into the same staging directory before the image -# build job runs. -# -# The binary is static under either supported libc variant (`SUPERVISOR_LIBC`: -# musl by default, or glibc-static), so this Alpine base runs it unchanged. +# Alpine supplies nftables and iptables for pod-namespace egress enforcement. FROM alpine:3.22 AS supervisor @@ -28,10 +14,8 @@ ARG TARGETARCH RUN apk add --no-cache nftables iptables iptables-legacy -# --chmod=0555 restores execute bits after the actions/upload-artifact + -# download-artifact roundtrip strips them. Ownership stays root (0:0) for -# Podman image-volume mounts, while world-execute lets the Kubernetes -# network sidecar run this binary as the dedicated non-root proxy UID. +# Keep the binary root-owned for Podman image-volume mounts and executable by +# the Kubernetes network sidecar's non-root proxy UID. COPY --chmod=0555 deploy/docker/.build/prebuilt-binaries/${TARGETARCH}/openshell-sandbox /openshell-sandbox ENTRYPOINT ["/openshell-sandbox"] From ec073a1f0434487ef0b2a65d56b088925dcd7c69 Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 12:29:27 +0200 Subject: [PATCH 04/17] fix(nix): prevent host header leakage Signed-off-by: Simon Scatton --- nix/toolchains/linux-gnu-2.28/default.nix | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nix/toolchains/linux-gnu-2.28/default.nix b/nix/toolchains/linux-gnu-2.28/default.nix index 3e911f1ced..4b8b0ab453 100644 --- a/nix/toolchains/linux-gnu-2.28/default.nix +++ b/nix/toolchains/linux-gnu-2.28/default.nix @@ -31,7 +31,10 @@ let pkgs.overrideCC pkgs.stdenv ( pkgs.wrapCCWith { cc = pkgs.gccNGPackages.gcc-unwrapped.overrideAttrs (old: { - configureFlags = old.configureFlags ++ [ "--disable-fixincludes" ]; + configureFlags = old.configureFlags ++ [ + "--disable-fixincludes" + "--with-native-system-header-dir=/include" + ]; }); bintools = pkgs.wrapBintoolsWith { bintools = pkgs.binutils-unwrapped; From 3b01c8dee0c83b82427906cef6a1afd810cff158 Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 12:29:51 +0200 Subject: [PATCH 05/17] ci: parallelize artifact builds Refs #1683 Signed-off-by: Simon Scatton --- .github/actions/build-docker-image/action.yml | 64 ++++++ .github/workflows/branch-e2e.yml | 184 +++++++++++++++--- .github/workflows/build-binaries.yml | 175 +++-------------- .github/workflows/build-vm-driver.yml | 98 ++++++++++ .github/workflows/docker-build.yml | 83 +++----- 5 files changed, 368 insertions(+), 236 deletions(-) create mode 100644 .github/actions/build-docker-image/action.yml create mode 100644 .github/workflows/build-vm-driver.yml diff --git a/.github/actions/build-docker-image/action.yml b/.github/actions/build-docker-image/action.yml new file mode 100644 index 0000000000..e091ad234b --- /dev/null +++ b/.github/actions/build-docker-image/action.yml @@ -0,0 +1,64 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Build Docker Image +description: Build and push one architecture of an OpenShell image + +inputs: + component: + description: Docker image and Dockerfile name + required: true + binary: + description: Binary staged in the Docker build context + required: true + triple: + description: Binary artifact target triple + required: true + arch: + description: Docker architecture name + required: true + platform: + description: Docker platform + required: true + github-token: + description: Token used to push the image + required: true + +runs: + using: composite + steps: + - uses: ./.github/actions/setup-buildx + with: + buildkitd-config: /etc/buildkit/buildkitd.toml + + - name: Log in to GHCR + shell: bash + run: echo "${{ inputs.github-token }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin + + - name: Download ${{ inputs.binary }} + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ inputs.binary }}-${{ inputs.triple }} + path: artifact + + - name: Stage ${{ inputs.binary }} + shell: bash + run: | + install -d deploy/docker/.build/prebuilt-binaries/${{ inputs.arch }} + tar -xzf artifact/${{ inputs.binary }}-${{ inputs.triple }}.tar.gz -C artifact + install -m 0755 artifact/${{ inputs.binary }} deploy/docker/.build/prebuilt-binaries/${{ inputs.arch }}/${{ inputs.binary }} + + - name: Build ${{ inputs.component }} image + shell: bash + run: | + docker buildx build \ + --builder openshell \ + --platform ${{ inputs.platform }} \ + --file deploy/docker/Dockerfile.${{ inputs.component }} \ + --target ${{ inputs.component }} \ + --tag ghcr.io/nvidia/openshell/${{ inputs.component }}:${{ github.sha }}-${{ inputs.arch }} \ + --cache-from type=gha,scope=${{ inputs.component }}-${{ inputs.arch }} \ + --cache-to type=gha,mode=max,scope=${{ inputs.component }}-${{ inputs.arch }} \ + --provenance=false \ + --push \ + . diff --git a/.github/workflows/branch-e2e.yml b/.github/workflows/branch-e2e.yml index fed4342684..8992f7c3ed 100644 --- a/.github/workflows/branch-e2e.yml +++ b/.github/workflows/branch-e2e.yml @@ -76,25 +76,139 @@ jobs: echo "run_any_e2e=$run_any_e2e" } >> "$GITHUB_OUTPUT" - build-binaries: + version: needs: [pr_metadata] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_any_e2e == 'true' permissions: contents: read + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + cargo: ${{ steps.version.outputs.cargo }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + + - name: Compute version + id: version + run: | + cargo="$(python3 tasks/scripts/release.py get-version --cargo)" + echo "cargo=$cargo" >> "$GITHUB_OUTPUT" + + build-cli: + needs: version + permissions: + contents: read + strategy: + matrix: + include: + - triple: x86_64-unknown-linux-musl + runner: linux-amd64-cpu8 + dev_shell: .#devShells.x86_64-linux.musl + - triple: aarch64-unknown-linux-musl + runner: linux-arm64-cpu8 + dev_shell: .#devShells.aarch64-linux.musl + - triple: aarch64-apple-darwin + runner: macos-15-xlarge + dev_shell: .#devShells.aarch64-darwin.default uses: ./.github/workflows/build-binaries.yml + with: + binary: openshell + triple: ${{ matrix.triple }} + runner: ${{ matrix.runner }} + dev-shell: ${{ matrix.dev_shell }} + cargo-version: ${{ needs.version.outputs.cargo }} secrets: inherit - build-images: - needs: [pr_metadata, build-binaries] + build-sandbox: + needs: version + permissions: + contents: read + strategy: + matrix: + include: + - triple: x86_64-unknown-linux-musl + runner: linux-amd64-cpu8 + dev_shell: .#devShells.x86_64-linux.musl + - triple: aarch64-unknown-linux-musl + runner: linux-arm64-cpu8 + dev_shell: .#devShells.aarch64-linux.musl + uses: ./.github/workflows/build-binaries.yml + with: + binary: openshell-sandbox + triple: ${{ matrix.triple }} + runner: ${{ matrix.runner }} + dev-shell: ${{ matrix.dev_shell }} + cargo-version: ${{ needs.version.outputs.cargo }} + secrets: inherit + + build-gateway: + needs: version + permissions: + contents: read + strategy: + matrix: + include: + - triple: x86_64-unknown-linux-gnu + runner: linux-amd64-cpu8 + dev_shell: .#devShells.x86_64-linux.glibc-2-28 + interpreter: /lib64/ld-linux-x86-64.so.2 + - triple: aarch64-unknown-linux-gnu + runner: linux-arm64-cpu8 + dev_shell: .#devShells.aarch64-linux.glibc-2-28 + interpreter: /lib/ld-linux-aarch64.so.1 + - triple: aarch64-apple-darwin + runner: macos-15-xlarge + dev_shell: .#devShells.aarch64-darwin.default + uses: ./.github/workflows/build-binaries.yml + with: + binary: openshell-gateway + triple: ${{ matrix.triple }} + runner: ${{ matrix.runner }} + dev-shell: ${{ matrix.dev_shell }} + cargo-version: ${{ needs.version.outputs.cargo }} + interpreter: ${{ matrix.interpreter }} + secrets: inherit + + build-vm-driver: + needs: [pr_metadata, version, build-sandbox] + if: needs.pr_metadata.outputs.run_core_e2e == 'true' + permissions: + contents: read + uses: ./.github/workflows/build-vm-driver.yml + with: + cargo-version: ${{ needs.version.outputs.cargo }} + secrets: inherit + + build-gateway-image: + needs: [pr_metadata, build-gateway] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_any_e2e == 'true' permissions: contents: read packages: write uses: ./.github/workflows/docker-build.yml + with: + component: gateway + binary: openshell-gateway + target-suffix: unknown-linux-gnu + secrets: inherit + + build-supervisor-image: + needs: [pr_metadata, build-sandbox] + if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_any_e2e == 'true' + permissions: + contents: read + packages: write + uses: ./.github/workflows/docker-build.yml + with: + component: supervisor + binary: openshell-sandbox + target-suffix: unknown-linux-musl secrets: inherit e2e: - needs: [pr_metadata, build-binaries, build-images] + needs: [pr_metadata, build-cli, build-vm-driver, build-gateway-image, build-supervisor-image] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' permissions: actions: read @@ -106,7 +220,7 @@ jobs: runner: linux-arm64-cpu8 gpu-e2e: - needs: [pr_metadata, build-binaries, build-images] + needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_gpu_e2e == 'true' permissions: actions: read @@ -117,7 +231,7 @@ jobs: image-tag: ${{ github.sha }} kubernetes-e2e: - needs: [pr_metadata, build-binaries, build-images] + needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' strategy: fail-fast: false @@ -147,7 +261,7 @@ jobs: extra-helm-values: ${{ matrix.extra_helm_values }} kubernetes-workspace-managed-e2e: - needs: [pr_metadata, build-binaries, build-images] + needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' permissions: actions: read @@ -163,7 +277,7 @@ jobs: # is not a release artifact. kubernetes-workspace-operator-e2e: - needs: [pr_metadata, build-binaries, build-images] + needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' permissions: actions: read @@ -176,7 +290,7 @@ jobs: e2e-task: e2e:kubernetes:workspace-operator kubernetes-ha-e2e: - needs: [pr_metadata, build-binaries, build-images] + needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_kubernetes_ha_e2e == 'true' permissions: actions: read @@ -190,7 +304,7 @@ jobs: external-postgres-secret: openshell-ha-pg kubernetes-credential-drivers-e2e: - needs: [pr_metadata, build-binaries, build-images] + needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_kubernetes_credential_drivers_e2e == 'true' permissions: actions: read @@ -204,14 +318,16 @@ jobs: core-e2e-result: name: Core E2E result - needs: [pr_metadata, build-binaries, build-images, e2e, kubernetes-e2e, kubernetes-workspace-managed-e2e, kubernetes-workspace-operator-e2e] + needs: [pr_metadata, build-cli, build-vm-driver, build-gateway-image, build-supervisor-image, e2e, kubernetes-e2e, kubernetes-workspace-managed-e2e, kubernetes-workspace-operator-e2e] if: always() && needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' runs-on: ubuntu-latest steps: - name: Verify core E2E jobs env: - BUILD_BINARIES_RESULT: ${{ needs.build-binaries.result }} - BUILD_IMAGES_RESULT: ${{ needs.build-images.result }} + BUILD_CLI_RESULT: ${{ needs.build-cli.result }} + BUILD_VM_DRIVER_RESULT: ${{ needs.build-vm-driver.result }} + BUILD_GATEWAY_IMAGE_RESULT: ${{ needs.build-gateway-image.result }} + BUILD_SUPERVISOR_IMAGE_RESULT: ${{ needs.build-supervisor-image.result }} E2E_RESULT: ${{ needs.e2e.result }} KUBERNETES_E2E_RESULT: ${{ needs.kubernetes-e2e.result }} KUBERNETES_WORKSPACE_MANAGED_E2E_RESULT: ${{ needs.kubernetes-workspace-managed-e2e.result }} @@ -220,8 +336,10 @@ jobs: set -euo pipefail failed=0 for item in \ - "build-binaries:$BUILD_BINARIES_RESULT" \ - "build-images:$BUILD_IMAGES_RESULT" \ + "build-cli:$BUILD_CLI_RESULT" \ + "build-vm-driver:$BUILD_VM_DRIVER_RESULT" \ + "build-gateway-image:$BUILD_GATEWAY_IMAGE_RESULT" \ + "build-supervisor-image:$BUILD_SUPERVISOR_IMAGE_RESULT" \ "e2e:$E2E_RESULT" \ "kubernetes-e2e:$KUBERNETES_E2E_RESULT" \ "kubernetes-workspace-managed-e2e:$KUBERNETES_WORKSPACE_MANAGED_E2E_RESULT" \ @@ -237,21 +355,23 @@ jobs: gpu-e2e-result: name: GPU E2E result - needs: [pr_metadata, build-binaries, build-images, gpu-e2e] + needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image, gpu-e2e] if: always() && needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_gpu_e2e == 'true' runs-on: ubuntu-latest steps: - name: Verify GPU E2E jobs env: - BUILD_BINARIES_RESULT: ${{ needs.build-binaries.result }} - BUILD_IMAGES_RESULT: ${{ needs.build-images.result }} + BUILD_CLI_RESULT: ${{ needs.build-cli.result }} + BUILD_GATEWAY_IMAGE_RESULT: ${{ needs.build-gateway-image.result }} + BUILD_SUPERVISOR_IMAGE_RESULT: ${{ needs.build-supervisor-image.result }} GPU_E2E_RESULT: ${{ needs.gpu-e2e.result }} run: | set -euo pipefail failed=0 for item in \ - "build-binaries:$BUILD_BINARIES_RESULT" \ - "build-images:$BUILD_IMAGES_RESULT" \ + "build-cli:$BUILD_CLI_RESULT" \ + "build-gateway-image:$BUILD_GATEWAY_IMAGE_RESULT" \ + "build-supervisor-image:$BUILD_SUPERVISOR_IMAGE_RESULT" \ "gpu-e2e:$GPU_E2E_RESULT"; do name="${item%%:*}" result="${item#*:}" @@ -264,21 +384,23 @@ jobs: kubernetes-ha-e2e-result: name: Kubernetes HA E2E result - needs: [pr_metadata, build-binaries, build-images, kubernetes-ha-e2e] + needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image, kubernetes-ha-e2e] if: always() && needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_kubernetes_ha_e2e == 'true' runs-on: ubuntu-latest steps: - name: Verify Kubernetes HA E2E jobs env: - BUILD_BINARIES_RESULT: ${{ needs.build-binaries.result }} - BUILD_IMAGES_RESULT: ${{ needs.build-images.result }} + BUILD_CLI_RESULT: ${{ needs.build-cli.result }} + BUILD_GATEWAY_IMAGE_RESULT: ${{ needs.build-gateway-image.result }} + BUILD_SUPERVISOR_IMAGE_RESULT: ${{ needs.build-supervisor-image.result }} KUBERNETES_HA_E2E_RESULT: ${{ needs.kubernetes-ha-e2e.result }} run: | set -euo pipefail failed=0 for item in \ - "build-binaries:$BUILD_BINARIES_RESULT" \ - "build-images:$BUILD_IMAGES_RESULT" \ + "build-cli:$BUILD_CLI_RESULT" \ + "build-gateway-image:$BUILD_GATEWAY_IMAGE_RESULT" \ + "build-supervisor-image:$BUILD_SUPERVISOR_IMAGE_RESULT" \ "kubernetes-ha-e2e:$KUBERNETES_HA_E2E_RESULT"; do name="${item%%:*}" result="${item#*:}" @@ -291,21 +413,23 @@ jobs: kubernetes-credential-drivers-e2e-result: name: Kubernetes Credential Drivers E2E result - needs: [pr_metadata, build-binaries, build-images, kubernetes-credential-drivers-e2e] + needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image, kubernetes-credential-drivers-e2e] if: always() && needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_kubernetes_credential_drivers_e2e == 'true' runs-on: ubuntu-latest steps: - name: Verify Kubernetes credential drivers E2E jobs env: - BUILD_BINARIES_RESULT: ${{ needs.build-binaries.result }} - BUILD_IMAGES_RESULT: ${{ needs.build-images.result }} + BUILD_CLI_RESULT: ${{ needs.build-cli.result }} + BUILD_GATEWAY_IMAGE_RESULT: ${{ needs.build-gateway-image.result }} + BUILD_SUPERVISOR_IMAGE_RESULT: ${{ needs.build-supervisor-image.result }} KUBERNETES_CREDENTIAL_DRIVERS_E2E_RESULT: ${{ needs.kubernetes-credential-drivers-e2e.result }} run: | set -euo pipefail failed=0 for item in \ - "build-binaries:$BUILD_BINARIES_RESULT" \ - "build-images:$BUILD_IMAGES_RESULT" \ + "build-cli:$BUILD_CLI_RESULT" \ + "build-gateway-image:$BUILD_GATEWAY_IMAGE_RESULT" \ + "build-supervisor-image:$BUILD_SUPERVISOR_IMAGE_RESULT" \ "kubernetes-credential-drivers-e2e:$KUBERNETES_CREDENTIAL_DRIVERS_E2E_RESULT"; do name="${item%%:*}" result="${item#*:}" diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index f23c293c86..7a70942a0f 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -1,11 +1,30 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -name: Build Binaries +name: Build Binary on: workflow_call: inputs: + binary: + required: true + type: string + triple: + required: true + type: string + runner: + required: true + type: string + dev-shell: + required: true + type: string + cargo-version: + required: true + type: string + interpreter: + required: false + type: string + default: "" checkout-ref: required: false type: string @@ -13,72 +32,14 @@ on: secrets: CACHIX_AUTH_TOKEN: required: true - workflow_dispatch: permissions: contents: read jobs: - version: - name: Version - runs-on: ubuntu-latest - timeout-minutes: 5 - outputs: - cargo: ${{ steps.version.outputs.cargo }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ inputs['checkout-ref'] || github.sha }} - fetch-depth: 0 - - - name: Compute version - id: version - shell: bash - run: | - cargo="$(python3 tasks/scripts/release.py get-version --cargo)" - echo "cargo=$cargo" >> "$GITHUB_OUTPUT" - - rust-binary: - name: ${{ matrix.binary }} (${{ matrix.triple }}) - needs: version - strategy: - matrix: - include: - - binary: openshell - triple: x86_64-unknown-linux-musl - runner: linux-amd64-cpu8 - dev_shell: .#devShells.x86_64-linux.musl - - binary: openshell - triple: aarch64-unknown-linux-musl - runner: linux-arm64-cpu8 - dev_shell: .#devShells.aarch64-linux.musl - - binary: openshell - triple: aarch64-apple-darwin - runner: macos-15-xlarge - dev_shell: .#devShells.aarch64-darwin.default - - binary: openshell-sandbox - triple: x86_64-unknown-linux-musl - runner: linux-amd64-cpu8 - dev_shell: .#devShells.x86_64-linux.musl - - binary: openshell-sandbox - triple: aarch64-unknown-linux-musl - runner: linux-arm64-cpu8 - dev_shell: .#devShells.aarch64-linux.musl - - binary: openshell-gateway - triple: x86_64-unknown-linux-gnu - runner: linux-amd64-cpu8 - dev_shell: .#devShells.x86_64-linux.glibc-2-28 - interpreter: /lib64/ld-linux-x86-64.so.2 - - binary: openshell-gateway - triple: aarch64-unknown-linux-gnu - runner: linux-arm64-cpu8 - dev_shell: .#devShells.aarch64-linux.glibc-2-28 - interpreter: /lib/ld-linux-aarch64.so.1 - - binary: openshell-gateway - triple: aarch64-apple-darwin - runner: macos-15-xlarge - dev_shell: .#devShells.aarch64-darwin.default - runs-on: ${{ matrix.runner }} + build: + name: ${{ inputs.binary }} (${{ inputs.triple }}) + runs-on: ${{ inputs.runner }} timeout-minutes: 60 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -89,88 +50,10 @@ jobs: with: cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - - name: Build ${{ matrix.binary }} - uses: ./.github/actions/build-rust-binary - with: - binary: ${{ matrix.binary }} - triple: ${{ matrix.triple }} - dev-shell: ${{ matrix.dev_shell }} - cargo-version: ${{ needs.version.outputs.cargo }} - interpreter: ${{ matrix.interpreter }} - - vm-driver: - name: openshell-driver-vm (${{ matrix.triple }}) - needs: [version, rust-binary] - strategy: - matrix: - include: - - arch: x86_64 - triple: x86_64-unknown-linux-gnu - runner: linux-amd64-cpu8 - dev_shell: .#devShells.x86_64-linux.glibc-2-28 - interpreter: /lib64/ld-linux-x86-64.so.2 - - arch: aarch64 - triple: aarch64-unknown-linux-gnu - runner: linux-arm64-cpu8 - dev_shell: .#devShells.aarch64-linux.glibc-2-28 - interpreter: /lib/ld-linux-aarch64.so.1 - - arch: aarch64 - triple: aarch64-apple-darwin - runner: macos-15-xlarge - dev_shell: .#devShells.aarch64-darwin.default - runs-on: ${{ matrix.runner }} - timeout-minutes: 60 - defaults: - run: - shell: nix develop ${{ matrix.dev_shell }} -c bash -euo pipefail {0} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ inputs['checkout-ref'] || github.sha }} - - - uses: ./.github/actions/setup-nix - with: - cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} - - - name: Download openshell-sandbox - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: openshell-sandbox-${{ matrix.arch }}-unknown-linux-musl - path: sandbox - - - name: Build VM runtime - run: nix build .#vm-runtime - - - name: Compress Linux VM runtime - if: runner.os == 'Linux' - run: | - install -d target/vm-runtime-compressed - zstd -19 -T0 result/libkrun.so -o target/vm-runtime-compressed/libkrun.so.zst - zstd -19 -T0 result/libkrunfw.so.5 -o target/vm-runtime-compressed/libkrunfw.so.5.zst - zstd -19 -T0 result/gvproxy -o target/vm-runtime-compressed/gvproxy.zst - zstd -19 -T0 result/umoci -o target/vm-runtime-compressed/umoci.zst - - - name: Compress macOS VM runtime - if: runner.os == 'macOS' - run: | - install -d target/vm-runtime-compressed - zstd -19 -T0 result/libkrun.dylib -o target/vm-runtime-compressed/libkrun.dylib.zst - zstd -19 -T0 result/libkrunfw.5.dylib -o target/vm-runtime-compressed/libkrunfw.5.dylib.zst - zstd -19 -T0 result/gvproxy -o target/vm-runtime-compressed/gvproxy.zst - zstd -19 -T0 result/umoci -o target/vm-runtime-compressed/umoci.zst - - - name: Add openshell-sandbox to VM runtime - run: | - tar -xzf sandbox/openshell-sandbox-${{ matrix.arch }}-unknown-linux-musl.tar.gz -C sandbox - zstd -19 -T0 sandbox/openshell-sandbox -o target/vm-runtime-compressed/openshell-sandbox.zst - - - name: Build openshell-driver-vm - uses: ./.github/actions/build-rust-binary - env: - OPENSHELL_VM_RUNTIME_COMPRESSED_DIR: ${{ github.workspace }}/target/vm-runtime-compressed + - uses: ./.github/actions/build-rust-binary with: - binary: openshell-driver-vm - triple: ${{ matrix.triple }} - dev-shell: ${{ matrix.dev_shell }} - cargo-version: ${{ needs.version.outputs.cargo }} - interpreter: ${{ matrix.interpreter }} + binary: ${{ inputs.binary }} + triple: ${{ inputs.triple }} + dev-shell: ${{ inputs['dev-shell'] }} + cargo-version: ${{ inputs['cargo-version'] }} + interpreter: ${{ inputs.interpreter }} diff --git a/.github/workflows/build-vm-driver.yml b/.github/workflows/build-vm-driver.yml new file mode 100644 index 0000000000..5498c53f0b --- /dev/null +++ b/.github/workflows/build-vm-driver.yml @@ -0,0 +1,98 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Build VM Driver + +on: + workflow_call: + inputs: + cargo-version: + required: true + type: string + checkout-ref: + required: false + type: string + default: "" + secrets: + CACHIX_AUTH_TOKEN: + required: true + +permissions: + contents: read + +jobs: + build: + name: openshell-driver-vm (${{ matrix.triple }}) + strategy: + matrix: + include: + - arch: x86_64 + triple: x86_64-unknown-linux-gnu + runner: linux-amd64-cpu8 + dev_shell: .#devShells.x86_64-linux.glibc-2-28 + interpreter: /lib64/ld-linux-x86-64.so.2 + - arch: aarch64 + triple: aarch64-unknown-linux-gnu + runner: linux-arm64-cpu8 + dev_shell: .#devShells.aarch64-linux.glibc-2-28 + interpreter: /lib/ld-linux-aarch64.so.1 + - arch: aarch64 + triple: aarch64-apple-darwin + runner: macos-15-xlarge + dev_shell: .#devShells.aarch64-darwin.default + runs-on: ${{ matrix.runner }} + timeout-minutes: 60 + defaults: + run: + shell: nix develop ${{ matrix.dev_shell }} -c bash -euo pipefail {0} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs['checkout-ref'] || github.sha }} + + - uses: ./.github/actions/setup-nix + with: + cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }} + + - name: Download openshell-sandbox + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: openshell-sandbox-${{ matrix.arch }}-unknown-linux-musl + path: sandbox + + - name: Build VM runtime + run: nix build .#vm-runtime + + - name: Compress Linux VM runtime + if: runner.os == 'Linux' + run: | + install -d target/vm-runtime-compressed + zstd -19 -T0 result/libkrun.so -o target/vm-runtime-compressed/libkrun.so.zst + zstd -19 -T0 result/libkrunfw.so.5 -o target/vm-runtime-compressed/libkrunfw.so.5.zst + zstd -19 -T0 result/gvproxy -o target/vm-runtime-compressed/gvproxy.zst + zstd -19 -T0 result/umoci -o target/vm-runtime-compressed/umoci.zst + + - name: Compress macOS VM runtime + if: runner.os == 'macOS' + run: | + install -d target/vm-runtime-compressed + zstd -19 -T0 result/libkrun.dylib -o target/vm-runtime-compressed/libkrun.dylib.zst + zstd -19 -T0 result/libkrunfw.5.dylib -o target/vm-runtime-compressed/libkrunfw.5.dylib.zst + zstd -19 -T0 result/gvproxy -o target/vm-runtime-compressed/gvproxy.zst + zstd -19 -T0 result/umoci -o target/vm-runtime-compressed/umoci.zst + + - name: Add openshell-sandbox to VM runtime + run: | + tar -xzf sandbox/openshell-sandbox-${{ matrix.arch }}-unknown-linux-musl.tar.gz -C sandbox + zstd -19 -T0 sandbox/openshell-sandbox -o target/vm-runtime-compressed/openshell-sandbox.zst + + - name: Build openshell-driver-vm + uses: ./.github/actions/build-rust-binary + env: + OPENSHELL_VM_RUNTIME_COMPRESSED_DIR: ${{ github.workspace }}/target/vm-runtime-compressed + with: + binary: openshell-driver-vm + triple: ${{ matrix.triple }} + dev-shell: ${{ matrix.dev_shell }} + cargo-version: ${{ inputs['cargo-version'] }} + interpreter: ${{ matrix.interpreter }} diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index e77fae85f1..b663598969 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -6,6 +6,15 @@ name: Docker Build on: workflow_call: inputs: + component: + required: true + type: string + binary: + required: true + type: string + target-suffix: + required: true + type: string checkout-ref: required: false type: string @@ -17,33 +26,17 @@ permissions: jobs: build: - name: ${{ matrix.component }} (${{ matrix.platform }}) + name: ${{ inputs.component }} (${{ matrix.platform }}) strategy: fail-fast: false matrix: include: - - component: gateway - binary: openshell-gateway - triple: x86_64-unknown-linux-gnu - arch: amd64 + - arch: amd64 + rust_arch: x86_64 platform: linux/amd64 runner: linux-amd64-cpu8 - - component: gateway - binary: openshell-gateway - triple: aarch64-unknown-linux-gnu - arch: arm64 - platform: linux/arm64 - runner: linux-arm64-cpu8 - - component: supervisor - binary: openshell-sandbox - triple: x86_64-unknown-linux-musl - arch: amd64 - platform: linux/amd64 - runner: linux-amd64-cpu8 - - component: supervisor - binary: openshell-sandbox - triple: aarch64-unknown-linux-musl - arch: arm64 + - arch: arm64 + rust_arch: aarch64 platform: linux/arm64 runner: linux-arm64-cpu8 runs-on: ${{ matrix.runner }} @@ -62,48 +55,18 @@ jobs: with: ref: ${{ inputs['checkout-ref'] || github.sha }} - - uses: ./.github/actions/setup-buildx + - uses: ./.github/actions/build-docker-image with: - buildkitd-config: /etc/buildkit/buildkitd.toml - - - name: Log in to GHCR - shell: bash - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: Download ${{ matrix.binary }} - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: ${{ matrix.binary }}-${{ matrix.triple }} - path: artifact - - - name: Stage ${{ matrix.binary }} - shell: bash - run: | - install -d deploy/docker/.build/prebuilt-binaries/${{ matrix.arch }} - tar -xzf artifact/${{ matrix.binary }}-${{ matrix.triple }}.tar.gz -C artifact - install -m 0755 artifact/${{ matrix.binary }} deploy/docker/.build/prebuilt-binaries/${{ matrix.arch }}/${{ matrix.binary }} - - - name: Build ${{ matrix.component }} image - shell: bash - run: | - docker buildx build \ - --builder openshell \ - --platform ${{ matrix.platform }} \ - --file deploy/docker/Dockerfile.${{ matrix.component }} \ - --target ${{ matrix.component }} \ - --tag ghcr.io/nvidia/openshell/${{ matrix.component }}:${{ github.sha }}-${{ matrix.arch }} \ - --cache-from type=gha,scope=${{ matrix.component }}-${{ matrix.arch }} \ - --cache-to type=gha,mode=max,scope=${{ matrix.component }}-${{ matrix.arch }} \ - --provenance=false \ - --push \ - . + component: ${{ inputs.component }} + binary: ${{ inputs.binary }} + triple: ${{ matrix.rust_arch }}-${{ inputs['target-suffix'] }} + arch: ${{ matrix.arch }} + platform: ${{ matrix.platform }} + github-token: ${{ secrets.GITHUB_TOKEN }} manifest: - name: ${{ matrix.component }} manifest + name: ${{ inputs.component }} manifest needs: build - strategy: - matrix: - component: [gateway, supervisor] runs-on: linux-amd64-cpu8 timeout-minutes: 10 container: @@ -121,7 +84,7 @@ jobs: - name: Create manifest shell: bash run: | - image=ghcr.io/nvidia/openshell/${{ matrix.component }} + image=ghcr.io/nvidia/openshell/${{ inputs.component }} docker buildx imagetools create \ --prefer-index=false \ --tag "$image:${{ github.sha }}" \ From 8e5adb19469efc0cca4731d2f06446275a494b90 Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 13:56:25 +0200 Subject: [PATCH 06/17] ci: build external driver test artifacts Refs #1683 Signed-off-by: Simon Scatton --- .github/actions/build-docker-image/action.yml | 5 +- .github/actions/build-rust-binary/action.yml | 21 +++--- .github/actions/setup-e2e-cli/action.yml | 1 - .github/actions/setup-e2e-gateway/action.yml | 1 - .../actions/setup-e2e-vm-driver/action.yml | 1 - .github/workflows/branch-e2e.yml | 73 +++++++++++++++++++ .github/workflows/build-binaries.yml | 10 +++ .github/workflows/build-vm-driver.yml | 4 +- 8 files changed, 97 insertions(+), 19 deletions(-) diff --git a/.github/actions/build-docker-image/action.yml b/.github/actions/build-docker-image/action.yml index e091ad234b..e2239f297e 100644 --- a/.github/actions/build-docker-image/action.yml +++ b/.github/actions/build-docker-image/action.yml @@ -43,10 +43,7 @@ runs: - name: Stage ${{ inputs.binary }} shell: bash - run: | - install -d deploy/docker/.build/prebuilt-binaries/${{ inputs.arch }} - tar -xzf artifact/${{ inputs.binary }}-${{ inputs.triple }}.tar.gz -C artifact - install -m 0755 artifact/${{ inputs.binary }} deploy/docker/.build/prebuilt-binaries/${{ inputs.arch }}/${{ inputs.binary }} + run: install -Dm0755 artifact/${{ inputs.binary }} deploy/docker/.build/prebuilt-binaries/${{ inputs.arch }}/${{ inputs.binary }} - name: Build ${{ inputs.component }} image shell: bash diff --git a/.github/actions/build-rust-binary/action.yml b/.github/actions/build-rust-binary/action.yml index 616b0b14c5..88aa61a3ef 100644 --- a/.github/actions/build-rust-binary/action.yml +++ b/.github/actions/build-rust-binary/action.yml @@ -17,6 +17,14 @@ inputs: cargo-version: description: Cargo package version embedded in the binary required: true + artifact-name: + description: GitHub artifact name + required: false + default: "" + extra-cargo-flags: + description: Additional flags passed to cargo build + required: false + default: "" interpreter: description: ELF interpreter for a dynamically linked Linux binary required: false @@ -40,7 +48,7 @@ runs: - name: Build ${{ inputs.binary }} shell: nix develop ${{ inputs.dev-shell }} -c bash -euo pipefail {0} - run: GIT_DIR=/nonexistent cargo auditable build --release --bin "${{ inputs.binary }}" + run: GIT_DIR=/nonexistent cargo auditable build --release --bin "${{ inputs.binary }}" ${{ inputs.extra-cargo-flags }} - name: Verify ${{ inputs.binary }} shell: nix develop ${{ inputs.dev-shell }} -c bash -euo pipefail {0} @@ -67,16 +75,11 @@ runs: # Reject symbols introduced after glibc 2.28. tasks/scripts/verify-glibc-symbols.sh 2.28 "$binary" - - name: Package ${{ inputs.binary }} - shell: nix develop ${{ inputs.dev-shell }} -c bash -euo pipefail {0} - run: | - install -Dm0755 target/release/${{ inputs.binary }} artifacts/${{ inputs.binary }} - tar -czf artifacts/${{ inputs.binary }}-${{ inputs.triple }}.tar.gz -C artifacts ${{ inputs.binary }} - - name: Upload ${{ inputs.binary }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: ${{ inputs.binary }}-${{ inputs.triple }} - path: artifacts/*.tar.gz + name: ${{ inputs.artifact-name || format('{0}-{1}', inputs.binary, inputs.triple) }} + path: target/release/${{ inputs.binary }} + compression-level: 0 retention-days: 5 if-no-files-found: error diff --git a/.github/actions/setup-e2e-cli/action.yml b/.github/actions/setup-e2e-cli/action.yml index f41c883f99..4d43e232a4 100644 --- a/.github/actions/setup-e2e-cli/action.yml +++ b/.github/actions/setup-e2e-cli/action.yml @@ -13,7 +13,6 @@ runs: - name: Configure prebuilt CLI shell: bash run: | - tar -xzf "$GITHUB_WORKSPACE"/.e2e/prebuilt-cli/*.tar.gz -C "$GITHUB_WORKSPACE/.e2e/prebuilt-cli" cli="$GITHUB_WORKSPACE/.e2e/prebuilt-cli/openshell" chmod +x "$cli" "$cli" --version diff --git a/.github/actions/setup-e2e-gateway/action.yml b/.github/actions/setup-e2e-gateway/action.yml index f77c338235..d146cff70c 100644 --- a/.github/actions/setup-e2e-gateway/action.yml +++ b/.github/actions/setup-e2e-gateway/action.yml @@ -13,7 +13,6 @@ runs: - name: Configure prebuilt gateway shell: bash run: | - tar -xzf "$GITHUB_WORKSPACE"/.e2e/prebuilt-gateway/*.tar.gz -C "$GITHUB_WORKSPACE/.e2e/prebuilt-gateway" gateway="$GITHUB_WORKSPACE/.e2e/prebuilt-gateway/openshell-gateway" chmod +x "$gateway" "$gateway" --version diff --git a/.github/actions/setup-e2e-vm-driver/action.yml b/.github/actions/setup-e2e-vm-driver/action.yml index 191f001dec..a074ed97a8 100644 --- a/.github/actions/setup-e2e-vm-driver/action.yml +++ b/.github/actions/setup-e2e-vm-driver/action.yml @@ -13,7 +13,6 @@ runs: - name: Configure prebuilt VM driver shell: bash run: | - tar -xzf "$GITHUB_WORKSPACE"/.e2e/prebuilt-vm-driver/*.tar.gz -C "$GITHUB_WORKSPACE/.e2e/prebuilt-vm-driver" driver="$GITHUB_WORKSPACE/.e2e/prebuilt-vm-driver/openshell-driver-vm" chmod +x "$driver" "$driver" --version diff --git a/.github/workflows/branch-e2e.yml b/.github/workflows/branch-e2e.yml index 8992f7c3ed..65caad4b5c 100644 --- a/.github/workflows/branch-e2e.yml +++ b/.github/workflows/branch-e2e.yml @@ -171,6 +171,79 @@ jobs: interpreter: ${{ matrix.interpreter }} secrets: inherit + build-gateway-plain: + needs: [pr_metadata, version] + if: needs.pr_metadata.outputs.run_core_e2e == 'true' + permissions: + contents: read + strategy: + matrix: + include: + - triple: x86_64-unknown-linux-gnu + runner: linux-amd64-cpu8 + dev_shell: .#devShells.x86_64-linux.glibc-2-28 + interpreter: /lib64/ld-linux-x86-64.so.2 + - triple: aarch64-unknown-linux-gnu + runner: linux-arm64-cpu8 + dev_shell: .#devShells.aarch64-linux.glibc-2-28 + interpreter: /lib/ld-linux-aarch64.so.1 + uses: ./.github/workflows/build-binaries.yml + with: + binary: openshell-gateway + artifact-name: openshell-gateway-plain-${{ matrix.triple }} + triple: ${{ matrix.triple }} + runner: ${{ matrix.runner }} + dev-shell: ${{ matrix.dev_shell }} + cargo-version: ${{ needs.version.outputs.cargo }} + extra-cargo-flags: --no-default-features + interpreter: ${{ matrix.interpreter }} + secrets: inherit + + build-driver-docker: + needs: [pr_metadata, version] + if: needs.pr_metadata.outputs.run_core_e2e == 'true' + permissions: + contents: read + uses: ./.github/workflows/build-binaries.yml + with: + binary: openshell-driver-docker + triple: aarch64-unknown-linux-gnu + runner: linux-arm64-cpu8 + dev-shell: .#devShells.aarch64-linux.glibc-2-28 + cargo-version: ${{ needs.version.outputs.cargo }} + interpreter: /lib/ld-linux-aarch64.so.1 + secrets: inherit + + build-driver-podman: + needs: [pr_metadata, version] + if: needs.pr_metadata.outputs.run_core_e2e == 'true' + permissions: + contents: read + uses: ./.github/workflows/build-binaries.yml + with: + binary: openshell-driver-podman + triple: x86_64-unknown-linux-gnu + runner: linux-amd64-cpu8 + dev-shell: .#devShells.x86_64-linux.glibc-2-28 + cargo-version: ${{ needs.version.outputs.cargo }} + interpreter: /lib64/ld-linux-x86-64.so.2 + secrets: inherit + + build-driver-kubernetes: + needs: [pr_metadata, version] + if: needs.pr_metadata.outputs.run_core_e2e == 'true' + permissions: + contents: read + uses: ./.github/workflows/build-binaries.yml + with: + binary: openshell-driver-kubernetes + triple: x86_64-unknown-linux-gnu + runner: linux-amd64-cpu8 + dev-shell: .#devShells.x86_64-linux.glibc-2-28 + cargo-version: ${{ needs.version.outputs.cargo }} + interpreter: /lib64/ld-linux-x86-64.so.2 + secrets: inherit + build-vm-driver: needs: [pr_metadata, version, build-sandbox] if: needs.pr_metadata.outputs.run_core_e2e == 'true' diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 7a70942a0f..cd841a6349 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -21,6 +21,14 @@ on: cargo-version: required: true type: string + artifact-name: + required: false + type: string + default: "" + extra-cargo-flags: + required: false + type: string + default: "" interpreter: required: false type: string @@ -56,4 +64,6 @@ jobs: triple: ${{ inputs.triple }} dev-shell: ${{ inputs['dev-shell'] }} cargo-version: ${{ inputs['cargo-version'] }} + artifact-name: ${{ inputs['artifact-name'] }} + extra-cargo-flags: ${{ inputs['extra-cargo-flags'] }} interpreter: ${{ inputs.interpreter }} diff --git a/.github/workflows/build-vm-driver.yml b/.github/workflows/build-vm-driver.yml index 5498c53f0b..3e7eed2fcf 100644 --- a/.github/workflows/build-vm-driver.yml +++ b/.github/workflows/build-vm-driver.yml @@ -82,9 +82,7 @@ jobs: zstd -19 -T0 result/umoci -o target/vm-runtime-compressed/umoci.zst - name: Add openshell-sandbox to VM runtime - run: | - tar -xzf sandbox/openshell-sandbox-${{ matrix.arch }}-unknown-linux-musl.tar.gz -C sandbox - zstd -19 -T0 sandbox/openshell-sandbox -o target/vm-runtime-compressed/openshell-sandbox.zst + run: zstd -19 -T0 sandbox/openshell-sandbox -o target/vm-runtime-compressed/openshell-sandbox.zst - name: Build openshell-driver-vm uses: ./.github/actions/build-rust-binary From 7abab277370f64cca23877c61de404d4d4289746 Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 14:25:10 +0200 Subject: [PATCH 07/17] fix(nix): disable mold in musl shells Signed-off-by: Simon Scatton --- nix/devShells/musl.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/devShells/musl.nix b/nix/devShells/musl.nix index ba7c06bf22..d0dbda93c1 100644 --- a/nix/devShells/musl.nix +++ b/nix/devShells/musl.nix @@ -9,7 +9,7 @@ let muslPkgs = pkgs.pkgsMusl; - stdenv = pkgs.stdenvAdapters.useMoldLinker muslPkgs.stdenv; + stdenv = muslPkgs.stdenv; rust-bin = rust-overlay.lib.mkRustBin { } muslPkgs; rustToolchain = (rust-bin.fromRustupToolchainFile ../../rust-toolchain.toml).override { enableLibsecret = false; From 29cbd809518805be05eb59adfd9ff21439df2a1d Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 14:38:34 +0200 Subject: [PATCH 08/17] ci: key Rust cache by Nix shell derivation Signed-off-by: Simon Scatton --- .github/actions/build-rust-binary/action.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/actions/build-rust-binary/action.yml b/.github/actions/build-rust-binary/action.yml index 88aa61a3ef..dd1a66c19e 100644 --- a/.github/actions/build-rust-binary/action.yml +++ b/.github/actions/build-rust-binary/action.yml @@ -33,10 +33,17 @@ inputs: runs: using: composite steps: + - name: Hash development shell + id: dev-shell + shell: bash + env: + DEV_SHELL: ${{ inputs.dev-shell }} + run: echo "hash=$(nix hash file --type sha256 --base16 "$(nix eval --raw "${DEV_SHELL}.drvPath")")" >> "$GITHUB_OUTPUT" + - name: Cache Rust artifacts uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 with: - shared-key: binaries-${{ inputs.binary }}-${{ inputs.triple }} + shared-key: binaries-${{ inputs.binary }}-${{ inputs.triple }}-${{ steps.dev-shell.outputs.hash }} cache-on-failure: "true" cache-workspace-crates: "true" cache-bin: "false" From d7d1c6b47de075a8d75ee20b613a12af17b2dba4 Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 15:44:21 +0200 Subject: [PATCH 09/17] ci: refactor end-to-end workflows Signed-off-by: Simon Scatton --- .github/actions/build-docker-image/action.yml | 7 +- .github/actions/build-rust-binary/action.yml | 6 + .github/actions/check-job-results/action.yml | 33 + .github/actions/setup-e2e-driver/action.yml | 27 + .github/actions/setup-e2e-gateway/action.yml | 8 +- .github/actions/setup-e2e-kind/action.yml | 82 +++ .github/actions/setup-e2e-podman/action.yml | 119 ++++ .github/actions/setup-e2e-vm/action.yml | 66 ++ .github/workflows/branch-e2e.yml | 268 ++++---- .github/workflows/build-binaries.yml | 5 + .github/workflows/build-platform-binaries.yml | 106 ++++ .github/workflows/build-vm-driver.yml | 5 + .github/workflows/deb-package.yml | 19 +- .github/workflows/docker-build.yml | 13 +- .github/workflows/driver-vm-linux.yml | 221 ------- .github/workflows/driver-vm-macos.yml | 221 ------- .github/workflows/e2e-docker-test.yml | 108 ++++ .github/workflows/e2e-kubernetes-test.yml | 68 +-- .github/workflows/e2e-podman-test.yml | 91 +++ .github/workflows/e2e-test.yml | 369 ----------- .github/workflows/e2e-vm-test.yml | 58 ++ .../workflows/package-release-binaries.yml | 77 +++ .github/workflows/release-dev.yml | 562 +++-------------- .github/workflows/release-tag.yml | 575 ++++-------------- .github/workflows/rpm-package.yml | 14 +- .github/workflows/snap-package.yml | 24 +- CI.md | 10 +- e2e/rust/e2e-vm.sh | 2 +- e2e/support/gateway-common.sh | 13 +- e2e/with-kube-gateway.sh | 17 +- scripts/baseline_workflow_metrics.py | 12 +- 31 files changed, 1209 insertions(+), 1997 deletions(-) create mode 100644 .github/actions/check-job-results/action.yml create mode 100644 .github/actions/setup-e2e-driver/action.yml create mode 100644 .github/actions/setup-e2e-kind/action.yml create mode 100644 .github/actions/setup-e2e-podman/action.yml create mode 100644 .github/actions/setup-e2e-vm/action.yml create mode 100644 .github/workflows/build-platform-binaries.yml delete mode 100644 .github/workflows/driver-vm-linux.yml delete mode 100644 .github/workflows/driver-vm-macos.yml create mode 100644 .github/workflows/e2e-docker-test.yml create mode 100644 .github/workflows/e2e-podman-test.yml delete mode 100644 .github/workflows/e2e-test.yml create mode 100644 .github/workflows/e2e-vm-test.yml create mode 100644 .github/workflows/package-release-binaries.yml diff --git a/.github/actions/build-docker-image/action.yml b/.github/actions/build-docker-image/action.yml index e2239f297e..6e7c06cf85 100644 --- a/.github/actions/build-docker-image/action.yml +++ b/.github/actions/build-docker-image/action.yml @@ -20,6 +20,9 @@ inputs: platform: description: Docker platform required: true + image-tag: + description: Docker image tag + required: true github-token: description: Token used to push the image required: true @@ -47,13 +50,15 @@ runs: - name: Build ${{ inputs.component }} image shell: bash + env: + IMAGE_TAG: ${{ inputs.image-tag }} run: | docker buildx build \ --builder openshell \ --platform ${{ inputs.platform }} \ --file deploy/docker/Dockerfile.${{ inputs.component }} \ --target ${{ inputs.component }} \ - --tag ghcr.io/nvidia/openshell/${{ inputs.component }}:${{ github.sha }}-${{ inputs.arch }} \ + --tag ghcr.io/nvidia/openshell/${{ inputs.component }}:${IMAGE_TAG}-${{ inputs.arch }} \ --cache-from type=gha,scope=${{ inputs.component }}-${{ inputs.arch }} \ --cache-to type=gha,mode=max,scope=${{ inputs.component }}-${{ inputs.arch }} \ --provenance=false \ diff --git a/.github/actions/build-rust-binary/action.yml b/.github/actions/build-rust-binary/action.yml index dd1a66c19e..d3ca52adf4 100644 --- a/.github/actions/build-rust-binary/action.yml +++ b/.github/actions/build-rust-binary/action.yml @@ -17,6 +17,10 @@ inputs: cargo-version: description: Cargo package version embedded in the binary required: true + image-tag: + description: Default supervisor image tag embedded in the binary + required: false + default: "" artifact-name: description: GitHub artifact name required: false @@ -55,6 +59,8 @@ runs: - name: Build ${{ inputs.binary }} shell: nix develop ${{ inputs.dev-shell }} -c bash -euo pipefail {0} + env: + OPENSHELL_IMAGE_TAG: ${{ inputs.image-tag }} run: GIT_DIR=/nonexistent cargo auditable build --release --bin "${{ inputs.binary }}" ${{ inputs.extra-cargo-flags }} - name: Verify ${{ inputs.binary }} diff --git a/.github/actions/check-job-results/action.yml b/.github/actions/check-job-results/action.yml new file mode 100644 index 0000000000..bd6beb9456 --- /dev/null +++ b/.github/actions/check-job-results/action.yml @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Check job results +description: Fail when any required upstream job did not succeed + +inputs: + results: + description: JSON-encoded GitHub Actions needs context + required: true + +runs: + using: composite + steps: + - name: Check required jobs + shell: bash + env: + JOB_RESULTS: ${{ inputs.results }} + run: | + set -euo pipefail + failures="$( + jq -r ' + to_entries[] + | select(.value.result != "success") + | "\(.key) concluded \(.value.result)" + ' <<< "$JOB_RESULTS" + )" + if [ -n "$failures" ]; then + while IFS= read -r failure; do + echo "::error::$failure" + done <<< "$failures" + exit 1 + fi diff --git a/.github/actions/setup-e2e-driver/action.yml b/.github/actions/setup-e2e-driver/action.yml new file mode 100644 index 0000000000..51ce00611e --- /dev/null +++ b/.github/actions/setup-e2e-driver/action.yml @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Setup E2E Driver +description: Download an architecture-matched standalone compute driver for E2E tests + +inputs: + binary: + description: Compute driver binary name + required: true + +runs: + using: composite + steps: + - name: Download prebuilt ${{ inputs.binary }} + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ inputs.binary }}-${{ runner.arch == 'X64' && 'x86_64-unknown-linux-gnu' || 'aarch64-unknown-linux-gnu' }} + path: .e2e/prebuilt-driver + + - name: Configure prebuilt ${{ inputs.binary }} + shell: bash + run: | + driver="$GITHUB_WORKSPACE/.e2e/prebuilt-driver/${{ inputs.binary }}" + chmod +x "$driver" + "$driver" --version + echo "OPENSHELL_EXTERNAL_DRIVER_BIN=$driver" >> "$GITHUB_ENV" diff --git a/.github/actions/setup-e2e-gateway/action.yml b/.github/actions/setup-e2e-gateway/action.yml index d146cff70c..255a7f4e4c 100644 --- a/.github/actions/setup-e2e-gateway/action.yml +++ b/.github/actions/setup-e2e-gateway/action.yml @@ -1,13 +1,19 @@ name: Setup E2E Gateway description: Download an architecture-matched prebuilt OpenShell gateway for E2E tests +inputs: + artifact-name: + description: GitHub artifact name + required: false + default: "" + runs: using: composite steps: - name: Download prebuilt gateway uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: ${{ runner.arch == 'X64' && 'openshell-gateway-x86_64-unknown-linux-gnu' || 'openshell-gateway-aarch64-unknown-linux-gnu' }} + name: ${{ inputs.artifact-name || (runner.arch == 'X64' && 'openshell-gateway-x86_64-unknown-linux-gnu' || 'openshell-gateway-aarch64-unknown-linux-gnu') }} path: .e2e/prebuilt-gateway - name: Configure prebuilt gateway diff --git a/.github/actions/setup-e2e-kind/action.yml b/.github/actions/setup-e2e-kind/action.yml new file mode 100644 index 0000000000..fd0d12c453 --- /dev/null +++ b/.github/actions/setup-e2e-kind/action.yml @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Setup E2E kind +description: Create a kind cluster and preload OpenShell images for E2E tests + +inputs: + cluster-name: + description: kind cluster name + required: true + image-tag: + description: OpenShell image tag to load + required: true + images: + description: Space-separated OpenShell image components to load + required: false + default: gateway supervisor + registry: + description: Container registry and namespace + required: false + default: ghcr.io/nvidia/openshell + registry-username: + description: Registry username + required: true + registry-password: + description: Registry password + required: true + mise-version: + description: mise release to install + required: false + default: v2026.4.25 + +runs: + using: composite + steps: + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: ${{ inputs.mise-version }} + + - name: Log in to GHCR + shell: bash + env: + REGISTRY: ${{ inputs.registry }} + REGISTRY_USERNAME: ${{ inputs.registry-username }} + REGISTRY_PASSWORD: ${{ inputs.registry-password }} + run: echo "$REGISTRY_PASSWORD" | docker login "${REGISTRY%%/*}" -u "$REGISTRY_USERNAME" --password-stdin + + - name: Create kind cluster + uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0 + with: + cluster_name: ${{ inputs.cluster-name }} + wait: 120s + + - name: Export kind kubeconfig to mise path + shell: bash + env: + CLUSTER_NAME: ${{ inputs.cluster-name }} + run: | + set -euo pipefail + kind get kubeconfig --name "$CLUSTER_NAME" > "$GITHUB_WORKSPACE/kubeconfig" + chmod 600 "$GITHUB_WORKSPACE/kubeconfig" + + - name: Load OpenShell images into kind + shell: bash + env: + CLUSTER_NAME: ${{ inputs.cluster-name }} + IMAGE_COMPONENTS: ${{ inputs.images }} + IMAGE_TAG: ${{ inputs.image-tag }} + REGISTRY: ${{ inputs.registry }} + run: | + set -euo pipefail + for component in $IMAGE_COMPONENTS; do + case "$component" in + gateway | supervisor) ;; + *) echo "ERROR: unsupported OpenShell image component: $component" >&2; exit 1 ;; + esac + image="${REGISTRY}/${component}:${IMAGE_TAG}" + archive="${RUNNER_TEMP:-/tmp}/openshell-${component}-linux-amd64.tar" + docker pull --platform linux/amd64 "$image" + docker image save --platform linux/amd64 --output "$archive" "$image" + kind load image-archive "$archive" --name "$CLUSTER_NAME" + done diff --git a/.github/actions/setup-e2e-podman/action.yml b/.github/actions/setup-e2e-podman/action.yml new file mode 100644 index 0000000000..05399510c2 --- /dev/null +++ b/.github/actions/setup-e2e-podman/action.yml @@ -0,0 +1,119 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Setup E2E Podman +description: Configure the supported rootless Podman environment for E2E tests + +inputs: + mise-version: + description: mise release to install + required: false + default: v2026.4.25 + podman-major: + description: Expected Podman major version + required: true + podman-package-version: + description: Exact Ubuntu Podman package version + required: true + conmon-package-version: + description: Exact Ubuntu conmon package version + required: true + +runs: + using: composite + steps: + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: ${{ inputs.mise-version }} + + - name: Install Podman and build dependencies + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + apparmor \ + build-essential \ + fuse-overlayfs \ + libssl-dev \ + openssh-client \ + passt \ + pkg-config \ + "conmon=${{ inputs.conmon-package-version }}" \ + "podman=${{ inputs.podman-package-version }}" \ + uidmap + # Hosted runners can place newer binaries under /usr/local. Select the + # distro CLI and use Podman's supported final conmon-path override. + podman_config="${RUNNER_TEMP}/openshell-containers.conf" + printf '%s\n' \ + '[engine]' \ + 'conmon_path = ["/usr/bin/conmon"]' \ + > "${podman_config}" + echo "/usr/bin" >> "${GITHUB_PATH}" + echo "CONTAINERS_CONF_OVERRIDE=${podman_config}" >> "${GITHUB_ENV}" + + - name: Allow pasta to receive Podman stop signals + shell: bash + run: | + set -euo pipefail + # Ubuntu's packaged profile blocks this signal and makes Podman wait + # for its SIGKILL fallback. + profile=/etc/apparmor.d/usr.bin.pasta + rule=' signal (receive) peer=podman,' + if ! sudo grep -Fqx "${rule}" "${profile}"; then + sudo sed -i '\|^ include $|a\ signal (receive) peer=podman,' "${profile}" + fi + sudo grep -Fqx "${rule}" "${profile}" + sudo apparmor_parser --replace "${profile}" + + - name: Configure rootless Podman + shell: bash + run: | + set -euo pipefail + if ! grep -q "^${USER}:" /etc/subuid; then + sudo usermod --add-subuids 100000-165535 "$USER" + fi + if ! grep -q "^${USER}:" /etc/subgid; then + sudo usermod --add-subgids 100000-165535 "$USER" + fi + runtime_dir="/run/user/$(id -u)" + sudo install -d -m 0700 -o "$(id -u)" -g "$(id -g)" "$runtime_dir" + echo "XDG_RUNTIME_DIR=$runtime_dir" >> "$GITHUB_ENV" + + - name: Verify rootless Podman environment + shell: bash + run: | + set -euo pipefail + podman_version="$(podman version --format '{{.Client.Version}}')" + case "$podman_version" in + "${{ inputs.podman-major }}".*) ;; + *) echo "ERROR: expected Podman ${{ inputs.podman-major }}.x, found $podman_version" >&2; exit 1 ;; + esac + test "$(dpkg-query -W -f='${Version}' podman)" = "${{ inputs.podman-package-version }}" + test "$(dpkg-query -W -f='${Version}' conmon)" = "${{ inputs.conmon-package-version }}" + test "$(command -v podman)" = "/usr/bin/podman" + test "$(podman info --format '{{.Host.Conmon.Path}}')" = "/usr/bin/conmon" + test "$(podman info --format '{{.Host.Security.Rootless}}')" = "true" + test "$(podman info --format '{{.Host.RootlessNetworkCmd}}')" = "pasta" + test "$(sudo sysctl -n kernel.apparmor_restrict_unprivileged_userns)" = "1" + echo "=== host ===" + uname -a + echo "=== AppArmor ===" + cat /proc/self/attr/current + sudo aa-status || true + echo "=== Podman ===" + podman version + podman info --debug + + - name: Probe rootless capability bounding set + shell: bash + run: | + set -euo pipefail + probe="$RUNNER_TEMP/openshell-capbset-probe" + cc -static -O2 -Wall -Wextra -Werror \ + e2e/support/capbset-probe.c \ + -o "$probe" + podman run --rm \ + --cap-add=SETPCAP \ + --volume "$probe:/openshell-capbset-probe:ro" \ + docker.io/library/alpine:3.22 \ + /openshell-capbset-probe diff --git a/.github/actions/setup-e2e-vm/action.yml b/.github/actions/setup-e2e-vm/action.yml new file mode 100644 index 0000000000..1626f15e85 --- /dev/null +++ b/.github/actions/setup-e2e-vm/action.yml @@ -0,0 +1,66 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Setup E2E VM +description: Configure a bare Linux KVM host for VM E2E tests + +inputs: + mise-version: + description: mise release to install + required: false + default: v2026.4.25 + rust-cache-key: + description: Shared key for Rust test artifacts + required: false + default: e2e-vm-linux-amd64 + +runs: + using: composite + steps: + - name: Install system dependencies + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + build-essential \ + libssl-dev \ + openssh-client \ + pkg-config \ + socat \ + zstd + + - name: Enable KVM access + shell: bash + run: | + set -euo pipefail + if [[ ! -c /dev/kvm ]]; then + echo "::error::The GitHub-hosted runner did not expose /dev/kvm" + lscpu + grep -m1 -E '^(flags|Features)' /proc/cpuinfo || true + ls -la /dev + exit 1 + fi + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ + | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --settle --name-match=kvm + ls -l /dev/kvm + exec 3<>/dev/kvm + exec 3>&- + + - name: Validate VM host tools + shell: bash + run: | + command -v mke2fs + command -v mkfs.ext4 + command -v debugfs + + - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + with: + version: ${{ inputs.mise-version }} + + - name: Cache Rust target and registry + uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + with: + shared-key: ${{ inputs.rust-cache-key }} + cache-on-failure: "true" diff --git a/.github/workflows/branch-e2e.yml b/.github/workflows/branch-e2e.yml index 65caad4b5c..a86dda04da 100644 --- a/.github/workflows/branch-e2e.yml +++ b/.github/workflows/branch-e2e.yml @@ -100,24 +100,9 @@ jobs: needs: version permissions: contents: read - strategy: - matrix: - include: - - triple: x86_64-unknown-linux-musl - runner: linux-amd64-cpu8 - dev_shell: .#devShells.x86_64-linux.musl - - triple: aarch64-unknown-linux-musl - runner: linux-arm64-cpu8 - dev_shell: .#devShells.aarch64-linux.musl - - triple: aarch64-apple-darwin - runner: macos-15-xlarge - dev_shell: .#devShells.aarch64-darwin.default - uses: ./.github/workflows/build-binaries.yml + uses: ./.github/workflows/build-platform-binaries.yml with: - binary: openshell - triple: ${{ matrix.triple }} - runner: ${{ matrix.runner }} - dev-shell: ${{ matrix.dev_shell }} + component: cli cargo-version: ${{ needs.version.outputs.cargo }} secrets: inherit @@ -125,21 +110,9 @@ jobs: needs: version permissions: contents: read - strategy: - matrix: - include: - - triple: x86_64-unknown-linux-musl - runner: linux-amd64-cpu8 - dev_shell: .#devShells.x86_64-linux.musl - - triple: aarch64-unknown-linux-musl - runner: linux-arm64-cpu8 - dev_shell: .#devShells.aarch64-linux.musl - uses: ./.github/workflows/build-binaries.yml + uses: ./.github/workflows/build-platform-binaries.yml with: - binary: openshell-sandbox - triple: ${{ matrix.triple }} - runner: ${{ matrix.runner }} - dev-shell: ${{ matrix.dev_shell }} + component: sandbox-musl cargo-version: ${{ needs.version.outputs.cargo }} secrets: inherit @@ -147,28 +120,10 @@ jobs: needs: version permissions: contents: read - strategy: - matrix: - include: - - triple: x86_64-unknown-linux-gnu - runner: linux-amd64-cpu8 - dev_shell: .#devShells.x86_64-linux.glibc-2-28 - interpreter: /lib64/ld-linux-x86-64.so.2 - - triple: aarch64-unknown-linux-gnu - runner: linux-arm64-cpu8 - dev_shell: .#devShells.aarch64-linux.glibc-2-28 - interpreter: /lib/ld-linux-aarch64.so.1 - - triple: aarch64-apple-darwin - runner: macos-15-xlarge - dev_shell: .#devShells.aarch64-darwin.default - uses: ./.github/workflows/build-binaries.yml + uses: ./.github/workflows/build-platform-binaries.yml with: - binary: openshell-gateway - triple: ${{ matrix.triple }} - runner: ${{ matrix.runner }} - dev-shell: ${{ matrix.dev_shell }} + component: gateway cargo-version: ${{ needs.version.outputs.cargo }} - interpreter: ${{ matrix.interpreter }} secrets: inherit build-gateway-plain: @@ -256,7 +211,11 @@ jobs: build-gateway-image: needs: [pr_metadata, build-gateway] - if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_any_e2e == 'true' + if: >- + needs.pr_metadata.outputs.should_run == 'true' && + (needs.pr_metadata.outputs.run_core_e2e == 'true' || + needs.pr_metadata.outputs.run_kubernetes_ha_e2e == 'true' || + needs.pr_metadata.outputs.run_kubernetes_credential_drivers_e2e == 'true') permissions: contents: read packages: write @@ -280,20 +239,84 @@ jobs: target-suffix: unknown-linux-musl secrets: inherit - e2e: - needs: [pr_metadata, build-cli, build-vm-driver, build-gateway-image, build-supervisor-image] + docker-e2e: + needs: [pr_metadata, build-cli, build-gateway, build-supervisor-image] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' permissions: actions: read contents: read packages: read - uses: ./.github/workflows/e2e-test.yml + uses: ./.github/workflows/e2e-docker-test.yml with: image-tag: ${{ github.sha }} runner: linux-arm64-cpu8 + podman-e2e: + needs: [pr_metadata, build-cli, build-gateway, build-supervisor-image] + if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' + permissions: + actions: read + contents: read + packages: read + uses: ./.github/workflows/e2e-podman-test.yml + with: + image-tag: ${{ github.sha }} + + vm-e2e: + needs: [pr_metadata, build-cli, build-gateway, build-vm-driver] + if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' + permissions: + actions: read + contents: read + packages: read + uses: ./.github/workflows/e2e-vm-test.yml + + docker-external-driver-e2e: + needs: [pr_metadata, build-cli, build-gateway-plain, build-driver-docker, build-supervisor-image] + if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' + permissions: + actions: read + contents: read + packages: read + uses: ./.github/workflows/e2e-docker-test.yml + with: + image-tag: ${{ github.sha }} + runner: linux-arm64-cpu8 + gateway-artifact: openshell-gateway-plain-aarch64-unknown-linux-gnu + external-driver-binary: openshell-driver-docker + suite-matrix: >- + [{"suite":"external-driver","cmd":"mise run --no-deps --skip-deps e2e:docker:external-driver","apt_packages":"openssh-client","python_proto":false,"mcp":false}] + + podman-external-driver-e2e: + needs: [pr_metadata, build-cli, build-gateway-plain, build-driver-podman, build-supervisor-image] + if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' + permissions: + actions: read + contents: read + packages: read + uses: ./.github/workflows/e2e-podman-test.yml + with: + image-tag: ${{ github.sha }} + gateway-artifact: openshell-gateway-plain-x86_64-unknown-linux-gnu + external-driver-binary: openshell-driver-podman + suite-matrix: >- + [{"suite":"external-driver","runner":"ubuntu-26.04","podman_major":"5","podman_package_version":"5.7.0+ds2-3build1","conmon_package_version":"2.1.13+ds1-2","cmd":"mise run --no-deps --skip-deps e2e:podman:external-driver"}] + + vm-external-driver-e2e: + needs: [pr_metadata, build-cli, build-gateway-plain, build-vm-driver] + if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' + permissions: + actions: read + contents: read + packages: read + uses: ./.github/workflows/e2e-vm-test.yml + with: + gateway-artifact: openshell-gateway-plain-x86_64-unknown-linux-gnu + suite-name: external-driver + e2e-task: e2e:vm:external-driver + gpu-e2e: - needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image] + needs: [pr_metadata, build-cli, build-gateway, build-supervisor-image] if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_gpu_e2e == 'true' permissions: actions: read @@ -346,8 +369,21 @@ jobs: job-name: Kubernetes E2E (workspace managed mode) e2e-task: e2e:kubernetes:workspace-managed - # External Kubernetes driver E2E stays disabled while its standalone binary - # is not a release artifact. + kubernetes-external-driver-e2e: + needs: [pr_metadata, build-cli, build-gateway-plain, build-driver-kubernetes, build-supervisor-image] + if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' + permissions: + actions: read + contents: read + packages: read + uses: ./.github/workflows/e2e-kubernetes-test.yml + with: + image-tag: ${{ github.sha }} + job-name: Kubernetes E2E (external compute driver) + e2e-task: e2e:kubernetes:external-driver + gateway-artifact: openshell-gateway-plain-x86_64-unknown-linux-gnu + external-driver-binary: openshell-driver-kubernetes + cluster-images: supervisor kubernetes-workspace-operator-e2e: needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image] @@ -391,124 +427,40 @@ jobs: core-e2e-result: name: Core E2E result - needs: [pr_metadata, build-cli, build-vm-driver, build-gateway-image, build-supervisor-image, e2e, kubernetes-e2e, kubernetes-workspace-managed-e2e, kubernetes-workspace-operator-e2e] + needs: [pr_metadata, docker-e2e, podman-e2e, vm-e2e, docker-external-driver-e2e, podman-external-driver-e2e, vm-external-driver-e2e, kubernetes-e2e, kubernetes-external-driver-e2e, kubernetes-workspace-managed-e2e, kubernetes-workspace-operator-e2e] if: always() && needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' runs-on: ubuntu-latest steps: - - name: Verify core E2E jobs - env: - BUILD_CLI_RESULT: ${{ needs.build-cli.result }} - BUILD_VM_DRIVER_RESULT: ${{ needs.build-vm-driver.result }} - BUILD_GATEWAY_IMAGE_RESULT: ${{ needs.build-gateway-image.result }} - BUILD_SUPERVISOR_IMAGE_RESULT: ${{ needs.build-supervisor-image.result }} - E2E_RESULT: ${{ needs.e2e.result }} - KUBERNETES_E2E_RESULT: ${{ needs.kubernetes-e2e.result }} - KUBERNETES_WORKSPACE_MANAGED_E2E_RESULT: ${{ needs.kubernetes-workspace-managed-e2e.result }} - KUBERNETES_WORKSPACE_OPERATOR_E2E_RESULT: ${{ needs.kubernetes-workspace-operator-e2e.result }} - run: | - set -euo pipefail - failed=0 - for item in \ - "build-cli:$BUILD_CLI_RESULT" \ - "build-vm-driver:$BUILD_VM_DRIVER_RESULT" \ - "build-gateway-image:$BUILD_GATEWAY_IMAGE_RESULT" \ - "build-supervisor-image:$BUILD_SUPERVISOR_IMAGE_RESULT" \ - "e2e:$E2E_RESULT" \ - "kubernetes-e2e:$KUBERNETES_E2E_RESULT" \ - "kubernetes-workspace-managed-e2e:$KUBERNETES_WORKSPACE_MANAGED_E2E_RESULT" \ - "kubernetes-workspace-operator-e2e:$KUBERNETES_WORKSPACE_OPERATOR_E2E_RESULT"; do - name="${item%%:*}" - result="${item#*:}" - if [ "$result" != "success" ]; then - echo "::error::$name concluded $result" - failed=1 - fi - done - exit "$failed" + - uses: ./.github/actions/check-job-results + with: + results: ${{ toJSON(needs) }} gpu-e2e-result: name: GPU E2E result - needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image, gpu-e2e] + needs: [pr_metadata, gpu-e2e] if: always() && needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_gpu_e2e == 'true' runs-on: ubuntu-latest steps: - - name: Verify GPU E2E jobs - env: - BUILD_CLI_RESULT: ${{ needs.build-cli.result }} - BUILD_GATEWAY_IMAGE_RESULT: ${{ needs.build-gateway-image.result }} - BUILD_SUPERVISOR_IMAGE_RESULT: ${{ needs.build-supervisor-image.result }} - GPU_E2E_RESULT: ${{ needs.gpu-e2e.result }} - run: | - set -euo pipefail - failed=0 - for item in \ - "build-cli:$BUILD_CLI_RESULT" \ - "build-gateway-image:$BUILD_GATEWAY_IMAGE_RESULT" \ - "build-supervisor-image:$BUILD_SUPERVISOR_IMAGE_RESULT" \ - "gpu-e2e:$GPU_E2E_RESULT"; do - name="${item%%:*}" - result="${item#*:}" - if [ "$result" != "success" ]; then - echo "::error::$name concluded $result" - failed=1 - fi - done - exit "$failed" + - uses: ./.github/actions/check-job-results + with: + results: ${{ toJSON(needs) }} kubernetes-ha-e2e-result: name: Kubernetes HA E2E result - needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image, kubernetes-ha-e2e] + needs: [pr_metadata, kubernetes-ha-e2e] if: always() && needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_kubernetes_ha_e2e == 'true' runs-on: ubuntu-latest steps: - - name: Verify Kubernetes HA E2E jobs - env: - BUILD_CLI_RESULT: ${{ needs.build-cli.result }} - BUILD_GATEWAY_IMAGE_RESULT: ${{ needs.build-gateway-image.result }} - BUILD_SUPERVISOR_IMAGE_RESULT: ${{ needs.build-supervisor-image.result }} - KUBERNETES_HA_E2E_RESULT: ${{ needs.kubernetes-ha-e2e.result }} - run: | - set -euo pipefail - failed=0 - for item in \ - "build-cli:$BUILD_CLI_RESULT" \ - "build-gateway-image:$BUILD_GATEWAY_IMAGE_RESULT" \ - "build-supervisor-image:$BUILD_SUPERVISOR_IMAGE_RESULT" \ - "kubernetes-ha-e2e:$KUBERNETES_HA_E2E_RESULT"; do - name="${item%%:*}" - result="${item#*:}" - if [ "$result" != "success" ]; then - echo "::error::$name concluded $result" - failed=1 - fi - done - exit "$failed" + - uses: ./.github/actions/check-job-results + with: + results: ${{ toJSON(needs) }} kubernetes-credential-drivers-e2e-result: name: Kubernetes Credential Drivers E2E result - needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image, kubernetes-credential-drivers-e2e] + needs: [pr_metadata, kubernetes-credential-drivers-e2e] if: always() && needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_kubernetes_credential_drivers_e2e == 'true' runs-on: ubuntu-latest steps: - - name: Verify Kubernetes credential drivers E2E jobs - env: - BUILD_CLI_RESULT: ${{ needs.build-cli.result }} - BUILD_GATEWAY_IMAGE_RESULT: ${{ needs.build-gateway-image.result }} - BUILD_SUPERVISOR_IMAGE_RESULT: ${{ needs.build-supervisor-image.result }} - KUBERNETES_CREDENTIAL_DRIVERS_E2E_RESULT: ${{ needs.kubernetes-credential-drivers-e2e.result }} - run: | - set -euo pipefail - failed=0 - for item in \ - "build-cli:$BUILD_CLI_RESULT" \ - "build-gateway-image:$BUILD_GATEWAY_IMAGE_RESULT" \ - "build-supervisor-image:$BUILD_SUPERVISOR_IMAGE_RESULT" \ - "kubernetes-credential-drivers-e2e:$KUBERNETES_CREDENTIAL_DRIVERS_E2E_RESULT"; do - name="${item%%:*}" - result="${item#*:}" - if [ "$result" != "success" ]; then - echo "::error::$name concluded $result" - failed=1 - fi - done - exit "$failed" + - uses: ./.github/actions/check-job-results + with: + results: ${{ toJSON(needs) }} diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index cd841a6349..dccf2c1eae 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -21,6 +21,10 @@ on: cargo-version: required: true type: string + image-tag: + required: false + type: string + default: "" artifact-name: required: false type: string @@ -64,6 +68,7 @@ jobs: triple: ${{ inputs.triple }} dev-shell: ${{ inputs['dev-shell'] }} cargo-version: ${{ inputs['cargo-version'] }} + image-tag: ${{ inputs['image-tag'] }} artifact-name: ${{ inputs['artifact-name'] }} extra-cargo-flags: ${{ inputs['extra-cargo-flags'] }} interpreter: ${{ inputs.interpreter }} diff --git a/.github/workflows/build-platform-binaries.yml b/.github/workflows/build-platform-binaries.yml new file mode 100644 index 0000000000..69db18d033 --- /dev/null +++ b/.github/workflows/build-platform-binaries.yml @@ -0,0 +1,106 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Build Platform Binaries + +on: + workflow_call: + inputs: + component: + description: Target matrix to build + required: true + type: string + cargo-version: + required: true + type: string + image-tag: + required: false + type: string + default: "" + checkout-ref: + required: false + type: string + default: "" + secrets: + CACHIX_AUTH_TOKEN: + required: true + +permissions: + contents: read + +jobs: + cli: + if: inputs.component == 'cli' + strategy: + matrix: + include: + - triple: x86_64-unknown-linux-musl + runner: linux-amd64-cpu8 + dev_shell: .#devShells.x86_64-linux.musl + - triple: aarch64-unknown-linux-musl + runner: linux-arm64-cpu8 + dev_shell: .#devShells.aarch64-linux.musl + - triple: aarch64-apple-darwin + runner: macos-15-xlarge + dev_shell: .#devShells.aarch64-darwin.default + uses: ./.github/workflows/build-binaries.yml + with: + binary: openshell + triple: ${{ matrix.triple }} + runner: ${{ matrix.runner }} + dev-shell: ${{ matrix.dev_shell }} + cargo-version: ${{ inputs.cargo-version }} + image-tag: ${{ inputs.image-tag }} + checkout-ref: ${{ inputs.checkout-ref }} + secrets: inherit + + gateway: + if: inputs.component == 'gateway' + strategy: + matrix: + include: + - triple: x86_64-unknown-linux-gnu + runner: linux-amd64-cpu8 + dev_shell: .#devShells.x86_64-linux.glibc-2-28 + interpreter: /lib64/ld-linux-x86-64.so.2 + - triple: aarch64-unknown-linux-gnu + runner: linux-arm64-cpu8 + dev_shell: .#devShells.aarch64-linux.glibc-2-28 + interpreter: /lib/ld-linux-aarch64.so.1 + - triple: aarch64-apple-darwin + runner: macos-15-xlarge + dev_shell: .#devShells.aarch64-darwin.default + interpreter: "" + uses: ./.github/workflows/build-binaries.yml + with: + binary: openshell-gateway + triple: ${{ matrix.triple }} + runner: ${{ matrix.runner }} + dev-shell: ${{ matrix.dev_shell }} + cargo-version: ${{ inputs.cargo-version }} + image-tag: ${{ inputs.image-tag }} + interpreter: ${{ matrix.interpreter }} + checkout-ref: ${{ inputs.checkout-ref }} + secrets: inherit + + sandbox-musl: + if: inputs.component == 'sandbox-musl' + strategy: + matrix: + include: + - triple: x86_64-unknown-linux-musl + runner: linux-amd64-cpu8 + dev_shell: .#devShells.x86_64-linux.musl + - triple: aarch64-unknown-linux-musl + runner: linux-arm64-cpu8 + dev_shell: .#devShells.aarch64-linux.musl + uses: ./.github/workflows/build-binaries.yml + with: + binary: openshell-sandbox + triple: ${{ matrix.triple }} + runner: ${{ matrix.runner }} + dev-shell: ${{ matrix.dev_shell }} + cargo-version: ${{ inputs.cargo-version }} + image-tag: ${{ inputs.image-tag }} + checkout-ref: ${{ inputs.checkout-ref }} + secrets: inherit diff --git a/.github/workflows/build-vm-driver.yml b/.github/workflows/build-vm-driver.yml index 3e7eed2fcf..665d064f6e 100644 --- a/.github/workflows/build-vm-driver.yml +++ b/.github/workflows/build-vm-driver.yml @@ -9,6 +9,10 @@ on: cargo-version: required: true type: string + image-tag: + required: false + type: string + default: "" checkout-ref: required: false type: string @@ -93,4 +97,5 @@ jobs: triple: ${{ matrix.triple }} dev-shell: ${{ matrix.dev_shell }} cargo-version: ${{ inputs['cargo-version'] }} + image-tag: ${{ inputs['image-tag'] }} interpreter: ${{ matrix.interpreter }} diff --git a/.github/workflows/deb-package.yml b/.github/workflows/deb-package.yml index 3929203909..8031863992 100644 --- a/.github/workflows/deb-package.yml +++ b/.github/workflows/deb-package.yml @@ -49,28 +49,25 @@ jobs: - name: Download CLI artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: cli-linux-${{ matrix.arch }} - path: package-input/ + name: openshell-${{ matrix.cli_target }} + path: package-binaries/ - name: Download gateway artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: gateway-binary-linux-${{ matrix.arch }} - path: package-input/ + name: openshell-gateway-${{ matrix.gnu_target }} + path: package-binaries/ - name: Download VM driver artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: driver-vm-linux-${{ matrix.arch }} - path: package-input/ + name: openshell-driver-vm-${{ matrix.gnu_target }} + path: package-binaries/ - - name: Extract package inputs + - name: Configure package inputs run: | set -euo pipefail - mkdir -p package-binaries - tar -xzf "package-input/openshell-${{ matrix.cli_target }}.tar.gz" -C package-binaries - tar -xzf "package-input/openshell-gateway-${{ matrix.gnu_target }}.tar.gz" -C package-binaries - tar -xzf "package-input/openshell-driver-vm-${{ matrix.gnu_target }}.tar.gz" -C package-binaries + chmod +x package-binaries/openshell{,-gateway,-driver-vm} ls -lah package-binaries - name: Build Debian package diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index b663598969..de2b4acef0 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -15,6 +15,10 @@ on: target-suffix: required: true type: string + image-tag: + required: false + type: string + default: "" checkout-ref: required: false type: string @@ -62,6 +66,7 @@ jobs: triple: ${{ matrix.rust_arch }}-${{ inputs['target-suffix'] }} arch: ${{ matrix.arch }} platform: ${{ matrix.platform }} + image-tag: ${{ inputs.image-tag || github.sha }} github-token: ${{ secrets.GITHUB_TOKEN }} manifest: @@ -83,10 +88,12 @@ jobs: - name: Create manifest shell: bash + env: + IMAGE_TAG: ${{ inputs.image-tag || github.sha }} run: | image=ghcr.io/nvidia/openshell/${{ inputs.component }} docker buildx imagetools create \ --prefer-index=false \ - --tag "$image:${{ github.sha }}" \ - "$image:${{ github.sha }}-amd64" \ - "$image:${{ github.sha }}-arm64" + --tag "$image:${IMAGE_TAG}" \ + "$image:${IMAGE_TAG}-amd64" \ + "$image:${IMAGE_TAG}-arm64" diff --git a/.github/workflows/driver-vm-linux.yml b/.github/workflows/driver-vm-linux.yml deleted file mode 100644 index 942cacdfbd..0000000000 --- a/.github/workflows/driver-vm-linux.yml +++ /dev/null @@ -1,221 +0,0 @@ -name: Driver VM Linux - -on: - workflow_call: - inputs: - cargo-version: - required: false - type: string - default: "" - image-tag: - required: true - type: string - checkout-ref: - required: true - type: string - runtime-platforms: - description: "Comma-separated VM runtime platforms to download: linux-aarch64,linux-x86_64" - required: false - type: string - default: "linux-aarch64,linux-x86_64" - driver-targets: - description: "JSON matrix entries for Linux VM driver targets" - required: false - type: string - default: >- - [{"arch":"arm64","runner":"linux-arm64-cpu8","target":"aarch64-unknown-linux-gnu","zig_target":"aarch64-unknown-linux-gnu.2.28","platform":"linux-aarch64","guest_arch":"aarch64"},{"arch":"amd64","runner":"linux-amd64-cpu8","target":"x86_64-unknown-linux-gnu","zig_target":"x86_64-unknown-linux-gnu.2.28","platform":"linux-x86_64","guest_arch":"x86_64"}] - -permissions: - contents: read - packages: read - -defaults: - run: - shell: bash - -jobs: - download-kernel-runtime: - name: Download Kernel Runtime - runs-on: linux-amd64-cpu8 - timeout-minutes: 10 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ inputs['checkout-ref'] }} - - - name: Download Linux runtime tarballs - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RUNTIME_PLATFORMS: ${{ inputs['runtime-platforms'] }} - run: | - set -euo pipefail - mkdir -p runtime-artifacts - - runtime_platforms="${RUNTIME_PLATFORMS//[[:space:]]/}" - IFS=',' read -r -a platforms <<< "$runtime_platforms" - if [ "${#platforms[@]}" -eq 0 ]; then - echo "::error::No VM runtime platforms requested" - exit 1 - fi - for platform in "${platforms[@]}"; do - case "$platform" in - linux-aarch64 | linux-x86_64) ;; - *) - echo "::error::Unsupported VM runtime platform '$platform' in runtime-platforms" - exit 1 - ;; - esac - done - printf '%s\n' "${platforms[@]}" > runtime-artifacts/platforms.txt - - for platform in "${platforms[@]}"; do - asset="vm-runtime-${platform}.tar.zst" - echo "Downloading ${asset}..." - if ! gh release download vm-runtime \ - --repo "${GITHUB_REPOSITORY}" \ - --pattern "${asset}" \ - --dir runtime-artifacts \ - --clobber; then - echo "::error::No ${asset} asset found on vm-runtime release" - exit 1 - fi - done - - ls -lah runtime-artifacts/ - - - name: Verify downloads - run: | - set -euo pipefail - while IFS= read -r platform; do - test -f "runtime-artifacts/vm-runtime-${platform}.tar.zst" - done < runtime-artifacts/platforms.txt - - - name: Upload runtime artifacts - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: vm-driver-kernel-runtime-tarballs - path: runtime-artifacts/vm-runtime-*.tar.zst - retention-days: 1 - - build-driver-vm-linux: - name: Build Driver VM (Linux ${{ matrix.arch }}) - needs: [download-kernel-runtime] - strategy: - matrix: - include: ${{ fromJSON(inputs['driver-targets']) }} - runs-on: ${{ matrix.runner }} - timeout-minutes: 30 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - options: --privileged - env: - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENSHELL_IMAGE_TAG: ${{ inputs['image-tag'] }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ inputs['checkout-ref'] }} - fetch-depth: 0 - - - name: Mark workspace safe for git - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Fetch tags - run: git fetch --tags --force - - - name: Install tools - run: mise install --locked - - - name: Cache Rust target and registry - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - with: - shared-key: driver-vm-linux-${{ matrix.arch }} - cache-directories: .cache/sccache - cache-targets: "true" - - - name: Install zstd - run: apt-get update && apt-get install -y --no-install-recommends zstd && rm -rf /var/lib/apt/lists/* - - - name: Download kernel runtime tarball - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: vm-driver-kernel-runtime-tarballs - path: runtime-download/ - - - name: Stage compressed runtime for embedding - run: | - set -euo pipefail - COMPRESSED_DIR="${PWD}/target/vm-runtime-compressed" - VM_RUNTIME_TARBALL="${PWD}/runtime-download/vm-runtime-${{ matrix.platform }}.tar.zst" \ - VM_RUNTIME_PLATFORM="${{ matrix.platform }}" \ - OPENSHELL_VM_RUNTIME_COMPRESSED_DIR="$COMPRESSED_DIR" \ - tasks/scripts/vm/compress-vm-runtime.sh - - - name: Build bundled supervisor - run: | - set -euo pipefail - OPENSHELL_VM_RUNTIME_COMPRESSED_DIR="${PWD}/target/vm-runtime-compressed" \ - tasks/scripts/vm/build-supervisor-bundle.sh --arch "${{ matrix.guest_arch }}" - - - name: Verify embedded driver inputs - run: | - set -euo pipefail - for file in libkrun.so.zst libkrunfw.so.5.zst gvproxy.zst umoci.zst openshell-sandbox.zst; do - test -s "target/vm-runtime-compressed/${file}" - done - - - name: Scope workspace to driver-vm crates - run: | - set -euo pipefail - sed -i 's|members = \["crates/\*"\]|members = ["crates/openshell-driver-vm", "crates/openshell-core"]|' Cargo.toml - - - name: Patch workspace version - if: ${{ inputs['cargo-version'] != '' }} - run: | - set -euo pipefail - sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ inputs['cargo-version'] }}"'"/}' Cargo.toml - - - name: Build openshell-driver-vm with glibc 2.28 floor - run: | - set -euo pipefail - mise x -- rustup target add ${{ matrix.target }} - OPENSHELL_VM_RUNTIME_COMPRESSED_DIR="${PWD}/target/vm-runtime-compressed" \ - mise x -- cargo zigbuild --release --target ${{ matrix.zig_target }} -p openshell-driver-vm --bin openshell-driver-vm - mkdir -p artifacts/bin - install -m 0755 target/${{ matrix.target }}/release/openshell-driver-vm artifacts/bin/openshell-driver-vm - - - name: Verify packaged binary - run: | - set -euo pipefail - OUTPUT="$(artifacts/bin/openshell-driver-vm --version)" - echo "$OUTPUT" - grep -q '^openshell-driver-vm ' <<<"$OUTPUT" - - - name: Verify glibc symbol floor - run: tasks/scripts/verify-glibc-symbols.sh 2.28 artifacts/bin/openshell-driver-vm - - - name: sccache stats - if: always() - run: mise x -- sccache --show-stats - - - name: Package binary - run: | - set -euo pipefail - mkdir -p artifacts - tar -czf "artifacts/openshell-driver-vm-${{ matrix.target }}.tar.gz" \ - -C artifacts/bin openshell-driver-vm - - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: driver-vm-linux-${{ matrix.arch }} - path: artifacts/*.tar.gz - retention-days: 5 diff --git a/.github/workflows/driver-vm-macos.yml b/.github/workflows/driver-vm-macos.yml deleted file mode 100644 index a97ade9cbb..0000000000 --- a/.github/workflows/driver-vm-macos.yml +++ /dev/null @@ -1,221 +0,0 @@ -name: Driver VM macOS - -on: - workflow_call: - inputs: - cargo-version: - required: true - type: string - image-tag: - required: true - type: string - checkout-ref: - required: true - type: string - -permissions: - contents: read - packages: read - -defaults: - run: - shell: bash - -jobs: - download-kernel-runtime: - name: Download Kernel Runtime - runs-on: linux-amd64-cpu8 - timeout-minutes: 10 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ inputs['checkout-ref'] }} - - - name: Download macOS runtime tarball - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - mkdir -p runtime-artifacts - - asset="vm-runtime-darwin-aarch64.tar.zst" - echo "Downloading ${asset}..." - if ! gh release download vm-runtime \ - --repo "${GITHUB_REPOSITORY}" \ - --pattern "${asset}" \ - --dir runtime-artifacts \ - --clobber; then - echo "::error::No ${asset} asset found on vm-runtime release" - exit 1 - fi - - ls -lah runtime-artifacts/ - - - name: Verify download - run: test -f runtime-artifacts/vm-runtime-darwin-aarch64.tar.zst - - - name: Upload runtime artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: vm-driver-macos-kernel-runtime-tarball - path: runtime-artifacts/vm-runtime-darwin-aarch64.tar.zst - retention-days: 1 - - build-supervisor-arm64: - name: Build Supervisor Bundle (arm64) - runs-on: linux-arm64-cpu8 - timeout-minutes: 30 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - env: - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENSHELL_IMAGE_TAG: ${{ inputs['image-tag'] }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ inputs['checkout-ref'] }} - fetch-depth: 0 - - - name: Mark workspace safe for git - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Fetch tags - run: git fetch --tags --force - - - name: Install tools - run: mise install --locked - - - name: Cache Rust target and registry - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - with: - shared-key: driver-vm-supervisor-arm64 - cache-directories: .cache/sccache - cache-targets: "true" - - - name: Install zstd - run: apt-get update && apt-get install -y --no-install-recommends zstd && rm -rf /var/lib/apt/lists/* - - - name: Build bundled supervisor - run: | - set -euo pipefail - tasks/scripts/vm/build-supervisor-bundle.sh --arch aarch64 - - - name: sccache stats - if: always() - run: mise x -- sccache --show-stats - - - name: Upload supervisor bundle - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: driver-vm-supervisor-arm64 - path: target/vm-runtime-compressed/openshell-sandbox.zst - retention-days: 1 - - build-driver-vm-macos: - name: Build Driver VM (macOS) - needs: [download-kernel-runtime, build-supervisor-arm64] - runs-on: linux-amd64-cpu8 - timeout-minutes: 60 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - options: --privileged - volumes: - - /var/run/docker.sock:/var/run/docker.sock - env: - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ inputs['checkout-ref'] }} - fetch-depth: 0 - - - name: Mark workspace safe for git - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Fetch tags - run: git fetch --tags --force - - - name: Log in to GHCR - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: Set up Docker Buildx - uses: ./.github/actions/setup-buildx - - - name: Install zstd - run: apt-get update && apt-get install -y --no-install-recommends zstd && rm -rf /var/lib/apt/lists/* - - - name: Download kernel runtime tarball - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: vm-driver-macos-kernel-runtime-tarball - path: runtime-download/ - - - name: Prepare compressed runtime directory - run: | - set -euo pipefail - COMPRESSED_DIR="${PWD}/target/vm-runtime-compressed-macos" - VM_RUNTIME_TARBALL="${PWD}/runtime-download/vm-runtime-darwin-aarch64.tar.zst" \ - VM_RUNTIME_PLATFORM="darwin-aarch64" \ - OPENSHELL_VM_RUNTIME_COMPRESSED_DIR="$COMPRESSED_DIR" \ - tasks/scripts/vm/compress-vm-runtime.sh - - - name: Download bundled supervisor - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: driver-vm-supervisor-arm64 - path: target/vm-runtime-compressed-macos/ - - - name: Verify bundled supervisor - run: | - set -euo pipefail - test -f target/vm-runtime-compressed-macos/openshell-sandbox.zst - ls -lh target/vm-runtime-compressed-macos/openshell-sandbox.zst - - - name: Verify embedded driver inputs - run: | - set -euo pipefail - for file in libkrun.dylib.zst libkrunfw.5.dylib.zst gvproxy.zst umoci.zst openshell-sandbox.zst; do - test -s "target/vm-runtime-compressed-macos/${file}" - done - - - name: Build macOS binary via Docker - run: | - set -euo pipefail - docker buildx build \ - --file deploy/docker/Dockerfile.driver-vm-macos \ - --build-arg OPENSHELL_CARGO_VERSION="${{ inputs['cargo-version'] }}" \ - --build-arg OPENSHELL_IMAGE_TAG="${{ inputs['image-tag'] }}" \ - --build-arg CARGO_TARGET_CACHE_SCOPE="${{ github.sha }}" \ - --build-context vm-runtime-compressed="${PWD}/target/vm-runtime-compressed-macos" \ - --target binary \ - --output type=local,dest=out/ \ - . - - - name: Verify packaged binary shape - run: test -x out/openshell-driver-vm - - - name: Package binary - run: | - set -euo pipefail - mkdir -p artifacts - tar -czf artifacts/openshell-driver-vm-aarch64-apple-darwin.tar.gz \ - -C out openshell-driver-vm - ls -lh artifacts/ - - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: driver-vm-macos - path: artifacts/*.tar.gz - retention-days: 5 diff --git a/.github/workflows/e2e-docker-test.yml b/.github/workflows/e2e-docker-test.yml new file mode 100644 index 0000000000..8cfaa0eabc --- /dev/null +++ b/.github/workflows/e2e-docker-test.yml @@ -0,0 +1,108 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Docker E2E Test + +on: + workflow_call: + inputs: + image-tag: + required: true + type: string + runner: + required: false + type: string + default: linux-amd64-cpu8 + checkout-ref: + required: false + type: string + default: "" + gateway-artifact: + required: false + type: string + default: "" + external-driver-binary: + required: false + type: string + default: "" + suite-matrix: + required: false + type: string + default: >- + [{"suite":"python","cmd":"mise run --no-deps --skip-deps e2e:python","apt_packages":"","python_proto":true,"mcp":false},{"suite":"oidc-python","cmd":"mise run --no-deps --skip-deps e2e:oidc-python:docker","apt_packages":"","python_proto":true,"mcp":false},{"suite":"oidc-pkce-docker","cmd":"mise run --no-deps --skip-deps e2e:oidc-pkce:docker","apt_packages":"openssh-client","python_proto":false,"mcp":false},{"suite":"rust-docker","cmd":"mise run --no-deps --skip-deps e2e:rust","apt_packages":"openssh-client","python_proto":false,"mcp":false},{"suite":"mcp","cmd":"mise run --no-deps --skip-deps e2e:mcp","apt_packages":"","python_proto":false,"mcp":true}] + +permissions: + actions: read + contents: read + packages: read + +jobs: + e2e: + name: E2E (${{ matrix.suite }}) + runs-on: ${{ inputs.runner }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON(inputs.suite-matrix) }} + container: + image: ghcr.io/nvidia/openshell/ci:latest + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + options: --privileged + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /home/runner/_work:/home/runner/_work + env: + MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + IMAGE_TAG: ${{ inputs.image-tag }} + OPENSHELL_REGISTRY: ghcr.io/nvidia/openshell + OPENSHELL_SUPERVISOR_IMAGE: ${{ format('ghcr.io/nvidia/openshell/supervisor:{0}', inputs.image-tag) }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs.checkout-ref || github.sha }} + persist-credentials: false + + - uses: ./.github/actions/setup-e2e-cli + + - uses: ./.github/actions/setup-e2e-gateway + with: + artifact-name: ${{ inputs.gateway-artifact }} + + - if: inputs.external-driver-binary != '' + uses: ./.github/actions/setup-e2e-driver + with: + binary: ${{ inputs.external-driver-binary }} + + - name: Check out MCP conformance tests + if: matrix.mcp + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + repository: modelcontextprotocol/conformance + ref: b9041ea41b0188581803459dbae71bc7e02fd995 + path: .cache/mcp-conformance + persist-credentials: false + + - name: Install OS test dependencies + if: matrix.apt_packages != '' + env: + APT_PACKAGES: ${{ matrix.apt_packages }} + run: | + read -ra apt_packages <<< "$APT_PACKAGES" + apt-get update + apt-get install -y "${apt_packages[@]}" + rm -rf /var/lib/apt/lists/* + + - name: Log in to GHCR + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin + + - name: Install Python dependencies and generate protobuf stubs + if: matrix.python_proto + run: uv sync --frozen && mise run --no-deps python:proto + + - name: Run tests + env: + OPENSHELL_MCP_CONFORMANCE_CLIENT_IMAGE: ${{ format('openshell-mcp-conformance-client:{0}', inputs.image-tag) }} + run: ${{ matrix.cmd }} diff --git a/.github/workflows/e2e-kubernetes-test.yml b/.github/workflows/e2e-kubernetes-test.yml index 6e76da59fc..4498f9d44a 100644 --- a/.github/workflows/e2e-kubernetes-test.yml +++ b/.github/workflows/e2e-kubernetes-test.yml @@ -47,6 +47,21 @@ on: required: false type: string default: "v2026.4.25" + gateway-artifact: + description: "Optional prebuilt gateway artifact used to compose a local test image" + required: false + type: string + default: "" + external-driver-binary: + description: "Optional standalone compute driver binary used to compose a local test image" + required: false + type: string + default: "" + cluster-images: + description: "Space-separated published images to preload into kind" + required: false + type: string + default: "gateway supervisor" permissions: actions: read @@ -78,44 +93,27 @@ jobs: - name: Use prebuilt OpenShell CLI uses: ./.github/actions/setup-e2e-cli - - 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: Log in to GHCR - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: Create kind cluster - uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0 + - name: Use prebuilt OpenShell gateway + if: inputs.gateway-artifact != '' + uses: ./.github/actions/setup-e2e-gateway with: - cluster_name: ${{ env.KIND_CLUSTER_NAME }} - wait: 120s + artifact-name: ${{ inputs.gateway-artifact }} - # mise.toml sets KUBECONFIG="{{config_root}}/kubeconfig"; helm/kind-action - # writes to ~/.kube/config. Materialize the kind context at the mise path - # so `mise run e2e:kubernetes` (and the wrapper's `kubectl --context=...`) - # finds the kind cluster. - - name: Export kind kubeconfig to mise path - run: | - set -euo pipefail - kind get kubeconfig --name "$KIND_CLUSTER_NAME" > "$GITHUB_WORKSPACE/kubeconfig" - chmod 600 "$GITHUB_WORKSPACE/kubeconfig" + - name: Use prebuilt external compute driver + if: inputs.external-driver-binary != '' + uses: ./.github/actions/setup-e2e-driver + with: + binary: ${{ inputs.external-driver-binary }} - - name: Load gateway and supervisor images into kind - run: | - set -euo pipefail - for component in gateway supervisor; do - image="ghcr.io/nvidia/openshell/${component}:${{ inputs.image-tag }}" - archive="${RUNNER_TEMP:-/tmp}/openshell-${component}-linux-amd64.tar" - docker pull --platform linux/amd64 "$image" - docker image save --platform linux/amd64 --output "$archive" "$image" - kind load image-archive "$archive" --name "$KIND_CLUSTER_NAME" - done + - name: Set up kind cluster + uses: ./.github/actions/setup-e2e-kind + with: + cluster-name: ${{ env.KIND_CLUSTER_NAME }} + image-tag: ${{ inputs.image-tag }} + images: ${{ inputs.cluster-images }} + registry-username: ${{ github.actor }} + registry-password: ${{ secrets.GITHUB_TOKEN }} + mise-version: ${{ inputs.mise-version }} - name: Run Kubernetes E2E env: diff --git a/.github/workflows/e2e-podman-test.yml b/.github/workflows/e2e-podman-test.yml new file mode 100644 index 0000000000..61d6af2844 --- /dev/null +++ b/.github/workflows/e2e-podman-test.yml @@ -0,0 +1,91 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Podman E2E Test + +on: + workflow_call: + inputs: + image-tag: + required: true + type: string + checkout-ref: + required: false + type: string + default: "" + gateway-artifact: + required: false + type: string + default: "" + external-driver-binary: + required: false + type: string + default: "" + suite-matrix: + required: false + type: string + default: >- + [{"suite":"rootless","runner":"ubuntu-26.04","podman_major":"5","podman_package_version":"5.7.0+ds2-3build1","conmon_package_version":"2.1.13+ds1-2","cmd":"mise run --no-deps --skip-deps e2e:podman:rootless"},{"suite":"provider-refresh-keycloak","runner":"ubuntu-26.04","podman_major":"5","podman_package_version":"5.7.0+ds2-3build1","conmon_package_version":"2.1.13+ds1-2","cmd":"mise run --no-deps --skip-deps e2e:provider-refresh-keycloak"}] + +permissions: + actions: read + contents: read + packages: read + +jobs: + e2e: + name: E2E (rust-podman-${{ matrix.suite }}, ${{ matrix.runner }}) + runs-on: ${{ matrix.runner }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON(inputs.suite-matrix) }} + env: + IMAGE_TAG: ${{ inputs.image-tag }} + MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OPENSHELL_REGISTRY: ghcr.io/nvidia/openshell + OPENSHELL_SUPERVISOR_IMAGE: ${{ format('ghcr.io/nvidia/openshell/supervisor:{0}', inputs.image-tag) }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs.checkout-ref || github.sha }} + persist-credentials: false + + - uses: ./.github/actions/setup-e2e-cli + + - uses: ./.github/actions/setup-e2e-gateway + with: + artifact-name: ${{ inputs.gateway-artifact }} + + - if: inputs.external-driver-binary != '' + uses: ./.github/actions/setup-e2e-driver + with: + binary: ${{ inputs.external-driver-binary }} + + - uses: ./.github/actions/setup-e2e-podman + with: + podman-major: ${{ matrix.podman_major }} + podman-package-version: ${{ matrix.podman_package_version }} + conmon-package-version: ${{ matrix.conmon_package_version }} + + - name: Log in to GHCR + run: echo "${{ secrets.GITHUB_TOKEN }}" | podman login ghcr.io -u "${{ github.actor }}" --password-stdin + + - name: Run tests + run: ${{ matrix.cmd }} + + - name: Print AppArmor denials + if: always() + run: sudo dmesg | grep -E 'apparmor=.*DENIED|profile="unprivileged_userns"' | tail -100 || true + + - name: Fail on pasta SIGTERM AppArmor denial + if: always() + run: | + set -euo pipefail + denials="$(sudo dmesg | grep -E 'profile="pasta".*requested_mask="receive".*signal=term.*peer="podman"' || true)" + if [ -n "${denials}" ]; then + echo "::error::pasta denied Podman's SIGTERM; Podman will use its SIGKILL fallback" + printf '%s\n' "${denials}" + exit 1 + fi diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml deleted file mode 100644 index da374fe522..0000000000 --- a/.github/workflows/e2e-test.yml +++ /dev/null @@ -1,369 +0,0 @@ -name: E2E Test - -on: - workflow_call: - inputs: - image-tag: - description: "Image tag to test (typically the commit SHA)" - required: true - type: string - runner: - description: "GitHub Actions runner label" - required: false - type: string - default: "linux-amd64-cpu8" - checkout-ref: - description: "Git ref to check out for test inputs (defaults to the workflow SHA)" - required: false - type: string - default: "" - -permissions: - actions: read - contents: read - packages: read - -jobs: - e2e: - name: "E2E (${{ matrix.suite }})" - runs-on: ${{ inputs.runner }} - timeout-minutes: 30 - strategy: - fail-fast: false - matrix: - include: - - suite: python - cmd: "mise run --no-deps --skip-deps e2e:python" - apt_packages: "" - - suite: oidc-python - cmd: "mise run --no-deps --skip-deps e2e:oidc-python:docker" - apt_packages: "" - - suite: oidc-pkce-docker - cmd: "mise run --no-deps --skip-deps e2e:oidc-pkce:docker" - apt_packages: "openssh-client" - - suite: rust-docker - cmd: "mise run --no-deps --skip-deps e2e:rust" - apt_packages: "openssh-client" - # External first-party driver lanes stay disabled while their - # standalone binaries are not release artifacts. - - suite: mcp - cmd: "mise run --no-deps --skip-deps e2e:mcp" - apt_packages: "" - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - options: --privileged - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - /home/runner/_work:/home/runner/_work - env: - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - IMAGE_TAG: ${{ inputs.image-tag }} - OPENSHELL_REGISTRY: ghcr.io/nvidia/openshell - OPENSHELL_REGISTRY_HOST: ghcr.io - OPENSHELL_REGISTRY_NAMESPACE: nvidia/openshell - OPENSHELL_REGISTRY_USERNAME: ${{ github.actor }} - OPENSHELL_REGISTRY_PASSWORD: ${{ 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 - - - name: Use prebuilt OpenShell gateway - uses: ./.github/actions/setup-e2e-gateway - - - name: Check out MCP conformance tests - if: matrix.suite == 'mcp' - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - repository: modelcontextprotocol/conformance - # Pin after v0.1.16 to include the tools_call client scenario fix. - ref: b9041ea41b0188581803459dbae71bc7e02fd995 - path: .cache/mcp-conformance - persist-credentials: false - - - name: Install OS test dependencies - if: matrix.apt_packages != '' - env: - APT_PACKAGES: ${{ matrix.apt_packages }} - run: apt-get update && apt-get install -y ${APT_PACKAGES} && rm -rf /var/lib/apt/lists/* - - - name: Log in to GHCR with Docker - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: Install Python dependencies and generate protobuf stubs - if: matrix.suite == 'python' || matrix.suite == 'oidc-python' - run: uv sync --frozen && mise run --no-deps python:proto - - - name: Run tests - env: - OPENSHELL_SUPERVISOR_IMAGE: ${{ format('ghcr.io/nvidia/openshell/supervisor:{0}', inputs.image-tag) }} - OPENSHELL_MCP_CONFORMANCE_CLIENT_IMAGE: ${{ format('openshell-mcp-conformance-client:{0}', inputs.image-tag) }} - run: ${{ matrix.cmd }} - - e2e-podman-rootless: - name: E2E (rust-podman-${{ matrix.suite }}, ${{ matrix.runner }}) - # Run directly on the Ubuntu host so the test observes the host's AppArmor - # and unprivileged-user-namespace policy. A privileged job container masks - # the restrictions that production rootless Podman installations enforce. - # Ubuntu 26.04 provides the supported Podman 5.x and pasta combination. - # Re-add older/slirp4netns environments when direct callbacks through a - # rootless-network namespace relay are supported. - runs-on: ${{ matrix.runner }} - timeout-minutes: 30 - strategy: - fail-fast: false - matrix: - include: - # Keep package versions explicit so hosted-runner tool overrides - # cannot silently change the supported test environment. - - suite: rootless - runner: ubuntu-26.04 - podman_major: "5" - podman_package_version: "5.7.0+ds2-3build1" - conmon_package_version: "2.1.13+ds1-2" - cmd: "mise run --no-deps --skip-deps e2e:podman:rootless" - # The external-driver suite stays disabled while the standalone - # Podman driver is not a release artifact. - - suite: provider-refresh-keycloak - runner: ubuntu-26.04 - podman_major: "5" - podman_package_version: "5.7.0+ds2-3build1" - conmon_package_version: "2.1.13+ds1-2" - cmd: "mise run --no-deps --skip-deps e2e:provider-refresh-keycloak" - env: - IMAGE_TAG: ${{ inputs.image-tag }} - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENSHELL_REGISTRY: ghcr.io/nvidia/openshell - OPENSHELL_REGISTRY_HOST: ghcr.io - OPENSHELL_REGISTRY_NAMESPACE: nvidia/openshell - OPENSHELL_REGISTRY_USERNAME: ${{ github.actor }} - OPENSHELL_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} - OPENSHELL_SUPERVISOR_IMAGE: ${{ format('ghcr.io/nvidia/openshell/supervisor:{0}', inputs.image-tag) }} - 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 - - - name: Use prebuilt OpenShell gateway - uses: ./.github/actions/setup-e2e-gateway - - - 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 Podman and build dependencies - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends \ - apparmor \ - build-essential \ - fuse-overlayfs \ - libssl-dev \ - openssh-client \ - passt \ - pkg-config \ - "conmon=${{ matrix.conmon_package_version }}" \ - "podman=${{ matrix.podman_package_version }}" \ - uidmap - # Hosted runners can place newer Podman and conmon binaries under - # /usr/local ahead of Ubuntu's packages. Select the distro CLI and - # use Podman's supported final config override for its conmon path. - podman_config="${RUNNER_TEMP}/openshell-containers.conf" - printf '%s\n' \ - '[engine]' \ - 'conmon_path = ["/usr/bin/conmon"]' \ - > "${podman_config}" - echo "/usr/bin" >> "${GITHUB_PATH}" - echo "CONTAINERS_CONF_OVERRIDE=${podman_config}" >> "${GITHUB_ENV}" - - - name: Allow pasta to receive Podman stop signals - # Ubuntu's packaged pasta profile currently blocks this signal, forcing - # Podman to wait for its SIGKILL fallback. Keep this narrow allowance - # until the distribution package includes the upstream profile fix. - run: | - set -euo pipefail - profile=/etc/apparmor.d/usr.bin.pasta - rule=' signal (receive) peer=podman,' - if ! sudo grep -Fqx "${rule}" "${profile}"; then - sudo sed -i '\|^ include $|a\ signal (receive) peer=podman,' "${profile}" - fi - sudo grep -Fqx "${rule}" "${profile}" - sudo apparmor_parser --replace "${profile}" - - - name: Configure rootless Podman - run: | - set -euo pipefail - if ! grep -q "^${USER}:" /etc/subuid; then - sudo usermod --add-subuids 100000-165535 "$USER" - fi - if ! grep -q "^${USER}:" /etc/subgid; then - sudo usermod --add-subgids 100000-165535 "$USER" - fi - runtime_dir="/run/user/$(id -u)" - sudo install -d -m 0700 -o "$(id -u)" -g "$(id -g)" "$runtime_dir" - echo "XDG_RUNTIME_DIR=$runtime_dir" >> "$GITHUB_ENV" - - - name: Verify rootless Podman environment - run: | - set -euo pipefail - podman_version="$(podman version --format '{{.Client.Version}}')" - case "$podman_version" in - "${{ matrix.podman_major }}".*) ;; - *) echo "ERROR: expected Podman ${{ matrix.podman_major }}.x, found $podman_version" >&2; exit 1 ;; - esac - test "$(dpkg-query -W -f='${Version}' podman)" = "${{ matrix.podman_package_version }}" - test "$(dpkg-query -W -f='${Version}' conmon)" = "${{ matrix.conmon_package_version }}" - test "$(command -v podman)" = "/usr/bin/podman" - test "$(podman info --format '{{.Host.Conmon.Path}}')" = "/usr/bin/conmon" - test "$(podman info --format '{{.Host.Security.Rootless}}')" = "true" - test "$(podman info --format '{{.Host.RootlessNetworkCmd}}')" = "pasta" - test "$(sudo sysctl -n kernel.apparmor_restrict_unprivileged_userns)" = "1" - echo "=== host ===" - uname -a - echo "=== AppArmor ===" - cat /proc/self/attr/current - sudo aa-status || true - echo "=== Podman ===" - podman version - podman info --debug - - - name: Probe rootless capability bounding set - run: | - set -euo pipefail - probe="$RUNNER_TEMP/openshell-capbset-probe" - cc -static -O2 -Wall -Wextra -Werror \ - e2e/support/capbset-probe.c \ - -o "$probe" - podman run --rm \ - --cap-add=SETPCAP \ - --volume "$probe:/openshell-capbset-probe:ro" \ - docker.io/library/alpine:3.22 \ - /openshell-capbset-probe - - - name: Log in to GHCR with Podman - run: echo "${{ secrets.GITHUB_TOKEN }}" | podman login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: Run Podman E2E - run: ${{ matrix.cmd }} - - - name: Print AppArmor denials - if: always() - run: sudo dmesg | grep -E 'apparmor=.*DENIED|profile="unprivileged_userns"' | tail -100 || true - - - name: Fail on pasta SIGTERM AppArmor denial - if: always() - run: | - set -euo pipefail - denials="$(sudo dmesg | grep -E 'profile="pasta".*requested_mask="receive".*signal=term.*peer="podman"' || true)" - if [ -n "${denials}" ]; then - echo "::error::pasta denied Podman's SIGTERM; Podman will use its SIGKILL fallback" - printf '%s\n' "${denials}" - exit 1 - fi - - e2e-vm: - name: E2E (rust-vm-${{ matrix.suite }}) - # libkrun needs KVM, so this job must run directly on a GitHub-hosted - # Linux VM. GitHub-hosted macOS runners do not support nested - # virtualization, and a job container would hide the host KVM device. - runs-on: ubuntu-24.04 - timeout-minutes: 90 - strategy: - fail-fast: false - matrix: - include: - - suite: managed - cmd: "mise run --no-deps --skip-deps e2e:vm" - # The unmanaged external-driver suite stays disabled; the released - # VM driver is exercised through the managed gateway path above. - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - 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 - - - name: Use prebuilt OpenShell gateway - uses: ./.github/actions/setup-e2e-gateway - - - name: Use prebuilt OpenShell VM driver - uses: ./.github/actions/setup-e2e-vm-driver - - - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends \ - build-essential \ - libssl-dev \ - openssh-client \ - pkg-config \ - socat \ - zstd - - - name: Enable KVM access - run: | - set -euo pipefail - if [[ ! -c /dev/kvm ]]; then - echo "::error::The GitHub-hosted runner did not expose /dev/kvm" - lscpu - grep -m1 -E '^(flags|Features)' /proc/cpuinfo || true - ls -la /dev - exit 1 - fi - - # Package installation can restart systemd-udevd, which reapplies - # the default root:kvm 0660 mode. Install a persistent rule after - # dependencies so later udev events preserve runner access. - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ - | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --settle --name-match=kvm - - ls -l /dev/kvm - exec 3<>/dev/kvm - exec 3>&- - - - name: Validate VM host tools - run: | - command -v mke2fs - command -v mkfs.ext4 - command -v debugfs - - - 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: Cache Rust target and registry - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - with: - shared-key: e2e-vm-linux-amd64 - cache-on-failure: "true" - - - name: Run VM E2E - run: ${{ matrix.cmd }} diff --git a/.github/workflows/e2e-vm-test.yml b/.github/workflows/e2e-vm-test.yml new file mode 100644 index 0000000000..57e04c32b3 --- /dev/null +++ b/.github/workflows/e2e-vm-test.yml @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: VM E2E Test + +on: + workflow_call: + inputs: + checkout-ref: + required: false + type: string + default: "" + gateway-artifact: + required: false + type: string + default: "" + suite-name: + required: false + type: string + default: managed + e2e-task: + required: false + type: string + default: e2e:vm + +permissions: + actions: read + contents: read + packages: read + +jobs: + e2e: + name: E2E (rust-vm-${{ inputs.suite-name }}) + runs-on: ubuntu-24.04 + timeout-minutes: 90 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs.checkout-ref || github.sha }} + persist-credentials: false + + - uses: ./.github/actions/setup-e2e-cli + + - uses: ./.github/actions/setup-e2e-gateway + with: + artifact-name: ${{ inputs.gateway-artifact }} + + - uses: ./.github/actions/setup-e2e-vm-driver + + - uses: ./.github/actions/setup-e2e-vm + + - name: Run tests + env: + E2E_TASK: ${{ inputs.e2e-task }} + run: mise run --no-deps --skip-deps "$E2E_TASK" diff --git a/.github/workflows/package-release-binaries.yml b/.github/workflows/package-release-binaries.yml new file mode 100644 index 0000000000..f1900f3c35 --- /dev/null +++ b/.github/workflows/package-release-binaries.yml @@ -0,0 +1,77 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Package Release Binaries + +on: + workflow_call: + +permissions: + actions: read + contents: read + +jobs: + package: + name: Package ${{ matrix.artifact }} + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + include: + - artifact: openshell-x86_64-unknown-linux-musl + binary: openshell + package: cli-linux-amd64 + - artifact: openshell-aarch64-unknown-linux-musl + binary: openshell + package: cli-linux-arm64 + - artifact: openshell-aarch64-apple-darwin + binary: openshell + package: cli-macos + - artifact: openshell-gateway-x86_64-unknown-linux-gnu + binary: openshell-gateway + package: gateway-binary-linux-amd64 + - artifact: openshell-gateway-aarch64-unknown-linux-gnu + binary: openshell-gateway + package: gateway-binary-linux-arm64 + - artifact: openshell-gateway-aarch64-apple-darwin + binary: openshell-gateway + package: gateway-binary-macos + - artifact: openshell-sandbox-x86_64-unknown-linux-musl + binary: openshell-sandbox + package: supervisor-binary-linux-amd64 + - artifact: openshell-sandbox-aarch64-unknown-linux-musl + binary: openshell-sandbox + package: supervisor-binary-linux-arm64 + - artifact: openshell-driver-vm-x86_64-unknown-linux-gnu + binary: openshell-driver-vm + package: driver-vm-linux-amd64 + - artifact: openshell-driver-vm-aarch64-unknown-linux-gnu + binary: openshell-driver-vm + package: driver-vm-linux-arm64 + - artifact: openshell-driver-vm-aarch64-apple-darwin + binary: openshell-driver-vm + package: driver-vm-macos + steps: + - name: Download ${{ matrix.artifact }} + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ matrix.artifact }} + path: raw + + - name: Create archive + env: + ARTIFACT: ${{ matrix.artifact }} + BINARY: ${{ matrix.binary }} + run: | + set -euo pipefail + chmod +x "raw/${BINARY}" + tar -czf "${ARTIFACT}.tar.gz" -C raw "${BINARY}" + + - name: Upload ${{ matrix.package }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: ${{ matrix.package }} + path: ${{ matrix.artifact }}.tar.gz + retention-days: 5 + if-no-files-found: error diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml index 500fcb4be6..dbf5e1ee18 100644 --- a/.github/workflows/release-dev.yml +++ b/.github/workflows/release-dev.yml @@ -53,36 +53,112 @@ jobs: echo "rpm_version=$(uv run python tasks/scripts/release.py get-version --rpm-version)" >> "$GITHUB_OUTPUT" echo "rpm_release=$(uv run python tasks/scripts/release.py get-version --rpm-release)" >> "$GITHUB_OUTPUT" + build-cli: + needs: compute-versions + permissions: + contents: read + uses: ./.github/workflows/build-platform-binaries.yml + with: + component: cli + cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} + image-tag: dev + secrets: inherit + build-gateway: - needs: [compute-versions] - uses: ./.github/workflows/docker-build.yml + needs: compute-versions + permissions: + contents: read + uses: ./.github/workflows/build-platform-binaries.yml with: component: gateway cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} - auditable: true + image-tag: dev + secrets: inherit - build-supervisor: - needs: [compute-versions] + build-sandbox: + needs: compute-versions + permissions: + contents: read + uses: ./.github/workflows/build-platform-binaries.yml + with: + component: sandbox-musl + cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} + image-tag: dev + secrets: inherit + + build-vm-driver: + needs: [compute-versions, build-sandbox] + permissions: + contents: read + uses: ./.github/workflows/build-vm-driver.yml + with: + cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} + image-tag: dev + secrets: inherit + + package-binaries: + needs: [build-cli, build-gateway, build-sandbox, build-vm-driver] + permissions: + actions: read + contents: read + uses: ./.github/workflows/package-release-binaries.yml + + build-gateway-image: + needs: build-gateway + permissions: + contents: read + packages: write + uses: ./.github/workflows/docker-build.yml + with: + component: gateway + binary: openshell-gateway + target-suffix: unknown-linux-gnu + secrets: inherit + + build-supervisor-image: + needs: build-sandbox + permissions: + contents: read + packages: write uses: ./.github/workflows/docker-build.yml with: component: supervisor - cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} - auditable: true + binary: openshell-sandbox + target-suffix: unknown-linux-musl + secrets: inherit - e2e: - needs: [build-gateway, build-supervisor] + docker-e2e: + needs: [build-cli, build-gateway, build-supervisor-image] permissions: actions: read contents: read packages: read - uses: ./.github/workflows/e2e-test.yml + uses: ./.github/workflows/e2e-docker-test.yml with: image-tag: ${{ github.sha }} runner: linux-arm64-cpu8 + podman-e2e: + needs: [build-cli, build-gateway, build-supervisor-image] + permissions: + actions: read + contents: read + packages: read + uses: ./.github/workflows/e2e-podman-test.yml + with: + image-tag: ${{ github.sha }} + + vm-e2e: + needs: [build-cli, build-gateway, build-vm-driver] + permissions: + actions: read + contents: read + packages: read + uses: ./.github/workflows/e2e-vm-test.yml + tag-ghcr-dev: name: Tag GHCR Images as Dev - needs: [build-gateway, build-supervisor, release-dev] + needs: [build-gateway-image, build-supervisor-image, release-dev] runs-on: linux-amd64-cpu8 timeout-minutes: 10 steps: @@ -138,456 +214,10 @@ jobs: path: target/wheels/*.whl retention-days: 5 - # --------------------------------------------------------------------------- - # Build CLI binaries (Linux musl — static, native on each arch) - # - # Builds run directly on the CI host (glibc Ubuntu). Zig provides musl - # C/C++ toolchains for bundled-z3 and ring, and is also used as the linker. - # --------------------------------------------------------------------------- - build-cli-linux: - name: Build CLI (Linux ${{ matrix.arch }}) - needs: [compute-versions] - strategy: - matrix: - include: - - arch: amd64 - runner: linux-amd64-cpu8 - target: x86_64-unknown-linux-musl - zig_target: x86_64-linux-musl - - arch: arm64 - runner: linux-arm64-cpu8 - target: aarch64-unknown-linux-musl - zig_target: aarch64-linux-musl - runs-on: ${{ matrix.runner }} - timeout-minutes: 60 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - options: --privileged - env: - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENSHELL_IMAGE_TAG: dev - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - - - name: Mark workspace safe for git - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Fetch tags - run: git fetch --tags --force - - - name: Install tools - run: mise install --locked - - - name: Cache Rust target and registry - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - with: - shared-key: cli-musl-${{ matrix.arch }} - cache-directories: .cache/sccache - cache-targets: "true" - - - name: Add Rust musl target - run: mise x -- rustup target add ${{ matrix.target }} - - - name: Set up zig musl wrappers - run: | - set -euo pipefail - ZIG="$(mise which zig)" - ZIG_TARGET="${{ matrix.zig_target }}" - mkdir -p /tmp/zig-musl - - # cc-rs injects --target= (for example - # aarch64-unknown-linux-musl), which zig does not parse. Strip any - # caller-provided --target and use the wrapper's zig-native target. - for tool in cc c++; do - printf '#!/bin/bash\nargs=()\nfor arg in "$@"; do\n case "$arg" in\n --target=*) ;;\n *) args+=("$arg") ;;\n esac\ndone\nexec "%s" %s --target=%s "${args[@]}"\n' \ - "$ZIG" "$tool" "$ZIG_TARGET" > "/tmp/zig-musl/${tool}" - chmod +x "/tmp/zig-musl/${tool}" - done - - TARGET_ENV=$(echo "${{ matrix.target }}" | tr '-' '_') - TARGET_ENV_UPPER=${TARGET_ENV^^} - - # Use zig for C/C++ compilation and final linking. - echo "CC_${TARGET_ENV}=/tmp/zig-musl/cc" >> "$GITHUB_ENV" - echo "CXX_${TARGET_ENV}=/tmp/zig-musl/c++" >> "$GITHUB_ENV" - echo "CARGO_TARGET_${TARGET_ENV_UPPER}_LINKER=/tmp/zig-musl/cc" >> "$GITHUB_ENV" - - # Let zig own CRT/startfiles to avoid duplicate _start symbols. - echo "CARGO_TARGET_${TARGET_ENV_UPPER}_RUSTFLAGS=-Clink-self-contained=no" >> "$GITHUB_ENV" - - # z3 built with zig c++ uses libc++ symbols (std::__1::*). - # Override z3-sys default (stdc++) so Rust links the matching runtime. - echo "CXXSTDLIB=c++" >> "$GITHUB_ENV" - - - name: Patch workspace version - if: needs.compute-versions.outputs.cargo_version != '' - run: | - set -euo pipefail - sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ needs.compute-versions.outputs.cargo_version }}"'"/}' Cargo.toml - - - name: Build ${{ matrix.target }} - run: mise x -- cargo build --release --target ${{ matrix.target }} -p openshell-cli - - - name: sccache stats - if: always() - run: mise x -- sccache --show-stats - - - name: Package binary - run: | - set -euo pipefail - mkdir -p artifacts - tar -czf artifacts/openshell-${{ matrix.target }}.tar.gz \ - -C target/${{ matrix.target }}/release openshell - ls -lh artifacts/ - - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: cli-linux-${{ matrix.arch }} - path: artifacts/*.tar.gz - retention-days: 5 - - # --------------------------------------------------------------------------- - # Build CLI binary (macOS aarch64 via osxcross) - # --------------------------------------------------------------------------- - build-cli-macos: - name: Build CLI (macOS) - needs: [compute-versions] - runs-on: linux-amd64-cpu8 - timeout-minutes: 60 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - options: --privileged - volumes: - - /var/run/docker.sock:/var/run/docker.sock - env: - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - - - name: Mark workspace safe for git - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Fetch tags - run: git fetch --tags --force - - - name: Log in to GHCR - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: Set up Docker Buildx - uses: ./.github/actions/setup-buildx - - - name: Build macOS binary via Docker - run: | - set -euo pipefail - docker buildx build \ - --file deploy/docker/Dockerfile.cli-macos \ - --build-arg OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" \ - --build-arg OPENSHELL_IMAGE_TAG=dev \ - --build-arg CARGO_TARGET_CACHE_SCOPE="${{ github.sha }}" \ - --target binary \ - --output type=local,dest=out/ \ - . - - - name: Package binary - run: | - set -euo pipefail - mkdir -p artifacts - tar -czf artifacts/openshell-aarch64-apple-darwin.tar.gz \ - -C out openshell - ls -lh artifacts/ - - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: cli-macos - path: artifacts/*.tar.gz - retention-days: 5 - - # --------------------------------------------------------------------------- - # Build standalone gateway binaries (Linux GNU — glibc 2.28 floor) - # --------------------------------------------------------------------------- - build-gateway-binary-linux: - name: Build Gateway Binary (Linux ${{ matrix.arch }}) - needs: [compute-versions] - strategy: - matrix: - include: - - arch: amd64 - runner: linux-amd64-cpu8 - target: x86_64-unknown-linux-gnu - zig_target: x86_64-unknown-linux-gnu.2.28 - - arch: arm64 - runner: linux-arm64-cpu8 - target: aarch64-unknown-linux-gnu - zig_target: aarch64-unknown-linux-gnu.2.28 - runs-on: ${{ matrix.runner }} - timeout-minutes: 60 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - options: --privileged - env: - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - - - name: Mark workspace safe for git - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Fetch tags - run: git fetch --tags --force - - - name: Install tools - run: mise install --locked - - - name: Cache Rust target and registry - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - with: - shared-key: gateway-binary-gnu-${{ matrix.arch }}-zig-wrapper-${{ hashFiles('tasks/scripts/setup-zig-cc-wrapper.sh') }} - cache-directories: .cache/sccache - cache-targets: "true" - - - name: Patch workspace version - if: needs.compute-versions.outputs.cargo_version != '' - run: | - set -euo pipefail - sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ needs.compute-versions.outputs.cargo_version }}"'"/}' Cargo.toml - - - name: Set up Zig C/C++ wrappers - run: tasks/scripts/setup-zig-cc-wrapper.sh ${{ matrix.zig_target }} ${{ matrix.zig_target }} /tmp/zig-gnu - - - name: Build ${{ matrix.zig_target }} - env: - OPENSHELL_IMAGE_TAG: ${{ github.sha }} - run: | - set -euo pipefail - mise x -- rustup target add ${{ matrix.target }} - mise x -- cargo zigbuild --release --target ${{ matrix.zig_target }} -p openshell-server --bin openshell-gateway --features bundled-z3 - mkdir -p artifacts/bin - install -m 0755 target/${{ matrix.target }}/release/openshell-gateway artifacts/bin/openshell-gateway - - - name: Verify packaged binary - run: | - set -euo pipefail - OUTPUT="$(artifacts/bin/openshell-gateway --version)" - echo "$OUTPUT" - grep -q '^openshell-gateway ' <<<"$OUTPUT" - ldd artifacts/bin/openshell-gateway || true - if ldd artifacts/bin/openshell-gateway | grep -q 'libz3'; then - echo "gateway binary must not depend on shared libz3; build with bundled-z3" >&2 - exit 1 - fi - - - name: Verify glibc symbol floor - run: tasks/scripts/verify-glibc-symbols.sh 2.28 artifacts/bin/openshell-gateway - - - name: sccache stats - if: always() - run: mise x -- sccache --show-stats - - - name: Package binary - run: | - set -euo pipefail - mkdir -p artifacts - tar -czf artifacts/openshell-gateway-${{ matrix.target }}.tar.gz \ - -C artifacts/bin openshell-gateway - ls -lh artifacts/ - - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: gateway-binary-linux-${{ matrix.arch }} - path: artifacts/*.tar.gz - retention-days: 5 - - # --------------------------------------------------------------------------- - # Build standalone gateway binary (macOS aarch64 via osxcross) - # --------------------------------------------------------------------------- - build-gateway-binary-macos: - name: Build Gateway Binary (macOS) - needs: [compute-versions] - runs-on: linux-amd64-cpu8 - timeout-minutes: 60 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - options: --privileged - volumes: - - /var/run/docker.sock:/var/run/docker.sock - env: - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - - - name: Mark workspace safe for git - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Fetch tags - run: git fetch --tags --force - - - name: Log in to GHCR - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: Set up Docker Buildx - uses: ./.github/actions/setup-buildx - - - name: Build macOS binary via Docker - run: | - set -euo pipefail - docker buildx build \ - --file deploy/docker/Dockerfile.gateway-macos \ - --build-arg OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" \ - --build-arg OPENSHELL_IMAGE_TAG=dev \ - --build-arg CARGO_TARGET_CACHE_SCOPE="${{ github.sha }}" \ - --target binary \ - --output type=local,dest=out/ \ - . - - - name: Verify packaged binary shape - run: | - set -euo pipefail - test -x out/openshell-gateway - - - name: Package binary - run: | - set -euo pipefail - mkdir -p artifacts - tar -czf artifacts/openshell-gateway-aarch64-apple-darwin.tar.gz \ - -C out openshell-gateway - ls -lh artifacts/ - - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: gateway-binary-macos - path: artifacts/*.tar.gz - retention-days: 5 - - # --------------------------------------------------------------------------- - # Build standalone supervisor binaries (Linux GNU — native on each arch) - # --------------------------------------------------------------------------- - build-supervisor-binary-linux: - name: Build Supervisor Binary (Linux ${{ matrix.arch }}) - needs: [compute-versions] - strategy: - matrix: - include: - - arch: amd64 - runner: linux-amd64-cpu8 - target: x86_64-unknown-linux-gnu - - arch: arm64 - runner: linux-arm64-cpu8 - target: aarch64-unknown-linux-gnu - runs-on: ${{ matrix.runner }} - timeout-minutes: 60 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - options: --privileged - env: - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - - - name: Mark workspace safe for git - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Fetch tags - run: git fetch --tags --force - - - name: Install tools - run: mise install --locked - - - name: Cache Rust target and registry - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - with: - shared-key: supervisor-binary-gnu-${{ matrix.arch }} - cache-directories: .cache/sccache - cache-targets: "true" - - - name: Patch workspace version - if: needs.compute-versions.outputs.cargo_version != '' - run: | - set -euo pipefail - sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ needs.compute-versions.outputs.cargo_version }}"'"/}' Cargo.toml - - - name: Build ${{ matrix.target }} - run: | - set -euo pipefail - mise x -- cargo build --release --target ${{ matrix.target }} -p openshell-sandbox --bin openshell-sandbox - - - name: Verify packaged binary - run: | - set -euo pipefail - OUTPUT="$(target/${{ matrix.target }}/release/openshell-sandbox --version)" - echo "$OUTPUT" - grep -q '^openshell-sandbox ' <<<"$OUTPUT" - - - name: sccache stats - if: always() - run: mise x -- sccache --show-stats - - - name: Package binary - run: | - set -euo pipefail - mkdir -p artifacts - tar -czf artifacts/openshell-sandbox-${{ matrix.target }}.tar.gz \ - -C target/${{ matrix.target }}/release openshell-sandbox - ls -lh artifacts/ - - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: supervisor-binary-linux-${{ matrix.arch }} - path: artifacts/*.tar.gz - retention-days: 5 - - build-driver-vm-linux: - name: Build Driver VM Linux - needs: [compute-versions] - uses: ./.github/workflows/driver-vm-linux.yml - with: - cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} - image-tag: dev - checkout-ref: ${{ github.sha }} - secrets: inherit - - build-driver-vm-macos: - name: Build Driver VM macOS - needs: [compute-versions] - uses: ./.github/workflows/driver-vm-macos.yml - with: - cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} - image-tag: dev - checkout-ref: ${{ github.sha }} - secrets: inherit build-deb: name: Build Debian Packages - needs: [compute-versions, build-cli-linux, build-gateway-binary-linux, build-driver-vm-linux] + needs: [compute-versions, build-cli, build-gateway, build-vm-driver] uses: ./.github/workflows/deb-package.yml with: deb-version: ${{ needs.compute-versions.outputs.deb_version }} @@ -596,7 +226,7 @@ jobs: build-snap: name: Build Snap - needs: [compute-versions, build-cli-linux, build-gateway-binary-linux, build-supervisor-binary-linux] + needs: [compute-versions, build-cli, build-gateway, build-sandbox] uses: ./.github/workflows/snap-package.yml with: checkout-ref: ${{ github.sha }} @@ -607,7 +237,7 @@ jobs: build-rpm: name: Build RPM Packages - needs: [compute-versions, build-cli-linux, build-gateway-binary-linux] + needs: [compute-versions, build-cli, build-gateway] uses: ./.github/workflows/rpm-package.yml with: checkout-ref: ${{ github.sha }} @@ -618,7 +248,7 @@ jobs: smoke-linux-dev-artifacts: name: Smoke Linux Dev Artifacts (${{ matrix.name }}) - needs: [build-gateway-binary-linux, build-driver-vm-linux, build-deb, build-rpm, build-python-wheel] + needs: [package-binaries, build-deb, build-rpm, build-python-wheel] timeout-minutes: 20 strategy: fail-fast: false @@ -712,7 +342,7 @@ jobs: # --------------------------------------------------------------------------- release-dev: name: Release Dev - needs: [compute-versions, build-cli-linux, build-cli-macos, build-gateway-binary-linux, build-gateway-binary-macos, build-supervisor-binary-linux, build-python-wheel, e2e, build-driver-vm-linux, build-driver-vm-macos, build-deb, build-rpm, build-snap, smoke-linux-dev-artifacts] + needs: [compute-versions, package-binaries, build-python-wheel, docker-e2e, podman-e2e, vm-e2e, build-deb, build-rpm, build-snap, smoke-linux-dev-artifacts] runs-on: linux-amd64-cpu8 timeout-minutes: 10 permissions: @@ -839,8 +469,8 @@ jobs: openshell-gateway-aarch64-apple-darwin.tar.gz > openshell-gateway-checksums-sha256.txt cat openshell-gateway-checksums-sha256.txt sha256sum \ - openshell-sandbox-x86_64-unknown-linux-gnu.tar.gz \ - openshell-sandbox-aarch64-unknown-linux-gnu.tar.gz > openshell-sandbox-checksums-sha256.txt + openshell-sandbox-x86_64-unknown-linux-musl.tar.gz \ + openshell-sandbox-aarch64-unknown-linux-musl.tar.gz > openshell-sandbox-checksums-sha256.txt cat openshell-sandbox-checksums-sha256.txt - name: Generate Homebrew formula @@ -945,8 +575,8 @@ jobs: release/openshell-gateway-x86_64-unknown-linux-gnu.tar.gz release/openshell-gateway-aarch64-unknown-linux-gnu.tar.gz release/openshell-gateway-aarch64-apple-darwin.tar.gz - release/openshell-sandbox-x86_64-unknown-linux-gnu.tar.gz - release/openshell-sandbox-aarch64-unknown-linux-gnu.tar.gz + release/openshell-sandbox-x86_64-unknown-linux-musl.tar.gz + release/openshell-sandbox-aarch64-unknown-linux-musl.tar.gz release/openshell-driver-vm-x86_64-unknown-linux-gnu.tar.gz release/openshell-driver-vm-aarch64-unknown-linux-gnu.tar.gz release/openshell-driver-vm-aarch64-apple-darwin.tar.gz diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 74be300b0c..c884134e6d 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -73,41 +73,124 @@ jobs: echo "semver=${RELEASE_TAG#v}" >> "$GITHUB_OUTPUT" echo "source_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + build-cli: + needs: compute-versions + permissions: + contents: read + uses: ./.github/workflows/build-platform-binaries.yml + with: + component: cli + cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} + image-tag: ${{ needs.compute-versions.outputs.semver }} + checkout-ref: ${{ inputs.tag || github.ref }} + secrets: inherit + build-gateway: - needs: [compute-versions] - uses: ./.github/workflows/docker-build.yml + needs: compute-versions + permissions: + contents: read + uses: ./.github/workflows/build-platform-binaries.yml with: component: gateway cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} + image-tag: ${{ needs.compute-versions.outputs.semver }} + checkout-ref: ${{ inputs.tag || github.ref }} + secrets: inherit + + build-sandbox: + needs: compute-versions + permissions: + contents: read + uses: ./.github/workflows/build-platform-binaries.yml + with: + component: sandbox-musl + cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} + image-tag: ${{ needs.compute-versions.outputs.semver }} + checkout-ref: ${{ inputs.tag || github.ref }} + secrets: inherit + + build-vm-driver: + needs: [compute-versions, build-sandbox] + permissions: + contents: read + uses: ./.github/workflows/build-vm-driver.yml + with: + cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} + image-tag: ${{ needs.compute-versions.outputs.semver }} + checkout-ref: ${{ inputs.tag || github.ref }} + secrets: inherit + + package-binaries: + needs: [build-cli, build-gateway, build-sandbox, build-vm-driver] + permissions: + actions: read + contents: read + uses: ./.github/workflows/package-release-binaries.yml + + build-gateway-image: + needs: [compute-versions, build-gateway] + permissions: + contents: read + packages: write + uses: ./.github/workflows/docker-build.yml + with: + component: gateway + binary: openshell-gateway + target-suffix: unknown-linux-gnu image-tag: ${{ needs.compute-versions.outputs.source_sha }} checkout-ref: ${{ inputs.tag || github.ref }} - auditable: true + secrets: inherit - build-supervisor: - needs: [compute-versions] + build-supervisor-image: + needs: [compute-versions, build-sandbox] + permissions: + contents: read + packages: write uses: ./.github/workflows/docker-build.yml with: component: supervisor - cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} + binary: openshell-sandbox + target-suffix: unknown-linux-musl image-tag: ${{ needs.compute-versions.outputs.source_sha }} checkout-ref: ${{ inputs.tag || github.ref }} - auditable: true + secrets: inherit - e2e: - needs: [compute-versions, build-gateway, build-supervisor] + docker-e2e: + needs: [compute-versions, build-cli, build-gateway, build-supervisor-image] permissions: actions: read contents: read packages: read - uses: ./.github/workflows/e2e-test.yml + uses: ./.github/workflows/e2e-docker-test.yml with: image-tag: ${{ needs.compute-versions.outputs.source_sha }} checkout-ref: ${{ inputs.tag || github.ref }} runner: linux-arm64-cpu8 + podman-e2e: + needs: [compute-versions, build-cli, build-gateway, build-supervisor-image] + permissions: + actions: read + contents: read + packages: read + uses: ./.github/workflows/e2e-podman-test.yml + with: + image-tag: ${{ needs.compute-versions.outputs.source_sha }} + checkout-ref: ${{ inputs.tag || github.ref }} + + vm-e2e: + needs: [compute-versions, build-cli, build-gateway, build-vm-driver] + permissions: + actions: read + contents: read + packages: read + uses: ./.github/workflows/e2e-vm-test.yml + with: + checkout-ref: ${{ inputs.tag || github.ref }} + tag-ghcr-release: name: Tag GHCR Images for Release - needs: [compute-versions, build-gateway, build-supervisor, release] + needs: [compute-versions, build-gateway-image, build-supervisor-image, release] runs-on: linux-amd64-cpu8 timeout-minutes: 10 steps: @@ -170,461 +253,9 @@ jobs: path: target/wheels/*.whl retention-days: 5 - # --------------------------------------------------------------------------- - # Build CLI binaries (Linux musl — static, native on each arch) - # - # Builds run directly on the CI host (glibc Ubuntu). Zig provides musl - # C/C++ toolchains for bundled-z3 and ring, and is also used as the linker. - # --------------------------------------------------------------------------- - build-cli-linux: - name: Build CLI (Linux ${{ matrix.arch }}) - needs: [compute-versions] - strategy: - matrix: - include: - - arch: amd64 - runner: linux-amd64-cpu8 - target: x86_64-unknown-linux-musl - zig_target: x86_64-linux-musl - - arch: arm64 - runner: linux-arm64-cpu8 - target: aarch64-unknown-linux-musl - zig_target: aarch64-linux-musl - runs-on: ${{ matrix.runner }} - timeout-minutes: 60 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - options: --privileged - env: - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - OPENSHELL_IMAGE_TAG: ${{ needs.compute-versions.outputs.semver }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ inputs.tag || github.ref }} - fetch-depth: 0 - - - name: Mark workspace safe for git - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Fetch tags - run: git fetch --tags --force - - - name: Install tools - run: mise install --locked - - - name: Cache Rust target and registry - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - with: - shared-key: cli-musl-${{ matrix.arch }} - cache-directories: .cache/sccache - cache-targets: "true" - - - name: Add Rust musl target - run: mise x -- rustup target add ${{ matrix.target }} - - - name: Set up zig musl wrappers - run: | - set -euo pipefail - ZIG="$(mise which zig)" - ZIG_TARGET="${{ matrix.zig_target }}" - mkdir -p /tmp/zig-musl - - # cc-rs injects --target= (for example - # aarch64-unknown-linux-musl), which zig does not parse. Strip any - # caller-provided --target and use the wrapper's zig-native target. - for tool in cc c++; do - printf '#!/bin/bash\nargs=()\nfor arg in "$@"; do\n case "$arg" in\n --target=*) ;;\n *) args+=("$arg") ;;\n esac\ndone\nexec "%s" %s --target=%s "${args[@]}"\n' \ - "$ZIG" "$tool" "$ZIG_TARGET" > "/tmp/zig-musl/${tool}" - chmod +x "/tmp/zig-musl/${tool}" - done - - TARGET_ENV=$(echo "${{ matrix.target }}" | tr '-' '_') - TARGET_ENV_UPPER=${TARGET_ENV^^} - - # Use zig for C/C++ compilation and final linking. - echo "CC_${TARGET_ENV}=/tmp/zig-musl/cc" >> "$GITHUB_ENV" - echo "CXX_${TARGET_ENV}=/tmp/zig-musl/c++" >> "$GITHUB_ENV" - echo "CARGO_TARGET_${TARGET_ENV_UPPER}_LINKER=/tmp/zig-musl/cc" >> "$GITHUB_ENV" - - # Let zig own CRT/startfiles to avoid duplicate _start symbols. - echo "CARGO_TARGET_${TARGET_ENV_UPPER}_RUSTFLAGS=-Clink-self-contained=no" >> "$GITHUB_ENV" - - # z3 built with zig c++ uses libc++ symbols (std::__1::*). - # Override z3-sys default (stdc++) so Rust links the matching runtime. - echo "CXXSTDLIB=c++" >> "$GITHUB_ENV" - - - name: Patch workspace version - if: needs.compute-versions.outputs.cargo_version != '' - run: | - set -euo pipefail - sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ needs.compute-versions.outputs.cargo_version }}"'"/}' Cargo.toml - - - name: Build ${{ matrix.target }} - run: mise x -- cargo build --release --target ${{ matrix.target }} -p openshell-cli - - - name: sccache stats - if: always() - run: mise x -- sccache --show-stats - - - name: Package binary - run: | - set -euo pipefail - mkdir -p artifacts - tar -czf artifacts/openshell-${{ matrix.target }}.tar.gz \ - -C target/${{ matrix.target }}/release openshell - ls -lh artifacts/ - - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: cli-linux-${{ matrix.arch }} - path: artifacts/*.tar.gz - retention-days: 5 - - # --------------------------------------------------------------------------- - # Build CLI binary (macOS aarch64 via osxcross) - # --------------------------------------------------------------------------- - build-cli-macos: - name: Build CLI (macOS) - needs: [compute-versions] - runs-on: linux-amd64-cpu8 - timeout-minutes: 60 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - options: --privileged - volumes: - - /var/run/docker.sock:/var/run/docker.sock - env: - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ inputs.tag || github.ref }} - fetch-depth: 0 - - - name: Mark workspace safe for git - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Fetch tags - run: git fetch --tags --force - - - name: Log in to GHCR - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: Set up Docker Buildx - uses: ./.github/actions/setup-buildx - - - name: Build macOS binary via Docker - run: | - set -euo pipefail - docker buildx build \ - --file deploy/docker/Dockerfile.cli-macos \ - --build-arg OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" \ - --build-arg OPENSHELL_IMAGE_TAG="${{ needs.compute-versions.outputs.semver }}" \ - --build-arg CARGO_TARGET_CACHE_SCOPE="${{ github.sha }}" \ - --target binary \ - --output type=local,dest=out/ \ - . - - - name: Package binary - run: | - set -euo pipefail - mkdir -p artifacts - tar -czf artifacts/openshell-aarch64-apple-darwin.tar.gz \ - -C out openshell - ls -lh artifacts/ - - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: cli-macos - path: artifacts/*.tar.gz - retention-days: 5 - - # --------------------------------------------------------------------------- - # Build standalone gateway binaries (Linux GNU — glibc 2.28 floor) - # --------------------------------------------------------------------------- - build-gateway-binary-linux: - name: Build Gateway Binary (Linux ${{ matrix.arch }}) - needs: [compute-versions] - strategy: - matrix: - include: - - arch: amd64 - runner: linux-amd64-cpu8 - target: x86_64-unknown-linux-gnu - zig_target: x86_64-unknown-linux-gnu.2.28 - - arch: arm64 - runner: linux-arm64-cpu8 - target: aarch64-unknown-linux-gnu - zig_target: aarch64-unknown-linux-gnu.2.28 - runs-on: ${{ matrix.runner }} - timeout-minutes: 60 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - options: --privileged - env: - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ inputs.tag || github.ref }} - fetch-depth: 0 - - - name: Mark workspace safe for git - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Fetch tags - run: git fetch --tags --force - - - name: Install tools - run: mise install --locked - - - name: Cache Rust target and registry - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - with: - shared-key: gateway-binary-gnu-${{ matrix.arch }}-zig-wrapper-${{ hashFiles('tasks/scripts/setup-zig-cc-wrapper.sh') }} - cache-directories: .cache/sccache - cache-targets: "true" - - - name: Patch workspace version - if: needs.compute-versions.outputs.cargo_version != '' - run: | - set -euo pipefail - sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ needs.compute-versions.outputs.cargo_version }}"'"/}' Cargo.toml - - - name: Set up Zig C/C++ wrappers - run: tasks/scripts/setup-zig-cc-wrapper.sh ${{ matrix.zig_target }} ${{ matrix.zig_target }} /tmp/zig-gnu - - - name: Build ${{ matrix.zig_target }} - env: - OPENSHELL_IMAGE_TAG: ${{ needs.compute-versions.outputs.source_sha }} - run: | - set -euo pipefail - mise x -- rustup target add ${{ matrix.target }} - mise x -- cargo zigbuild --release --target ${{ matrix.zig_target }} -p openshell-server --bin openshell-gateway --features bundled-z3 - mkdir -p artifacts/bin - install -m 0755 target/${{ matrix.target }}/release/openshell-gateway artifacts/bin/openshell-gateway - - - name: Verify packaged binary - run: | - set -euo pipefail - OUTPUT="$(artifacts/bin/openshell-gateway --version)" - echo "$OUTPUT" - grep -q '^openshell-gateway ' <<<"$OUTPUT" - ldd artifacts/bin/openshell-gateway || true - if ldd artifacts/bin/openshell-gateway | grep -q 'libz3'; then - echo "gateway binary must not require shared libz3; keep z3 bundled for portable release artifacts" >&2 - exit 1 - fi - - - name: Verify glibc symbol floor - run: tasks/scripts/verify-glibc-symbols.sh 2.28 artifacts/bin/openshell-gateway - - - name: sccache stats - if: always() - run: mise x -- sccache --show-stats - - - name: Package binary - run: | - set -euo pipefail - mkdir -p artifacts - tar -czf artifacts/openshell-gateway-${{ matrix.target }}.tar.gz \ - -C artifacts/bin openshell-gateway - ls -lh artifacts/ - - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: gateway-binary-linux-${{ matrix.arch }} - path: artifacts/*.tar.gz - retention-days: 5 - - # --------------------------------------------------------------------------- - # Build standalone supervisor binaries (Linux GNU — native on each arch) - # --------------------------------------------------------------------------- - build-supervisor-binary-linux: - name: Build Supervisor Binary (Linux ${{ matrix.arch }}) - needs: [compute-versions] - strategy: - matrix: - include: - - arch: amd64 - runner: linux-amd64-cpu8 - target: x86_64-unknown-linux-gnu - - arch: arm64 - runner: linux-arm64-cpu8 - target: aarch64-unknown-linux-gnu - runs-on: ${{ matrix.runner }} - timeout-minutes: 60 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - options: --privileged - env: - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ inputs.tag || github.ref }} - fetch-depth: 0 - - - name: Mark workspace safe for git - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Fetch tags - run: git fetch --tags --force - - - name: Install tools - run: mise install --locked - - - name: Cache Rust target and registry - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - with: - shared-key: supervisor-binary-gnu-${{ matrix.arch }} - cache-directories: .cache/sccache - cache-targets: "true" - - - name: Patch workspace version - if: needs.compute-versions.outputs.cargo_version != '' - run: | - set -euo pipefail - sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ needs.compute-versions.outputs.cargo_version }}"'"/}' Cargo.toml - - - name: Build ${{ matrix.target }} - run: | - set -euo pipefail - mise x -- cargo build --release --target ${{ matrix.target }} -p openshell-sandbox --bin openshell-sandbox - - - name: Verify packaged binary - run: | - set -euo pipefail - OUTPUT="$(target/${{ matrix.target }}/release/openshell-sandbox --version)" - echo "$OUTPUT" - grep -q '^openshell-sandbox ' <<<"$OUTPUT" - - - name: sccache stats - if: always() - run: mise x -- sccache --show-stats - - - name: Package binary - run: | - set -euo pipefail - mkdir -p artifacts - tar -czf artifacts/openshell-sandbox-${{ matrix.target }}.tar.gz \ - -C target/${{ matrix.target }}/release openshell-sandbox - ls -lh artifacts/ - - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: supervisor-binary-linux-${{ matrix.arch }} - path: artifacts/*.tar.gz - retention-days: 5 - - # --------------------------------------------------------------------------- - # Build standalone gateway binary (macOS aarch64 via osxcross) - # --------------------------------------------------------------------------- - build-gateway-binary-macos: - name: Build Gateway Binary (macOS) - needs: [compute-versions] - runs-on: linux-amd64-cpu8 - timeout-minutes: 60 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - options: --privileged - volumes: - - /var/run/docker.sock:/var/run/docker.sock - env: - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ inputs.tag || github.ref }} - fetch-depth: 0 - - - name: Mark workspace safe for git - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Fetch tags - run: git fetch --tags --force - - - name: Log in to GHCR - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - - - name: Set up Docker Buildx - uses: ./.github/actions/setup-buildx - - - name: Build macOS binary via Docker - run: | - set -euo pipefail - docker buildx build \ - --file deploy/docker/Dockerfile.gateway-macos \ - --build-arg OPENSHELL_CARGO_VERSION="${{ needs.compute-versions.outputs.cargo_version }}" \ - --build-arg OPENSHELL_IMAGE_TAG="${{ needs.compute-versions.outputs.semver }}" \ - --build-arg CARGO_TARGET_CACHE_SCOPE="${{ github.sha }}" \ - --target binary \ - --output type=local,dest=out/ \ - . - - - name: Verify packaged binary shape - run: | - set -euo pipefail - test -x out/openshell-gateway - - - name: Package binary - run: | - set -euo pipefail - mkdir -p artifacts - tar -czf artifacts/openshell-gateway-aarch64-apple-darwin.tar.gz \ - -C out openshell-gateway - ls -lh artifacts/ - - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: gateway-binary-macos - path: artifacts/*.tar.gz - retention-days: 5 - - build-driver-vm-linux: - name: Build Driver VM Linux - needs: [compute-versions] - uses: ./.github/workflows/driver-vm-linux.yml - with: - cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} - image-tag: ${{ needs.compute-versions.outputs.semver }} - checkout-ref: ${{ inputs.tag || github.ref }} - secrets: inherit - - build-driver-vm-macos: - name: Build Driver VM macOS - needs: [compute-versions] - uses: ./.github/workflows/driver-vm-macos.yml - with: - cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} - image-tag: ${{ needs.compute-versions.outputs.semver }} - checkout-ref: ${{ inputs.tag || github.ref }} - secrets: inherit - build-deb: name: Build Debian Packages - needs: [compute-versions, build-cli-linux, build-gateway-binary-linux, build-driver-vm-linux] + needs: [compute-versions, build-cli, build-gateway, build-vm-driver] uses: ./.github/workflows/deb-package.yml with: deb-version: ${{ needs.compute-versions.outputs.deb_version }} @@ -633,7 +264,7 @@ jobs: build-snap: name: Build Snap - needs: [compute-versions, build-cli-linux, build-gateway-binary-linux, build-supervisor-binary-linux] + needs: [compute-versions, build-cli, build-gateway, build-sandbox] uses: ./.github/workflows/snap-package.yml with: checkout-ref: ${{ inputs.tag || github.ref }} @@ -644,7 +275,7 @@ jobs: build-rpm: name: Build RPM Packages - needs: [compute-versions, build-cli-linux, build-gateway-binary-linux] + needs: [compute-versions, build-cli, build-gateway] uses: ./.github/workflows/rpm-package.yml with: checkout-ref: ${{ inputs.tag || github.ref }} @@ -655,7 +286,7 @@ jobs: smoke-linux-release-artifacts: name: Smoke Linux Release Artifacts (${{ matrix.name }}) - needs: [build-gateway-binary-linux, build-driver-vm-linux, build-deb, build-rpm, build-python-wheel] + needs: [package-binaries, build-deb, build-rpm, build-python-wheel] timeout-minutes: 20 strategy: fail-fast: false @@ -792,7 +423,7 @@ jobs: # --------------------------------------------------------------------------- release: name: Release - needs: [compute-versions, build-cli-linux, build-cli-macos, build-gateway-binary-linux, build-gateway-binary-macos, build-supervisor-binary-linux, build-python-wheel, e2e, build-driver-vm-linux, build-driver-vm-macos, build-deb, build-rpm, build-snap, smoke-linux-release-artifacts] + needs: [compute-versions, package-binaries, build-python-wheel, docker-e2e, podman-e2e, vm-e2e, build-deb, build-rpm, build-snap, smoke-linux-release-artifacts] runs-on: linux-amd64-cpu8 timeout-minutes: 10 permissions: @@ -896,8 +527,8 @@ jobs: openshell-gateway-aarch64-apple-darwin.tar.gz > openshell-gateway-checksums-sha256.txt cat openshell-gateway-checksums-sha256.txt sha256sum \ - openshell-sandbox-x86_64-unknown-linux-gnu.tar.gz \ - openshell-sandbox-aarch64-unknown-linux-gnu.tar.gz > openshell-sandbox-checksums-sha256.txt + openshell-sandbox-x86_64-unknown-linux-musl.tar.gz \ + openshell-sandbox-aarch64-unknown-linux-musl.tar.gz > openshell-sandbox-checksums-sha256.txt cat openshell-sandbox-checksums-sha256.txt - name: Generate Homebrew formula @@ -966,8 +597,8 @@ jobs: release/openshell-gateway-x86_64-unknown-linux-gnu.tar.gz release/openshell-gateway-aarch64-unknown-linux-gnu.tar.gz release/openshell-gateway-aarch64-apple-darwin.tar.gz - release/openshell-sandbox-x86_64-unknown-linux-gnu.tar.gz - release/openshell-sandbox-aarch64-unknown-linux-gnu.tar.gz + release/openshell-sandbox-x86_64-unknown-linux-musl.tar.gz + release/openshell-sandbox-aarch64-unknown-linux-musl.tar.gz release/openshell-driver-vm-x86_64-unknown-linux-gnu.tar.gz release/openshell-driver-vm-aarch64-unknown-linux-gnu.tar.gz release/openshell-driver-vm-aarch64-apple-darwin.tar.gz diff --git a/.github/workflows/rpm-package.yml b/.github/workflows/rpm-package.yml index 591d10e619..8701644585 100644 --- a/.github/workflows/rpm-package.yml +++ b/.github/workflows/rpm-package.yml @@ -68,21 +68,19 @@ jobs: - name: Download CLI artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: cli-linux-${{ matrix.artifact_arch }} - path: package-input/ + name: openshell-${{ matrix.cli_target }} + path: package-binaries/ - name: Download gateway artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: gateway-binary-linux-${{ matrix.artifact_arch }} - path: package-input/ + name: openshell-gateway-${{ matrix.gnu_target }} + path: package-binaries/ - - name: Extract package inputs + - name: Configure package inputs run: | set -euo pipefail - mkdir -p package-binaries - tar -xzf "package-input/openshell-${{ matrix.cli_target }}.tar.gz" -C package-binaries - tar -xzf "package-input/openshell-gateway-${{ matrix.gnu_target }}.tar.gz" -C package-binaries + chmod +x package-binaries/openshell{,-gateway} ls -lah package-binaries - name: Mark workspace safe for git diff --git a/.github/workflows/snap-package.yml b/.github/workflows/snap-package.yml index ed248b79e5..efcd998735 100644 --- a/.github/workflows/snap-package.yml +++ b/.github/workflows/snap-package.yml @@ -37,8 +37,10 @@ jobs: matrix: include: - arch: amd64 + rust_arch: x86_64 runner: linux-amd64-cpu8 - arch: arm64 + rust_arch: aarch64 runner: linux-arm64-cpu8 runs-on: ${{ matrix.runner }} timeout-minutes: 60 @@ -79,35 +81,27 @@ jobs: - name: Download prebuilt CLI binary uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: cli-linux-${{ matrix.arch }} + name: openshell-${{ matrix.rust_arch }}-unknown-linux-musl path: prebuilt/cli - name: Download prebuilt gateway binary uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: gateway-binary-linux-${{ matrix.arch }} + name: openshell-gateway-${{ matrix.rust_arch }}-unknown-linux-gnu path: prebuilt/gateway - name: Download prebuilt sandbox binary uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: - name: supervisor-binary-linux-${{ matrix.arch }} + name: openshell-sandbox-${{ matrix.rust_arch }}-unknown-linux-musl path: prebuilt/sandbox - - name: Extract prebuilt binaries + - name: Configure prebuilt binaries run: | set -euo pipefail - mkdir -p prebuilt/{cli,gateway,sandbox} - - for d in cli gateway sandbox; do - for tarball in prebuilt/$d/*.tar.gz; do - if [ -f "$tarball" ]; then - tar -xzf "$tarball" -C "prebuilt/$d" - else - echo "WARNING: no tarball found in prebuilt/$d/" >&2 - fi - done - done + chmod +x prebuilt/cli/openshell + chmod +x prebuilt/gateway/openshell-gateway + chmod +x prebuilt/sandbox/openshell-sandbox ls -laR prebuilt/ - name: Prepare snap build directory diff --git a/CI.md b/CI.md index c3d33622f7..67b7f8e49b 100644 --- a/CI.md +++ b/CI.md @@ -14,14 +14,14 @@ Merge queue validation is a second integration gate for `main`. After a PR has p Three opt-in labels enable the long-running E2E suites: -- `test:e2e` runs the standard Docker, Kubernetes, and VM E2E - suites in `Branch E2E Checks` +- `test:e2e` runs the Docker, rootless Podman, Kubernetes, and VM E2E suites + with both managed and standalone compute drivers in `Branch E2E Checks` - `test:e2e-gpu` runs GPU E2E in `Branch E2E Checks` - `test:e2e-kubernetes` runs Kubernetes E2E with the HA Helm overlay (`replicaCount: 2` and bundled PostgreSQL) and the credential-driver suite (Kubernetes Secrets plus Vault) in `Branch E2E Checks` -When multiple labels are present, `Branch E2E Checks` builds the shared gateway and supervisor images once, builds one CLI artifact per runner architecture, builds the Linux VM driver artifact once, and fans out all enabled suites in parallel. Docker, Podman, GPU, Rust, Python, MCP, and VM E2E jobs reuse the matching prebuilt gateway and CLI binaries instead of compiling additional debug binaries in each job; Kubernetes E2E consumes the gateway image directly and reuses the prebuilt CLI. VM E2E also reuses the prebuilt VM driver artifact and falls back to local VM-driver/runtime preparation for local runs or workflow invocations that omit the artifact. +When multiple labels are present, `Branch E2E Checks` builds each generic multi-architecture artifact set once and fans out enabled suites in parallel. Runtime-specific reusable workflows define the Docker, Podman, VM, and Kubernetes lanes. Composite actions own the replaceable Podman, KVM, kind, and mise setup. Each lane depends only on the artifact categories it consumes: VM does not wait for container-driver artifacts or supervisor images, and GPU does not wait for the gateway image. Docker, Podman, GPU, Rust, Python, MCP, and VM E2E reuse matching prebuilt gateway and CLI binaries instead of compiling debug binaries in test jobs. Standalone-driver lanes additionally reuse driver-free gateway and compute-driver artifacts. Kubernetes managed-driver lanes consume published gateway and supervisor images, while the standalone-driver lane composes its gateway image from prebuilt binaries. The `OpenShell / E2E` and `OpenShell / GPU E2E` required statuses are evaluated from separate suite result jobs inside that workflow. `test:e2e-kubernetes` is optional while Kubernetes HA and credential-driver behavior are under active iteration: failures are visible in the workflow run but do not publish a required CI gate status. The GitHub ruleset should require the `OpenShell / ...` statuses published by `Required CI Gates`, not the push-triggered workflow jobs directly. @@ -172,6 +172,10 @@ The bot's full administrator documentation is internal to NVIDIA. The only comma |---|---| | `.github/workflows/branch-checks.yml` | Required non-E2E checks. Triggers on `push: pull-request/[0-9]+` for PR mirrors and `merge_group` for queued merges. | | `.github/workflows/branch-e2e.yml` | Standard, GPU, Kubernetes HA, and Kubernetes credential-driver E2E. PR mirror pushes use `test:e2e`, `test:e2e-gpu`, and `test:e2e-kubernetes` labels; merge groups run core and GPU E2E. | +| `.github/workflows/build-platform-binaries.yml` | Shared CLI, gateway, and static sandbox target matrices used by branch and release workflows. | +| `.github/workflows/package-release-binaries.yml` | Packages raw build artifacts into release tarballs without rebuilding them. | +| `.github/workflows/e2e-docker-test.yml`, `e2e-podman-test.yml`, `e2e-vm-test.yml`, `e2e-kubernetes-test.yml` | Reusable runtime lanes called directly by branch and release workflows. Callers select suites and declare only the artifacts each runtime consumes. | +| `.github/actions/setup-e2e-*` | Shared artifact, Podman, KVM, and kind setup used by the runtime lanes. | | `.github/workflows/helm-lint.yml` | Helm chart validation. PR mirror pushes skip lint jobs unless Helm inputs changed; merge groups always validate Helm because they represent the final integration state. | | `.github/actions/setup-nix/action.yml` | Installs Nix and configures the OpenShell Cachix cache, using read-only cache access when no authentication token is available. | | `.github/actions/pr-gate/action.yml` | Composite action that resolves PR metadata and verifies the required label is set for PR mirror pushes. Non-push events are allowed through. | diff --git a/e2e/rust/e2e-vm.sh b/e2e/rust/e2e-vm.sh index 1960f83588..4acb7256bb 100755 --- a/e2e/rust/e2e-vm.sh +++ b/e2e/rust/e2e-vm.sh @@ -102,7 +102,7 @@ if [ -z "${OPENSHELL_GATEWAY_BIN:-}" ]; then echo "==> Building driver-free openshell-gateway" cargo build \ -p openshell-server --bin openshell-gateway \ - --no-default-features --features telemetry + --no-default-features else build_packages+=(-p openshell-server) fi diff --git a/e2e/support/gateway-common.sh b/e2e/support/gateway-common.sh index 1b2b8e4145..c512dab60a 100644 --- a/e2e/support/gateway-common.sh +++ b/e2e/support/gateway-common.sh @@ -220,7 +220,7 @@ e2e_build_gateway_binaries() { if [ "${OPENSHELL_E2E_EXTERNAL_COMPUTE_DRIVER:-0}" = "1" ]; then cargo build "${jobs[@]}" \ -p openshell-server --bin openshell-gateway \ - --no-default-features --features telemetry + --no-default-features else cargo build "${jobs[@]}" \ -p openshell-server --bin openshell-gateway @@ -259,9 +259,14 @@ e2e_build_external_driver() { jobs=(-j "${CARGO_BUILD_JOBS}") fi target_dir="$(e2e_cargo_target_dir "${root}")" - printf -v "${output_var}" '%s' "${target_dir}/debug/${binary}" - echo "Building external ${binary}..." - cargo build "${jobs[@]}" -p "${package}" --bin "${binary}" + if [ -n "${OPENSHELL_EXTERNAL_DRIVER_BIN:-}" ]; then + printf -v "${output_var}" '%s' "${OPENSHELL_EXTERNAL_DRIVER_BIN}" + echo "Using prebuilt external driver at ${OPENSHELL_EXTERNAL_DRIVER_BIN}" + else + printf -v "${output_var}" '%s' "${target_dir}/debug/${binary}" + echo "Building external ${binary}..." + cargo build "${jobs[@]}" -p "${package}" --bin "${binary}" + fi if [ ! -x "${!output_var}" ]; then echo "ERROR: expected external driver binary at ${!output_var}" >&2 exit 1 diff --git a/e2e/with-kube-gateway.sh b/e2e/with-kube-gateway.sh index f83c8bafe1..08dd08113e 100755 --- a/e2e/with-kube-gateway.sh +++ b/e2e/with-kube-gateway.sh @@ -640,9 +640,15 @@ if [ "${OPENSHELL_E2E_KUBE_BUILD_IMAGES}" = "1" ]; then echo "ERROR: external Kubernetes driver image composition currently requires a Linux build host." >&2 exit 2 fi - cargo build -p openshell-server --bin openshell-gateway \ - --no-default-features --features telemetry,bundled-z3 - cargo build -p openshell-driver-kubernetes --bin openshell-driver-kubernetes + external_gateway="${OPENSHELL_GATEWAY_BIN:-${ROOT}/target/debug/openshell-gateway}" + external_driver="${OPENSHELL_EXTERNAL_DRIVER_BIN:-${ROOT}/target/debug/openshell-driver-kubernetes}" + if [ -z "${OPENSHELL_GATEWAY_BIN:-}" ]; then + cargo build -p openshell-server --bin openshell-gateway \ + --no-default-features --features bundled-z3 + fi + if [ -z "${OPENSHELL_EXTERNAL_DRIVER_BIN:-}" ]; then + cargo build -p openshell-driver-kubernetes --bin openshell-driver-kubernetes + fi case "$(uname -m)" in x86_64) external_arch=amd64 ;; aarch64|arm64) external_arch=arm64 ;; @@ -650,9 +656,8 @@ if [ "${OPENSHELL_E2E_KUBE_BUILD_IMAGES}" = "1" ]; then esac external_stage="${ROOT}/deploy/docker/.build/prebuilt-binaries/${external_arch}" mkdir -p "${external_stage}" - cp "${ROOT}/target/debug/openshell-gateway" "${external_stage}/openshell-gateway" - cp "${ROOT}/target/debug/openshell-driver-kubernetes" \ - "${external_stage}/openshell-driver-kubernetes" + cp "${external_gateway}" "${external_stage}/openshell-gateway" + cp "${external_driver}" "${external_stage}/openshell-driver-kubernetes" docker build \ --build-arg "TARGETARCH=${external_arch}" \ --build-arg "SUPERVISOR_IMAGE=${REGISTRY_VALUE}/supervisor:${IMAGE_TAG_VALUE}" \ diff --git a/scripts/baseline_workflow_metrics.py b/scripts/baseline_workflow_metrics.py index 5a15b70801..031c7001d6 100755 --- a/scripts/baseline_workflow_metrics.py +++ b/scripts/baseline_workflow_metrics.py @@ -41,9 +41,13 @@ WORKFLOWS: list[str] = [ "branch-checks.yml", "branch-e2e.yml", + "build-platform-binaries.yml", "ci-image.yml", "docker-build.yml", - "e2e-test.yml", + "e2e-docker-test.yml", + "e2e-podman-test.yml", + "e2e-vm-test.yml", + "package-release-binaries.yml", "release-canary.yml", "release-dev.yml", "release-tag.yml", @@ -55,8 +59,12 @@ # workflow_run. For these, we scan all repo runs in the window and attribute # via `referenced_workflows`. REUSABLE_WORKFLOWS: set[str] = { + "build-platform-binaries.yml", "docker-build.yml", - "e2e-test.yml", + "e2e-docker-test.yml", + "e2e-podman-test.yml", + "e2e-vm-test.yml", + "package-release-binaries.yml", } # Conclusions that represent a real execution on a runner. Percentile math From c3876ce04200071b3732c415da04e421d1028a2f Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 15:56:39 +0200 Subject: [PATCH 10/17] ci: split platform binary workflows Signed-off-by: Simon Scatton --- .github/workflows/branch-e2e.yml | 9 +- .github/workflows/build-cli-binaries.yml | 50 +++++++++ .github/workflows/build-gateway-binaries.yml | 54 +++++++++ .github/workflows/build-platform-binaries.yml | 106 ------------------ .github/workflows/build-sandbox-binaries.yml | 47 ++++++++ .github/workflows/release-dev.yml | 9 +- .github/workflows/release-tag.yml | 9 +- CI.md | 2 +- scripts/baseline_workflow_metrics.py | 8 +- 9 files changed, 167 insertions(+), 127 deletions(-) create mode 100644 .github/workflows/build-cli-binaries.yml create mode 100644 .github/workflows/build-gateway-binaries.yml delete mode 100644 .github/workflows/build-platform-binaries.yml create mode 100644 .github/workflows/build-sandbox-binaries.yml diff --git a/.github/workflows/branch-e2e.yml b/.github/workflows/branch-e2e.yml index a86dda04da..20081afe5c 100644 --- a/.github/workflows/branch-e2e.yml +++ b/.github/workflows/branch-e2e.yml @@ -100,9 +100,8 @@ jobs: needs: version permissions: contents: read - uses: ./.github/workflows/build-platform-binaries.yml + uses: ./.github/workflows/build-cli-binaries.yml with: - component: cli cargo-version: ${{ needs.version.outputs.cargo }} secrets: inherit @@ -110,9 +109,8 @@ jobs: needs: version permissions: contents: read - uses: ./.github/workflows/build-platform-binaries.yml + uses: ./.github/workflows/build-sandbox-binaries.yml with: - component: sandbox-musl cargo-version: ${{ needs.version.outputs.cargo }} secrets: inherit @@ -120,9 +118,8 @@ jobs: needs: version permissions: contents: read - uses: ./.github/workflows/build-platform-binaries.yml + uses: ./.github/workflows/build-gateway-binaries.yml with: - component: gateway cargo-version: ${{ needs.version.outputs.cargo }} secrets: inherit diff --git a/.github/workflows/build-cli-binaries.yml b/.github/workflows/build-cli-binaries.yml new file mode 100644 index 0000000000..b3cf77d040 --- /dev/null +++ b/.github/workflows/build-cli-binaries.yml @@ -0,0 +1,50 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Build CLI Binaries + +on: + workflow_call: + inputs: + cargo-version: + required: true + type: string + image-tag: + required: false + type: string + default: "" + checkout-ref: + required: false + type: string + default: "" + secrets: + CACHIX_AUTH_TOKEN: + required: true + +permissions: + contents: read + +jobs: + build: + strategy: + matrix: + include: + - triple: x86_64-unknown-linux-musl + runner: linux-amd64-cpu8 + dev_shell: .#devShells.x86_64-linux.musl + - triple: aarch64-unknown-linux-musl + runner: linux-arm64-cpu8 + dev_shell: .#devShells.aarch64-linux.musl + - triple: aarch64-apple-darwin + runner: macos-15-xlarge + dev_shell: .#devShells.aarch64-darwin.default + uses: ./.github/workflows/build-binaries.yml + with: + binary: openshell + triple: ${{ matrix.triple }} + runner: ${{ matrix.runner }} + dev-shell: ${{ matrix.dev_shell }} + cargo-version: ${{ inputs.cargo-version }} + image-tag: ${{ inputs.image-tag }} + checkout-ref: ${{ inputs.checkout-ref }} + secrets: inherit diff --git a/.github/workflows/build-gateway-binaries.yml b/.github/workflows/build-gateway-binaries.yml new file mode 100644 index 0000000000..a2064d6777 --- /dev/null +++ b/.github/workflows/build-gateway-binaries.yml @@ -0,0 +1,54 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Build Gateway Binaries + +on: + workflow_call: + inputs: + cargo-version: + required: true + type: string + image-tag: + required: false + type: string + default: "" + checkout-ref: + required: false + type: string + default: "" + secrets: + CACHIX_AUTH_TOKEN: + required: true + +permissions: + contents: read + +jobs: + build: + strategy: + matrix: + include: + - triple: x86_64-unknown-linux-gnu + runner: linux-amd64-cpu8 + dev_shell: .#devShells.x86_64-linux.glibc-2-28 + interpreter: /lib64/ld-linux-x86-64.so.2 + - triple: aarch64-unknown-linux-gnu + runner: linux-arm64-cpu8 + dev_shell: .#devShells.aarch64-linux.glibc-2-28 + interpreter: /lib/ld-linux-aarch64.so.1 + - triple: aarch64-apple-darwin + runner: macos-15-xlarge + dev_shell: .#devShells.aarch64-darwin.default + interpreter: "" + uses: ./.github/workflows/build-binaries.yml + with: + binary: openshell-gateway + triple: ${{ matrix.triple }} + runner: ${{ matrix.runner }} + dev-shell: ${{ matrix.dev_shell }} + cargo-version: ${{ inputs.cargo-version }} + image-tag: ${{ inputs.image-tag }} + interpreter: ${{ matrix.interpreter }} + checkout-ref: ${{ inputs.checkout-ref }} + secrets: inherit diff --git a/.github/workflows/build-platform-binaries.yml b/.github/workflows/build-platform-binaries.yml deleted file mode 100644 index 69db18d033..0000000000 --- a/.github/workflows/build-platform-binaries.yml +++ /dev/null @@ -1,106 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -name: Build Platform Binaries - -on: - workflow_call: - inputs: - component: - description: Target matrix to build - required: true - type: string - cargo-version: - required: true - type: string - image-tag: - required: false - type: string - default: "" - checkout-ref: - required: false - type: string - default: "" - secrets: - CACHIX_AUTH_TOKEN: - required: true - -permissions: - contents: read - -jobs: - cli: - if: inputs.component == 'cli' - strategy: - matrix: - include: - - triple: x86_64-unknown-linux-musl - runner: linux-amd64-cpu8 - dev_shell: .#devShells.x86_64-linux.musl - - triple: aarch64-unknown-linux-musl - runner: linux-arm64-cpu8 - dev_shell: .#devShells.aarch64-linux.musl - - triple: aarch64-apple-darwin - runner: macos-15-xlarge - dev_shell: .#devShells.aarch64-darwin.default - uses: ./.github/workflows/build-binaries.yml - with: - binary: openshell - triple: ${{ matrix.triple }} - runner: ${{ matrix.runner }} - dev-shell: ${{ matrix.dev_shell }} - cargo-version: ${{ inputs.cargo-version }} - image-tag: ${{ inputs.image-tag }} - checkout-ref: ${{ inputs.checkout-ref }} - secrets: inherit - - gateway: - if: inputs.component == 'gateway' - strategy: - matrix: - include: - - triple: x86_64-unknown-linux-gnu - runner: linux-amd64-cpu8 - dev_shell: .#devShells.x86_64-linux.glibc-2-28 - interpreter: /lib64/ld-linux-x86-64.so.2 - - triple: aarch64-unknown-linux-gnu - runner: linux-arm64-cpu8 - dev_shell: .#devShells.aarch64-linux.glibc-2-28 - interpreter: /lib/ld-linux-aarch64.so.1 - - triple: aarch64-apple-darwin - runner: macos-15-xlarge - dev_shell: .#devShells.aarch64-darwin.default - interpreter: "" - uses: ./.github/workflows/build-binaries.yml - with: - binary: openshell-gateway - triple: ${{ matrix.triple }} - runner: ${{ matrix.runner }} - dev-shell: ${{ matrix.dev_shell }} - cargo-version: ${{ inputs.cargo-version }} - image-tag: ${{ inputs.image-tag }} - interpreter: ${{ matrix.interpreter }} - checkout-ref: ${{ inputs.checkout-ref }} - secrets: inherit - - sandbox-musl: - if: inputs.component == 'sandbox-musl' - strategy: - matrix: - include: - - triple: x86_64-unknown-linux-musl - runner: linux-amd64-cpu8 - dev_shell: .#devShells.x86_64-linux.musl - - triple: aarch64-unknown-linux-musl - runner: linux-arm64-cpu8 - dev_shell: .#devShells.aarch64-linux.musl - uses: ./.github/workflows/build-binaries.yml - with: - binary: openshell-sandbox - triple: ${{ matrix.triple }} - runner: ${{ matrix.runner }} - dev-shell: ${{ matrix.dev_shell }} - cargo-version: ${{ inputs.cargo-version }} - image-tag: ${{ inputs.image-tag }} - checkout-ref: ${{ inputs.checkout-ref }} - secrets: inherit diff --git a/.github/workflows/build-sandbox-binaries.yml b/.github/workflows/build-sandbox-binaries.yml new file mode 100644 index 0000000000..40ffe8a1ad --- /dev/null +++ b/.github/workflows/build-sandbox-binaries.yml @@ -0,0 +1,47 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Build Sandbox Binaries + +on: + workflow_call: + inputs: + cargo-version: + required: true + type: string + image-tag: + required: false + type: string + default: "" + checkout-ref: + required: false + type: string + default: "" + secrets: + CACHIX_AUTH_TOKEN: + required: true + +permissions: + contents: read + +jobs: + build: + strategy: + matrix: + include: + - triple: x86_64-unknown-linux-musl + runner: linux-amd64-cpu8 + dev_shell: .#devShells.x86_64-linux.musl + - triple: aarch64-unknown-linux-musl + runner: linux-arm64-cpu8 + dev_shell: .#devShells.aarch64-linux.musl + uses: ./.github/workflows/build-binaries.yml + with: + binary: openshell-sandbox + triple: ${{ matrix.triple }} + runner: ${{ matrix.runner }} + dev-shell: ${{ matrix.dev_shell }} + cargo-version: ${{ inputs.cargo-version }} + image-tag: ${{ inputs.image-tag }} + checkout-ref: ${{ inputs.checkout-ref }} + secrets: inherit diff --git a/.github/workflows/release-dev.yml b/.github/workflows/release-dev.yml index dbf5e1ee18..290fd3d9c3 100644 --- a/.github/workflows/release-dev.yml +++ b/.github/workflows/release-dev.yml @@ -57,9 +57,8 @@ jobs: needs: compute-versions permissions: contents: read - uses: ./.github/workflows/build-platform-binaries.yml + uses: ./.github/workflows/build-cli-binaries.yml with: - component: cli cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} image-tag: dev secrets: inherit @@ -68,9 +67,8 @@ jobs: needs: compute-versions permissions: contents: read - uses: ./.github/workflows/build-platform-binaries.yml + uses: ./.github/workflows/build-gateway-binaries.yml with: - component: gateway cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} image-tag: dev secrets: inherit @@ -79,9 +77,8 @@ jobs: needs: compute-versions permissions: contents: read - uses: ./.github/workflows/build-platform-binaries.yml + uses: ./.github/workflows/build-sandbox-binaries.yml with: - component: sandbox-musl cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} image-tag: dev secrets: inherit diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index c884134e6d..e82b75546c 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -77,9 +77,8 @@ jobs: needs: compute-versions permissions: contents: read - uses: ./.github/workflows/build-platform-binaries.yml + uses: ./.github/workflows/build-cli-binaries.yml with: - component: cli cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} image-tag: ${{ needs.compute-versions.outputs.semver }} checkout-ref: ${{ inputs.tag || github.ref }} @@ -89,9 +88,8 @@ jobs: needs: compute-versions permissions: contents: read - uses: ./.github/workflows/build-platform-binaries.yml + uses: ./.github/workflows/build-gateway-binaries.yml with: - component: gateway cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} image-tag: ${{ needs.compute-versions.outputs.semver }} checkout-ref: ${{ inputs.tag || github.ref }} @@ -101,9 +99,8 @@ jobs: needs: compute-versions permissions: contents: read - uses: ./.github/workflows/build-platform-binaries.yml + uses: ./.github/workflows/build-sandbox-binaries.yml with: - component: sandbox-musl cargo-version: ${{ needs.compute-versions.outputs.cargo_version }} image-tag: ${{ needs.compute-versions.outputs.semver }} checkout-ref: ${{ inputs.tag || github.ref }} diff --git a/CI.md b/CI.md index 67b7f8e49b..2120fa546b 100644 --- a/CI.md +++ b/CI.md @@ -172,7 +172,7 @@ The bot's full administrator documentation is internal to NVIDIA. The only comma |---|---| | `.github/workflows/branch-checks.yml` | Required non-E2E checks. Triggers on `push: pull-request/[0-9]+` for PR mirrors and `merge_group` for queued merges. | | `.github/workflows/branch-e2e.yml` | Standard, GPU, Kubernetes HA, and Kubernetes credential-driver E2E. PR mirror pushes use `test:e2e`, `test:e2e-gpu`, and `test:e2e-kubernetes` labels; merge groups run core and GPU E2E. | -| `.github/workflows/build-platform-binaries.yml` | Shared CLI, gateway, and static sandbox target matrices used by branch and release workflows. | +| `.github/workflows/build-{cli,gateway,sandbox}-binaries.yml` | Independent target matrices used by branch and release workflows without creating skipped jobs. | | `.github/workflows/package-release-binaries.yml` | Packages raw build artifacts into release tarballs without rebuilding them. | | `.github/workflows/e2e-docker-test.yml`, `e2e-podman-test.yml`, `e2e-vm-test.yml`, `e2e-kubernetes-test.yml` | Reusable runtime lanes called directly by branch and release workflows. Callers select suites and declare only the artifacts each runtime consumes. | | `.github/actions/setup-e2e-*` | Shared artifact, Podman, KVM, and kind setup used by the runtime lanes. | diff --git a/scripts/baseline_workflow_metrics.py b/scripts/baseline_workflow_metrics.py index 031c7001d6..ffd5054679 100755 --- a/scripts/baseline_workflow_metrics.py +++ b/scripts/baseline_workflow_metrics.py @@ -41,7 +41,9 @@ WORKFLOWS: list[str] = [ "branch-checks.yml", "branch-e2e.yml", - "build-platform-binaries.yml", + "build-cli-binaries.yml", + "build-gateway-binaries.yml", + "build-sandbox-binaries.yml", "ci-image.yml", "docker-build.yml", "e2e-docker-test.yml", @@ -59,7 +61,9 @@ # workflow_run. For these, we scan all repo runs in the window and attribute # via `referenced_workflows`. REUSABLE_WORKFLOWS: set[str] = { - "build-platform-binaries.yml", + "build-cli-binaries.yml", + "build-gateway-binaries.yml", + "build-sandbox-binaries.yml", "docker-build.yml", "e2e-docker-test.yml", "e2e-podman-test.yml", From f383e6f1e736fd769328f57c7c415b912d40dbab Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 16:09:03 +0200 Subject: [PATCH 11/17] ci: remove obsolete native build workflows Signed-off-by: Simon Scatton --- .github/workflows/rust-native-build.yml | 383 ------------------ .../workflows/supervisor-static-validate.yml | 70 ---- 2 files changed, 453 deletions(-) delete mode 100644 .github/workflows/rust-native-build.yml delete mode 100644 .github/workflows/supervisor-static-validate.yml diff --git a/.github/workflows/rust-native-build.yml b/.github/workflows/rust-native-build.yml deleted file mode 100644 index 24124d0ae4..0000000000 --- a/.github/workflows/rust-native-build.yml +++ /dev/null @@ -1,383 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -name: Rust Image Binary Build (openshell-gateway / openshell-sandbox / openshell-cli) - -# Build Rust binaries per Linux architecture before the Docker image build -# consumes them as prebuilt artifacts. Gateway images use GNU-linked binaries -# for the NVIDIA distroless C/C++ runtime; supervisor and cli images use static -# binaries so the final image can remain scratch. Gateway GNU binaries are -# built with an explicit glibc 2.28 floor so image, package, and tarball -# artifacts share the same host portability contract. -# -# The supervisor libc is selectable via the `supervisor-libc` input (musl or -# glibc-static). Both variants are fully static and are verified as such, -# because the supervisor is executed from inside arbitrary sandbox images. - -on: - workflow_call: - inputs: - component: - description: "Binary component to build (gateway, sandbox, or cli)" - required: true - type: string - arch: - description: "Linux architecture to build (amd64 or arm64)" - required: true - type: string - supervisor-libc: - description: "libc variant for the sandbox component (musl or glibc-static)" - required: false - type: string - default: "musl" - cargo-version: - description: "Pre-computed cargo version (skips internal git-based computation)" - required: false - type: string - default: "" - features: - description: "Cargo features to enable" - required: false - type: string - default: "" - # Branch E2E and release gateway/supervisor image builds opt in. - # CLI images and standalone glibc-static supervisor validation keep false. - auditable: - description: "Embed cargo-auditable dependency metadata in the binary" - required: false - type: boolean - default: false - retention-days: - description: "Artifact retention period" - required: false - type: number - default: 5 - artifact-name: - description: "Artifact name override" - required: false - type: string - default: "" - checkout-ref: - description: "Git ref to check out for build inputs (defaults to the workflow SHA)" - required: false - type: string - default: "" - image-tag: - description: "Supervisor image tag to bake into gateway binaries" - required: false - type: string - default: "" -permissions: - contents: read - packages: read - -env: - CARGO_TERM_COLOR: always - CARGO_INCREMENTAL: "0" - MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Route sccache (already RUSTC_WRAPPER in mise.toml) to the GHA cache - # backend instead of the EKS memcached used by ARC. - SCCACHE_GHA_ENABLED: "true" - -defaults: - run: - shell: bash - -jobs: - rust-native-build: - name: ${{ inputs.component }} (${{ inputs.arch }}) - runs-on: ${{ inputs.arch == 'arm64' && 'linux-arm64-cpu8' || 'linux-amd64-cpu8' }} - timeout-minutes: 60 - env: - COMPONENT: ${{ inputs.component }} - ARCH: ${{ inputs.arch }} - FEATURES: ${{ inputs.features }} - SUPERVISOR_LIBC: ${{ inputs['supervisor-libc'] }} - # Partition the GHA sccache cache per (component, arch). Without this, - # concurrent jobs collide on the same cache key and later-starting - # writers hit 409 Conflict. The sandbox component also partitions per - # libc variant so musl and glibc-static builds do not evict each other. - SCCACHE_GHA_VERSION: ${{ inputs.component }}-${{ inputs.arch }}${{ inputs.component == 'sandbox' && format('-{0}', inputs['supervisor-libc']) || '' }} - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ inputs['checkout-ref'] || github.sha }} - fetch-depth: 0 - - - name: Mark workspace safe for git - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Fetch tags - run: git fetch --tags --force - - - name: Configure GHA sccache backend - # Exposes ACTIONS_CACHE_URL / ACTIONS_RUNTIME_TOKEN before `mise install` - # compiles cargo-installed tools through RUSTC_WRAPPER=sccache. - uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10 - - - name: Install tools - run: mise install --locked - - - name: Resolve build target - id: target - run: | - set -euo pipefail - - case "$COMPONENT" in - gateway) - crate=openshell-server - binary=openshell-gateway - zig_target= - ;; - sandbox) - crate=openshell-sandbox - binary=openshell-sandbox - zig_target= - ;; - cli) - crate=openshell-cli - binary=openshell - zig_target= - ;; - *) - echo "unsupported component: $COMPONENT" >&2 - exit 1 - ;; - esac - - # The sandbox binary must stay fully static. musl gets there via the - # musl target; glibc-static uses the GNU target with +crt-static and - # relies on this job running natively on the target architecture, - # because zig cannot statically link glibc. - static_libc=musl - if [[ "$COMPONENT" == "sandbox" ]]; then - case "$SUPERVISOR_LIBC" in - musl) static_libc=musl ;; - glibc-static) static_libc=gnu ;; - *) - echo "unsupported supervisor-libc: $SUPERVISOR_LIBC (expected musl or glibc-static)" >&2 - exit 1 - ;; - esac - fi - - case "$ARCH" in - amd64) - if [[ "$COMPONENT" == "sandbox" && "$static_libc" == "gnu" ]]; then - target=x86_64-unknown-linux-gnu - zig_target= - elif [[ "$COMPONENT" == "sandbox" || "$COMPONENT" == "cli" ]]; then - target=x86_64-unknown-linux-musl - zig_target=x86_64-linux-musl - else - target=x86_64-unknown-linux-gnu - zig_target=x86_64-unknown-linux-gnu.2.28 - fi - ;; - arm64) - if [[ "$COMPONENT" == "sandbox" && "$static_libc" == "gnu" ]]; then - target=aarch64-unknown-linux-gnu - zig_target= - elif [[ "$COMPONENT" == "sandbox" || "$COMPONENT" == "cli" ]]; then - target=aarch64-unknown-linux-musl - zig_target=aarch64-linux-musl - else - target=aarch64-unknown-linux-gnu - zig_target=aarch64-unknown-linux-gnu.2.28 - fi - ;; - *) - echo "unsupported arch: $ARCH" >&2 - exit 1 - ;; - esac - - { - echo "crate=$crate" - echo "binary=$binary" - echo "target=$target" - echo "zig_target=$zig_target" - } >> "$GITHUB_OUTPUT" - - - name: Cache Rust target and registry - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - with: - shared-key: rust-native-${{ inputs.component }}-${{ inputs.arch }}${{ inputs.component == 'sandbox' && format('-{0}', inputs['supervisor-libc']) || '' }}-zig-wrapper-${{ hashFiles('tasks/scripts/setup-zig-cc-wrapper.sh') }} - cache-directories: .cache/sccache - cache-targets: "true" - - - name: Compute cargo version - id: version - run: | - set -euo pipefail - if [[ -n "${{ inputs['cargo-version'] }}" ]]; then - echo "cargo_version=${{ inputs['cargo-version'] }}" >> "$GITHUB_OUTPUT" - else - echo "cargo_version=$(uv run python tasks/scripts/release.py get-version --cargo)" >> "$GITHUB_OUTPUT" - fi - - - name: Patch workspace version - if: steps.version.outputs.cargo_version != '' - run: | - set -euo pipefail - sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${{ steps.version.outputs.cargo_version }}"'"/}' Cargo.toml - - - name: Set up zig musl wrappers - if: contains(steps.target.outputs.target, 'musl') - run: | - set -euo pipefail - ZIG="$(mise which zig)" - ZIG_TARGET="${{ steps.target.outputs.zig_target }}" - mkdir -p /tmp/zig-musl - - # cc-rs injects --target=, which zig does not parse. - # Strip caller-provided --target and use the wrapper's zig target. - for tool in cc c++; do - printf '#!/bin/bash\nargs=()\nfor arg in "$@"; do\n case "$arg" in\n --target=*) ;;\n *) args+=("$arg") ;;\n esac\ndone\nexec "%s" %s --target=%s "${args[@]}"\n' \ - "$ZIG" "$tool" "$ZIG_TARGET" > "/tmp/zig-musl/${tool}" - chmod +x "/tmp/zig-musl/${tool}" - done - - TARGET_ENV=$(echo "${{ steps.target.outputs.target }}" | tr '-' '_') - TARGET_ENV_UPPER=${TARGET_ENV^^} - - echo "CC_${TARGET_ENV}=/tmp/zig-musl/cc" >> "$GITHUB_ENV" - echo "CXX_${TARGET_ENV}=/tmp/zig-musl/c++" >> "$GITHUB_ENV" - echo "CARGO_TARGET_${TARGET_ENV_UPPER}_LINKER=/tmp/zig-musl/cc" >> "$GITHUB_ENV" - echo "CARGO_TARGET_${TARGET_ENV_UPPER}_RUSTFLAGS=-Clink-self-contained=no" >> "$GITHUB_ENV" - - - name: Set up zig glibc wrappers - if: inputs.component == 'gateway' - run: tasks/scripts/setup-zig-cc-wrapper.sh "${{ steps.target.outputs.zig_target }}" "${{ steps.target.outputs.zig_target }}" /tmp/zig-gnu - - - name: Build ${{ steps.target.outputs.binary }} (${{ steps.target.outputs.zig_target || steps.target.outputs.target }}) - env: - # Preserve the release-codegen setting used by the old Dockerfile - # Rust build path so image artifacts keep the same release profile. - CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "1" - OPENSHELL_IMAGE_TAG: ${{ inputs['image-tag'] }} - run: | - set -euo pipefail - # z3 built with zig c++ uses libc++ symbols (std::__1::*). - # Override z3-sys default (stdc++) so Rust links the matching runtime. - if [[ "${{ inputs.component }}" == "cli" ]]; then - echo "CXXSTDLIB=c++" >> "$GITHUB_ENV" - fi - - mise x -- rustup target add "${{ steps.target.outputs.target }}" - - cargo_cmd=(cargo build) - cargo_env=() - build_target="${{ steps.target.outputs.target }}" - args=() - - if [[ "${{ inputs.component }}" == "gateway" ]]; then - cargo_cmd=(cargo zigbuild) - build_target="${{ steps.target.outputs.zig_target }}" - args+=(--features bundled-z3) - elif [[ "${{ inputs.component }}" == "sandbox" && "$SUPERVISOR_LIBC" == "glibc-static" ]]; then - # Static glibc requires the native toolchain's libc.a (build-essential - # in the CI image); cargo-zigbuild is not usable here because zig - # accepts -static for *-linux-gnu and links dynamically anyway. - export RUSTFLAGS="${RUSTFLAGS:-} -C target-feature=+crt-static" - fi - if [[ "${{ inputs.auditable }}" == "true" ]]; then - cargo_cmd=("${cargo_cmd[0]}" auditable "${cargo_cmd[@]:1}") - # mise.toml injects RUSTC_WRAPPER=sccache. Unset it after mise has - # constructed the environment so it cannot wrap cargo-auditable's - # RUSTC_WORKSPACE_WRAPPER and misidentify that wrapper as rustc. - cargo_env=(env -u RUSTC_WRAPPER) - fi - args+=( - --release - --target "$build_target" - -p "${{ steps.target.outputs.crate }}" - --bin "${{ steps.target.outputs.binary }}" - ) - if [[ -n "$FEATURES" ]]; then - args+=(--features "$FEATURES") - fi - if [[ -n "${{ steps.version.outputs.cargo_version }}" ]]; then - export GIT_DIR=/nonexistent - fi - mise x -- "${cargo_env[@]}" "${cargo_cmd[@]}" "${args[@]}" - - - name: Verify packaged binary - run: | - set -euo pipefail - BIN="target/${{ steps.target.outputs.target }}/release/${{ steps.target.outputs.binary }}" - OUTPUT="$("$BIN" --version)" - echo "$OUTPUT" - grep -q "^${{ steps.target.outputs.binary }} " <<<"$OUTPUT" - # Record linkage so image runtime drift is visible in logs. - ldd --version - ldd "$BIN" || true - if [[ "${{ inputs.component }}" == "gateway" ]] && ldd "$BIN" | grep -q 'libz3'; then - echo "gateway binary must not depend on shared libz3; enable bundled-z3 for image artifacts" >&2 - exit 1 - fi - - - name: Verify auditable dependency metadata - if: inputs.auditable - run: | - set -euo pipefail - BIN="target/${{ steps.target.outputs.target }}/release/${{ steps.target.outputs.binary }}" - cargo_packages="$( - SYFT_CHECK_FOR_APP_UPDATE=false \ - mise x -- syft "file:$BIN" -o cyclonedx-json | - jq '[.components[]? | select((.purl // "") | startswith("pkg:cargo/"))] | length' - )" - if [[ "$cargo_packages" -eq 0 ]]; then - echo "Syft did not decode any Cargo packages from $BIN" >&2 - exit 1 - fi - echo "auditable dependency metadata: $cargo_packages Cargo packages" - - - name: Verify glibc symbol floor - if: inputs.component == 'gateway' - run: | - set -euo pipefail - BIN="target/${{ steps.target.outputs.target }}/release/${{ steps.target.outputs.binary }}" - tasks/scripts/verify-glibc-symbols.sh 2.28 "$BIN" - - - name: Verify static linkage - if: inputs.component == 'sandbox' - run: | - set -euo pipefail - BIN="target/${{ steps.target.outputs.target }}/release/${{ steps.target.outputs.binary }}" - tasks/scripts/verify-static-binary.sh "$BIN" - - - name: Stage binary for prebuilt layout - run: | - set -euo pipefail - STAGE="prebuilt-binaries/$ARCH" - mkdir -p "$STAGE" - install -m 0755 \ - "target/${{ steps.target.outputs.target }}/release/${{ steps.target.outputs.binary }}" \ - "$STAGE/${{ steps.target.outputs.binary }}" - ls -lh "$STAGE/" - - - name: Upload artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: ${{ inputs['artifact-name'] != '' && inputs['artifact-name'] || format('rust-binary-{0}-linux-{1}', inputs.component, inputs.arch) }} - path: prebuilt-binaries/${{ inputs.arch }}/${{ steps.target.outputs.binary }} - retention-days: ${{ inputs['retention-days'] }} - if-no-files-found: error - - - name: sccache stats - if: always() - run: | - set +e - stats_bin="${SCCACHE_PATH:-sccache}" - "$stats_bin" --show-stats - status=$? - if [[ $status -ne 0 ]]; then - echo "::warning::sccache stats unavailable (exit $status)" - fi - exit 0 diff --git a/.github/workflows/supervisor-static-validate.yml b/.github/workflows/supervisor-static-validate.yml deleted file mode 100644 index 3b460a4588..0000000000 --- a/.github/workflows/supervisor-static-validate.yml +++ /dev/null @@ -1,70 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -name: Supervisor Static Linkage Validation - -# The glibc-static supervisor variant (SUPERVISOR_LIBC=glibc-static) has no -# other CI caller: docker-build.yml builds the default musl variant, so the -# GNU + crt-static build branch and its native-only, per-arch requirements are -# never exercised by image or release CI. Build the variant here on both -# architectures so it cannot regress unnoticed. rust-native-build.yml runs -# verify-static-binary.sh for the sandbox component, which fails the job on any -# dynamic linkage. -# -# Linkage can change from a new/updated dependency or a source change, not just -# from the build scripts, so the push path filters cover the workspace manifests -# and crate sources in addition to the build tooling. A nightly schedule is the -# unfiltered backstop for anything the filters miss. -# -# rust-native-build.yml runs on NVIDIA self-hosted runners, which reject jobs -# triggered by `pull_request`. This workflow therefore follows the repo's -# self-hosted convention (see branch-checks.yml / branch-e2e.yml): validate in -# the merge queue (pre-merge), on push to main (post-merge), nightly, and on -# demand — never on `pull_request`. - -on: - merge_group: - types: [checks_requested] - push: - branches: [main] - paths: - - "Cargo.toml" - - "Cargo.lock" - - "crates/**" - - "rust-toolchain.toml" - - "mise.toml" - - "mise.lock" - - ".cargo/config.toml" - - "tasks/scripts/stage-prebuilt-binaries.sh" - - "tasks/scripts/verify-static-binary.sh" - - ".github/workflows/rust-native-build.yml" - - ".github/workflows/supervisor-static-validate.yml" - schedule: - # Nightly (04:17 UTC) unfiltered run so a linkage regression cannot slip - # through the path filters unnoticed. Schedules run only on the default branch. - - cron: "17 4 * * *" - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -permissions: - contents: read - packages: read - -jobs: - glibc-static: - name: glibc-static supervisor (${{ matrix.arch }}) - strategy: - fail-fast: false - matrix: - arch: [amd64, arm64] - uses: ./.github/workflows/rust-native-build.yml - with: - component: sandbox - arch: ${{ matrix.arch }} - supervisor-libc: glibc-static - artifact-name: supervisor-glibc-static-${{ matrix.arch }} - retention-days: 1 - secrets: inherit From 9d8931dd4f0753dc675486003741647b632bb5c7 Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 16:14:24 +0200 Subject: [PATCH 12/17] ci: replace disallowed mise action Signed-off-by: Simon Scatton --- .github/actions/setup-e2e-kind/action.yml | 2 +- .github/actions/setup-e2e-podman/action.yml | 2 +- .github/actions/setup-e2e-vm/action.yml | 2 +- .github/actions/setup-mise/action.yml | 26 +++++++++++++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 .github/actions/setup-mise/action.yml diff --git a/.github/actions/setup-e2e-kind/action.yml b/.github/actions/setup-e2e-kind/action.yml index fd0d12c453..b9fa8375ba 100644 --- a/.github/actions/setup-e2e-kind/action.yml +++ b/.github/actions/setup-e2e-kind/action.yml @@ -33,7 +33,7 @@ inputs: runs: using: composite steps: - - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + - uses: ./.github/actions/setup-mise with: version: ${{ inputs.mise-version }} diff --git a/.github/actions/setup-e2e-podman/action.yml b/.github/actions/setup-e2e-podman/action.yml index 05399510c2..18789853fe 100644 --- a/.github/actions/setup-e2e-podman/action.yml +++ b/.github/actions/setup-e2e-podman/action.yml @@ -22,7 +22,7 @@ inputs: runs: using: composite steps: - - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + - uses: ./.github/actions/setup-mise with: version: ${{ inputs.mise-version }} diff --git a/.github/actions/setup-e2e-vm/action.yml b/.github/actions/setup-e2e-vm/action.yml index 1626f15e85..6164d999c5 100644 --- a/.github/actions/setup-e2e-vm/action.yml +++ b/.github/actions/setup-e2e-vm/action.yml @@ -55,7 +55,7 @@ runs: command -v mkfs.ext4 command -v debugfs - - uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4 + - uses: ./.github/actions/setup-mise with: version: ${{ inputs.mise-version }} diff --git a/.github/actions/setup-mise/action.yml b/.github/actions/setup-mise/action.yml new file mode 100644 index 0000000000..cdce30019f --- /dev/null +++ b/.github/actions/setup-mise/action.yml @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Setup mise +description: Install mise and the tools pinned by the repository + +inputs: + version: + description: mise release to install + required: false + default: v2026.4.25 + +runs: + using: composite + steps: + - name: Install mise and tools + shell: bash + env: + MISE_VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + curl https://mise.run | sh + export PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:$PATH" + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + echo "$HOME/.local/share/mise/shims" >> "$GITHUB_PATH" + mise install --locked From fef9105d309264ebd3949720ee623785d4472dec Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 16:36:24 +0200 Subject: [PATCH 13/17] ci: fix refactored e2e lanes Signed-off-by: Simon Scatton --- .github/workflows/e2e-docker-test.yml | 1 + .github/workflows/e2e-podman-test.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e-docker-test.yml b/.github/workflows/e2e-docker-test.yml index 8cfaa0eabc..8169740978 100644 --- a/.github/workflows/e2e-docker-test.yml +++ b/.github/workflows/e2e-docker-test.yml @@ -87,6 +87,7 @@ jobs: - name: Install OS test dependencies if: matrix.apt_packages != '' + shell: bash env: APT_PACKAGES: ${{ matrix.apt_packages }} run: | diff --git a/.github/workflows/e2e-podman-test.yml b/.github/workflows/e2e-podman-test.yml index 61d6af2844..95b1057148 100644 --- a/.github/workflows/e2e-podman-test.yml +++ b/.github/workflows/e2e-podman-test.yml @@ -25,7 +25,7 @@ on: required: false type: string default: >- - [{"suite":"rootless","runner":"ubuntu-26.04","podman_major":"5","podman_package_version":"5.7.0+ds2-3build1","conmon_package_version":"2.1.13+ds1-2","cmd":"mise run --no-deps --skip-deps e2e:podman:rootless"},{"suite":"provider-refresh-keycloak","runner":"ubuntu-26.04","podman_major":"5","podman_package_version":"5.7.0+ds2-3build1","conmon_package_version":"2.1.13+ds1-2","cmd":"mise run --no-deps --skip-deps e2e:provider-refresh-keycloak"}] + [{"suite":"provider-refresh-keycloak","runner":"ubuntu-26.04","podman_major":"5","podman_package_version":"5.7.0+ds2-3build1","conmon_package_version":"2.1.13+ds1-2","cmd":"mise run --no-deps --skip-deps e2e:provider-refresh-keycloak"}] permissions: actions: read From d175fdabe869f0d04e6380bccfbd6bd205d35644 Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 16:37:13 +0200 Subject: [PATCH 14/17] ci: check out local result action Signed-off-by: Simon Scatton --- .github/workflows/branch-e2e.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/branch-e2e.yml b/.github/workflows/branch-e2e.yml index 20081afe5c..defb2b29e7 100644 --- a/.github/workflows/branch-e2e.yml +++ b/.github/workflows/branch-e2e.yml @@ -428,6 +428,9 @@ jobs: if: always() && needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true' runs-on: ubuntu-latest steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/check-job-results with: results: ${{ toJSON(needs) }} @@ -438,6 +441,9 @@ jobs: if: always() && needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_gpu_e2e == 'true' runs-on: ubuntu-latest steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/check-job-results with: results: ${{ toJSON(needs) }} @@ -448,6 +454,9 @@ jobs: if: always() && needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_kubernetes_ha_e2e == 'true' runs-on: ubuntu-latest steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/check-job-results with: results: ${{ toJSON(needs) }} @@ -458,6 +467,9 @@ jobs: if: always() && needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_kubernetes_credential_drivers_e2e == 'true' runs-on: ubuntu-latest steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - uses: ./.github/actions/check-job-results with: results: ${{ toJSON(needs) }} From dcc9400c0216eaef83a98be273358e6c3239e5b7 Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 16:43:18 +0200 Subject: [PATCH 15/17] ci: cache mise installations Signed-off-by: Simon Scatton --- .github/actions/setup-mise/action.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/actions/setup-mise/action.yml b/.github/actions/setup-mise/action.yml index cdce30019f..28fd8844c1 100644 --- a/.github/actions/setup-mise/action.yml +++ b/.github/actions/setup-mise/action.yml @@ -13,6 +13,15 @@ inputs: runs: using: composite steps: + - name: Restore mise cache + id: mise-cache + uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 + with: + path: ~/.local/share/mise + key: mise-${{ runner.os }}-${{ runner.arch }}-${{ inputs.version }}-${{ hashFiles('mise.toml', 'mise.lock') }} + restore-keys: | + mise-${{ runner.os }}-${{ runner.arch }}-${{ inputs.version }}- + - name: Install mise and tools shell: bash env: @@ -24,3 +33,10 @@ runs: echo "$HOME/.local/bin" >> "$GITHUB_PATH" echo "$HOME/.local/share/mise/shims" >> "$GITHUB_PATH" mise install --locked + + - name: Save mise cache + if: steps.mise-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 + with: + path: ~/.local/share/mise + key: ${{ steps.mise-cache.outputs.cache-primary-key }} From 683473999be0b3d6a1fe001a74cc70ba3271a28a Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 17:03:08 +0200 Subject: [PATCH 16/17] ci: run docker builds on host runners Signed-off-by: Simon Scatton --- .github/actions/build-docker-image/action.yml | 5 ++-- .github/workflows/docker-build.yml | 26 +++++++------------ architecture/build.md | 25 +++++++----------- 3 files changed, 23 insertions(+), 33 deletions(-) diff --git a/.github/actions/build-docker-image/action.yml b/.github/actions/build-docker-image/action.yml index 6e7c06cf85..2a9503e87c 100644 --- a/.github/actions/build-docker-image/action.yml +++ b/.github/actions/build-docker-image/action.yml @@ -61,6 +61,7 @@ runs: --tag ghcr.io/nvidia/openshell/${{ inputs.component }}:${IMAGE_TAG}-${{ inputs.arch }} \ --cache-from type=gha,scope=${{ inputs.component }}-${{ inputs.arch }} \ --cache-to type=gha,mode=max,scope=${{ inputs.component }}-${{ inputs.arch }} \ - --provenance=false \ - --push \ + --provenance=mode=min \ + --attest type=sbom \ + --output type=image,push=true,oci-mediatypes=true,oci-artifact=true \ . diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index de2b4acef0..44f3531ab4 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -45,15 +45,6 @@ jobs: runner: linux-arm64-cpu8 runs-on: ${{ matrix.runner }} timeout-minutes: 20 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - options: --privileged - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - /etc/buildkit:/etc/buildkit:ro steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -74,14 +65,11 @@ jobs: needs: build runs-on: linux-amd64-cpu8 timeout-minutes: 10 - container: - image: ghcr.io/nvidia/openshell/ci:latest - credentials: - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - volumes: - - /var/run/docker.sock:/var/run/docker.sock steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs['checkout-ref'] || github.sha }} + - name: Log in to GHCR shell: bash run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin @@ -97,3 +85,9 @@ jobs: --tag "$image:${IMAGE_TAG}" \ "$image:${IMAGE_TAG}-amd64" \ "$image:${IMAGE_TAG}-arm64" + + - name: Verify merged manifest SBOM attestation + shell: bash + env: + IMAGE_REF: ghcr.io/nvidia/openshell/${{ inputs.component }}:${{ inputs.image-tag || github.sha }} + run: tasks/scripts/verify-image-sbom.sh "${IMAGE_REF}" --require-cargo diff --git a/architecture/build.md b/architecture/build.md index ff8dc424cc..d4a58165fc 100644 --- a/architecture/build.md +++ b/architecture/build.md @@ -104,7 +104,7 @@ and the supervisor image from `deploy/docker/Dockerfile.supervisor`. Neither Dockerfile compiles Rust — both copy a staged binary out of `deploy/docker/.build/prebuilt-binaries//` into the final image. -Binary staging is driven by `tasks/scripts/stage-prebuilt-binaries.sh`. Because +Local binary staging is driven by `tasks/scripts/stage-prebuilt-binaries.sh`. Because staging cross-compiles on the host, it sources `tasks/scripts/build-env.sh` and raises the per-process open-file limit before invoking `cargo zigbuild` on macOS — the static musl link opens hundreds of `.rlib` files at once and would @@ -126,9 +126,9 @@ host GNU libc target. The `glibc-static` variant uses plain `cargo build` with target architecture from `DOCKER_PLATFORM` when set. Otherwise, they require valid container engine host metadata and fail when the engine query is unavailable or reports an unsupported architecture, avoiding host-kernel -fallbacks that can target the wrong architecture. CI invokes the same staging -step via the `rust-native-build.yml` workflow (per-architecture, per-component) -and uploads the result as an artifact that the image build job downloads back +fallbacks that can target the wrong architecture. CI instead compiles binaries +in platform-specific Nix development shells through reusable workflows and the +shared `build-rust-binary` action. The image build downloads each binary artifact into the staging directory before running Buildx. Gateway and supervisor binaries staged into branch E2E, Release Dev, and Release @@ -141,17 +141,12 @@ is a different artifact from the source SBOM produced by `syft dir:.` in `tasks/sbom.toml`, which describes the checkout, and from the image SBOM attestation below, which describes a published image. -`docker-build.yml` and `rust-native-build.yml` take an `auditable` input that -defaults to false. The branch E2E gateway and supervisor image builds set it to -true so E2E exercises release-parity image binaries; other PR image builds and -standalone release artifacts stay non-auditable. The CI image gains the pinned -`cargo-auditable` tool through `mise install --locked` but ships no auditable -OpenShell binary of its own. Local staging opts in with -`OPENSHELL_AUDITABLE=1`. sccache's `RUSTC_WRAPPER` is unset only around auditable -builds, because it would otherwise wrap `cargo-auditable`'s workspace wrapper and -be misidentified as `rustc`. Auditable builds are verified by scanning the built -binary with Syft and requiring at least one decoded Cargo package; the check runs -only for those builds. +The shared binary build action compiles release artifacts with `cargo auditable`. +Branch E2E, Release Dev, and Release Tag image jobs stage those same artifacts +instead of rebuilding binaries in Docker. Each binary build scans its output with +Syft and requires at least one decoded Cargo package before uploading the +artifact. The CI image gains the pinned `cargo-auditable` tool through +`mise install --locked` but ships no auditable OpenShell binary of its own. Pushed Docker images carry minimal SLSA provenance and a per-platform SPDX SBOM generated by BuildKit's default Syft scanner. The registry exporter uses OCI From 312c797b34c7591b8511911860ddf8fa14837ddc Mon Sep 17 00:00:00 2001 From: Simon Scatton Date: Thu, 27 Aug 2026 18:23:14 +0200 Subject: [PATCH 17/17] ci: disable unstable kubernetes e2e lanes Signed-off-by: Simon Scatton --- .github/workflows/branch-e2e.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/branch-e2e.yml b/.github/workflows/branch-e2e.yml index defb2b29e7..5712fb0c0e 100644 --- a/.github/workflows/branch-e2e.yml +++ b/.github/workflows/branch-e2e.yml @@ -44,8 +44,8 @@ jobs: push) run_core_e2e="$(jq -r 'index("test:e2e") != null' <<< "$LABELS_JSON")" run_gpu_e2e="$(jq -r 'index("test:e2e-gpu") != null' <<< "$LABELS_JSON")" - run_kubernetes_ha_e2e="$(jq -r 'index("test:e2e-kubernetes") != null' <<< "$LABELS_JSON")" - run_kubernetes_credential_drivers_e2e="$(jq -r 'index("test:e2e-kubernetes") != null' <<< "$LABELS_JSON")" + run_kubernetes_ha_e2e=false + run_kubernetes_credential_drivers_e2e=false ;; merge_group) # Merge groups have no PR labels. When GPU E2E is required as documented @@ -59,8 +59,8 @@ jobs: *) run_core_e2e=true run_gpu_e2e=true - run_kubernetes_ha_e2e=true - run_kubernetes_credential_drivers_e2e=true + run_kubernetes_ha_e2e=false + run_kubernetes_credential_drivers_e2e=false ;; esac if [ "$run_core_e2e" = "true" ] || [ "$run_gpu_e2e" = "true" ] || [ "$run_kubernetes_ha_e2e" = "true" ] || [ "$run_kubernetes_credential_drivers_e2e" = "true" ]; then