From 3f771b472f42ea5dfef2e3fd483815bfcfcd69d2 Mon Sep 17 00:00:00 2001 From: Evan Lezar Date: Fri, 21 Aug 2026 12:25:45 +0200 Subject: [PATCH] test(snap): smoke PR artifacts before release Signed-off-by: Evan Lezar --- .agents/skills/test-release-canary/SKILL.md | 4 + .github/actions/snap-gateway-smoke/action.yml | 73 ++++++++ .github/workflows/branch-e2e.yml | 49 ++++- .github/workflows/e2e-label-help.yml | 7 +- .github/workflows/release-canary.yml | 64 +------ .github/workflows/snap-build.yml | 170 ++++++++++++++++++ .github/workflows/snap-package.yml | 138 +++----------- CI.md | 8 +- 8 files changed, 335 insertions(+), 178 deletions(-) create mode 100644 .github/actions/snap-gateway-smoke/action.yml create mode 100644 .github/workflows/snap-build.yml diff --git a/.agents/skills/test-release-canary/SKILL.md b/.agents/skills/test-release-canary/SKILL.md index a08bb0ed45..7436c7394d 100644 --- a/.agents/skills/test-release-canary/SKILL.md +++ b/.agents/skills/test-release-canary/SKILL.md @@ -29,6 +29,10 @@ validation lives in the `TypeScript SDK` branch check, including a publish dry-run. The tagged release workflow publishes the package to GitHub Packages; verify that job directly when diagnosing SDK publication failures. +The PR-only `test:snap` label runs the same gateway lifecycle smoke test in +`Branch E2E Checks`, but packages a Snap from that workflow's amd64 prebuilt +binary artifacts. It does not require or publish a Release Dev artifact. + ## Trigger paths The workflow has two triggers: diff --git a/.github/actions/snap-gateway-smoke/action.yml b/.github/actions/snap-gateway-smoke/action.yml new file mode 100644 index 0000000000..d1e731daaa --- /dev/null +++ b/.github/actions/snap-gateway-smoke/action.yml @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Snap gateway smoke test +description: Install a local OpenShell Snap with Docker and verify gateway readiness. + +inputs: + snap-path: + description: Path to the locally built Snap artifact. + required: true + telemetry-enabled: + description: Value supplied to the gateway service environment. + required: false + default: "false" + +runs: + using: composite + steps: + - name: Install snapd + shell: bash + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y snapd + sudo systemctl enable --now snapd.socket + sudo systemctl start snapd + sudo snap wait system seed.loaded + + - name: Install Docker snap + shell: bash + run: sudo snap install docker + + - name: Install, connect, and verify gateway + shell: bash + env: + SNAP_PATH: ${{ inputs.snap-path }} + TELEMETRY_ENABLED: ${{ inputs.telemetry-enabled }} + run: | + set -Eeuo pipefail + + collect_diagnostics() { + set +e + sudo snap services openshell + sudo snap connections openshell + sudo snap changes + sudo systemctl status snap.openshell.gateway.service --no-pager + sudo journalctl -b -u snap.openshell.gateway.service --no-pager -n 300 + sudo journalctl -b -u snapd.service --no-pager -n 300 + sudo snap logs openshell.gateway -n=300 + sudo ss -ltnp '( sport = :17670 )' + } + trap collect_diagnostics ERR + + snap_path=$(compgen -G "$SNAP_PATH" | head -n 1 || true) + test -n "$snap_path" + sudo systemctl set-environment "OPENSHELL_TELEMETRY_ENABLED=${TELEMETRY_ENABLED}" + sudo snap install "$snap_path" --dangerous + sudo snap connect openshell:docker docker:docker-daemon + sudo snap connect openshell:log-observe + sudo snap connect openshell:system-observe + + openshell --version + sudo snap services openshell + openshell gateway add http://127.0.0.1:17670 --local --name snap-docker + openshell gateway select snap-docker + for _ in $(seq 1 30); do + if openshell status; then + exit 0 + fi + sleep 1 + done + echo "Gateway did not become ready within 30 seconds" >&2 + exit 1 diff --git a/.github/workflows/branch-e2e.yml b/.github/workflows/branch-e2e.yml index 37154c75df..b7d9287541 100644 --- a/.github/workflows/branch-e2e.yml +++ b/.github/workflows/branch-e2e.yml @@ -27,6 +27,7 @@ jobs: run_gpu_e2e: ${{ steps.labels.outputs.run_gpu_e2e }} run_kubernetes_ha_e2e: ${{ steps.labels.outputs.run_kubernetes_ha_e2e }} run_kubernetes_credential_drivers_e2e: ${{ steps.labels.outputs.run_kubernetes_credential_drivers_e2e }} + run_snap_smoke: ${{ steps.labels.outputs.run_snap_smoke }} run_any_e2e: ${{ steps.labels.outputs.run_any_e2e }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -46,6 +47,7 @@ jobs: 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_snap_smoke="$(jq -r 'index("test:snap") != null' <<< "$LABELS_JSON")" ;; merge_group) # Merge groups have no PR labels. When GPU E2E is required as documented @@ -55,15 +57,17 @@ jobs: run_gpu_e2e=true run_kubernetes_ha_e2e=false run_kubernetes_credential_drivers_e2e=false + run_snap_smoke=true ;; *) run_core_e2e=true run_gpu_e2e=true run_kubernetes_ha_e2e=true run_kubernetes_credential_drivers_e2e=true + run_snap_smoke=true ;; esac - if [ "$run_core_e2e" = "true" ] || [ "$run_gpu_e2e" = "true" ] || [ "$run_kubernetes_ha_e2e" = "true" ] || [ "$run_kubernetes_credential_drivers_e2e" = "true" ]; then + if [ "$run_core_e2e" = "true" ] || [ "$run_gpu_e2e" = "true" ] || [ "$run_kubernetes_ha_e2e" = "true" ] || [ "$run_kubernetes_credential_drivers_e2e" = "true" ] || [ "$run_snap_smoke" = "true" ]; then run_any_e2e=true else run_any_e2e=false @@ -73,6 +77,7 @@ jobs: echo "run_gpu_e2e=$run_gpu_e2e" echo "run_kubernetes_ha_e2e=$run_kubernetes_ha_e2e" echo "run_kubernetes_credential_drivers_e2e=$run_kubernetes_credential_drivers_e2e" + echo "run_snap_smoke=$run_snap_smoke" echo "run_any_e2e=$run_any_e2e" } >> "$GITHUB_OUTPUT" @@ -86,6 +91,7 @@ jobs: with: component: gateway image-tag: ${{ github.sha }} + platform: ${{ (needs.pr_metadata.outputs.run_core_e2e == 'true' || needs.pr_metadata.outputs.run_gpu_e2e == 'true' || needs.pr_metadata.outputs.run_kubernetes_ha_e2e == 'true' || needs.pr_metadata.outputs.run_kubernetes_credential_drivers_e2e == 'true') && 'linux/amd64,linux/arm64' || 'linux/amd64' }} build-supervisor: needs: [pr_metadata] @@ -97,6 +103,7 @@ jobs: with: component: supervisor image-tag: ${{ github.sha }} + platform: ${{ (needs.pr_metadata.outputs.run_core_e2e == 'true' || needs.pr_metadata.outputs.run_gpu_e2e == 'true' || needs.pr_metadata.outputs.run_kubernetes_ha_e2e == 'true' || needs.pr_metadata.outputs.run_kubernetes_credential_drivers_e2e == 'true') && 'linux/amd64,linux/arm64' || 'linux/amd64' }} build-cli: needs: [pr_metadata] @@ -107,7 +114,45 @@ jobs: uses: ./.github/workflows/docker-build.yml with: component: cli - platform: linux/amd64,linux/arm64 + platform: ${{ (needs.pr_metadata.outputs.run_core_e2e == 'true' || needs.pr_metadata.outputs.run_gpu_e2e == 'true' || needs.pr_metadata.outputs.run_kubernetes_ha_e2e == 'true' || needs.pr_metadata.outputs.run_kubernetes_credential_drivers_e2e == 'true') && 'linux/amd64,linux/arm64' || 'linux/amd64' }} + + build-snap: + name: Build Snap + needs: [pr_metadata, build-gateway, build-supervisor, build-cli] + if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_snap_smoke == 'true' + permissions: + actions: read + contents: read + packages: read + uses: ./.github/workflows/snap-build.yml + with: + checkout-ref: ${{ github.sha }} + artifact-layout: raw + build-matrix: '{"include":[{"arch":"amd64","runner":"linux-amd64-cpu8"}]}' + cli-artifact-prefix: rust-binary-cli + gateway-artifact-prefix: rust-binary-gateway + supervisor-artifact-prefix: rust-binary-supervisor + + snap-smoke: + name: Snap Smoke + needs: [pr_metadata, build-snap] + if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_snap_smoke == 'true' + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + actions: read + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Download locally built Snap + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: snap-linux-amd64 + path: release/ + - name: Install and verify local Snap + uses: ./.github/actions/snap-gateway-smoke + with: + snap-path: ./release/*.snap build-driver-vm-linux: needs: [pr_metadata] diff --git a/.github/workflows/e2e-label-help.yml b/.github/workflows/e2e-label-help.yml index e5158dca3e..deca0b8e5a 100644 --- a/.github/workflows/e2e-label-help.yml +++ b/.github/workflows/e2e-label-help.yml @@ -19,7 +19,7 @@ permissions: {} jobs: hint: name: Post next-step hint for E2E label - if: github.event.label.name == 'test:e2e' || github.event.label.name == 'test:e2e-gpu' || github.event.label.name == 'test:e2e-kubernetes' + if: github.event.label.name == 'test:e2e' || github.event.label.name == 'test:e2e-gpu' || github.event.label.name == 'test:e2e-kubernetes' || github.event.label.name == 'test:snap' runs-on: ubuntu-latest permissions: pull-requests: write @@ -55,6 +55,11 @@ jobs: build_summary="gateway and supervisor images" status_summary="This is an optional proof-of-life suite; failures are visible in the workflow run but do not publish a required CI gate status." ;; + test:snap) + suite_summary="Snap packaging and gateway lifecycle smoke test" + build_summary="amd64 CLI, gateway, and supervisor binaries" + status_summary="This is an optional proof-of-life suite; failures are visible in the workflow run but do not publish a required CI gate status." + ;; *) echo "Unrecognized label $LABEL_NAME"; exit 1 ;; esac diff --git a/.github/workflows/release-canary.yml b/.github/workflows/release-canary.yml index 051584a84e..3dd3d7a88c 100644 --- a/.github/workflows/release-canary.yml +++ b/.github/workflows/release-canary.yml @@ -163,19 +163,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 steps: - - name: Install snapd - run: | - set -euo pipefail - sudo apt-get update - sudo apt-get install -y snapd - sudo systemctl enable --now snapd.socket - sudo systemctl start snapd - sudo snap wait system seed.loaded - - - name: Install Docker snap - run: | - set -euo pipefail - sudo snap install docker + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.workflow_run.head_sha || github.sha }} + fetch-depth: 1 - name: Download snap from release-dev artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -186,48 +177,11 @@ jobs: path: release/ merge-multiple: true - - name: Install snap (dangerous — from release, not store) - run: | - set -euo pipefail - sudo systemctl set-environment \ - "OPENSHELL_TELEMETRY_ENABLED=${OPENSHELL_TELEMETRY_ENABLED}" - sudo snap install ./release/*.snap --dangerous - - - name: Connect interfaces - run: | - set -euo pipefail - sudo snap connect openshell:docker docker:docker-daemon - sudo snap connect openshell:log-observe - sudo snap connect openshell:system-observe - - - name: Register snap gateway and check status - run: | - set -euo pipefail - openshell --version - sudo snap services openshell - openshell gateway add http://127.0.0.1:17670 --local --name snap-docker - openshell gateway select snap-docker - for _ in $(seq 1 30); do - if openshell status; then - exit 0 - fi - sleep 1 - done - echo "Gateway did not become ready within 30 seconds" >&2 - exit 1 - - - name: Collect Snap diagnostics - if: failure() - run: | - set +e - sudo snap services openshell - sudo snap connections openshell - sudo snap changes - sudo systemctl status snap.openshell.gateway.service --no-pager - sudo journalctl -b -u snap.openshell.gateway.service --no-pager -n 300 - sudo journalctl -b -u snapd.service --no-pager -n 300 - sudo snap logs openshell.gateway -n=300 - sudo ss -ltnp '( sport = :17670 )' + - name: Install and verify local Snap + uses: ./.github/actions/snap-gateway-smoke + with: + snap-path: ./release/*.snap + telemetry-enabled: ${{ env.OPENSHELL_TELEMETRY_ENABLED }} kubernetes: name: Kubernetes Helm (kind) diff --git a/.github/workflows/snap-build.yml b/.github/workflows/snap-build.yml new file mode 100644 index 0000000000..934c1a9280 --- /dev/null +++ b/.github/workflows/snap-build.yml @@ -0,0 +1,170 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: Build Snap + +on: + workflow_call: + inputs: + checkout-ref: + required: true + type: string + artifact-layout: + description: "Layout of the prebuilt binary artifacts (tarball or raw)" + required: false + type: string + default: tarball + build-matrix: + description: "JSON matrix of architectures to package" + required: false + type: string + default: '{"include":[{"arch":"amd64","runner":"linux-amd64-cpu8"},{"arch":"arm64","runner":"linux-arm64-cpu8"}]}' + cli-artifact-name: + required: false + type: string + default: "" + cli-artifact-prefix: + required: false + type: string + default: "" + gateway-artifact-name: + required: false + type: string + default: "" + gateway-artifact-prefix: + required: false + type: string + default: "" + supervisor-artifact-name: + required: false + type: string + default: "" + supervisor-artifact-prefix: + required: false + type: string + default: "" + +permissions: + contents: read + +defaults: + run: + shell: bash + +jobs: + build-snap: + name: Build Snap (Linux ${{ matrix.arch }}) + strategy: + matrix: ${{ fromJSON(inputs.build-matrix) }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 60 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ inputs.checkout-ref }} + fetch-depth: 0 + + - name: Install snapd + run: | + set -euo pipefail + if ! command -v snapd >/dev/null 2>&1; then + sudo apt-get update + sudo apt-get install -y snapd + fi + sudo systemctl enable --now snapd.socket + sudo systemctl start snapd + sudo snap wait system seed.loaded + + - name: Install LXD + run: | + set -euo pipefail + sudo snap install lxd + sudo usermod -aG lxd "$USER" + sudo lxd waitready + sudo lxd init --auto + sudo iptables -P FORWARD ACCEPT + sudo chgrp lxd /var/snap/lxd/common/lxd/unix.socket + sudo chmod 660 /var/snap/lxd/common/lxd/unix.socket + + - name: Install snapcraft + run: sudo snap install snapcraft --classic + + - name: Download prebuilt CLI binary + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ inputs.artifact-layout == 'raw' && format('{0}-linux-{1}', inputs.cli-artifact-prefix, matrix.arch) || inputs.cli-artifact-name || format('cli-linux-{0}', matrix.arch) }} + path: prebuilt/cli + + - name: Download prebuilt gateway binary + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ inputs.artifact-layout == 'raw' && format('{0}-linux-{1}', inputs.gateway-artifact-prefix, matrix.arch) || inputs.gateway-artifact-name || format('gateway-binary-linux-{0}', matrix.arch) }} + path: prebuilt/gateway + + - name: Download prebuilt sandbox binary + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ inputs.artifact-layout == 'raw' && format('{0}-linux-{1}', inputs.supervisor-artifact-prefix, matrix.arch) || inputs.supervisor-artifact-name || format('supervisor-binary-linux-{0}', matrix.arch) }} + path: prebuilt/sandbox + + - name: Prepare snap build directory + run: | + set -euo pipefail + case "${{ inputs.artifact-layout }}" in + tarball) + for directory in cli gateway sandbox; do + for tarball in "prebuilt/$directory"/*.tar.gz; do + test -f "$tarball" || continue + tar -xzf "$tarball" -C "prebuilt/$directory" + done + done + ;; + raw) ;; + *) + echo "unsupported artifact layout: ${{ inputs.artifact-layout }}" >&2 + exit 1 + ;; + esac + + find_binary() { + find "$1" -type f -name "$2" -print -quit + } + cli="$(find_binary prebuilt/cli openshell)" + gateway="$(find_binary prebuilt/gateway openshell-gateway)" + sandbox="$(find_binary prebuilt/sandbox openshell-sandbox)" + test -n "$cli" && test -n "$gateway" && test -n "$sandbox" + + mkdir -p snap/prebuilt/meta/gui + install -m 0755 "$cli" snap/prebuilt/openshell + install -m 0755 "$gateway" snap/prebuilt/openshell-gateway + install -m 0755 "$sandbox" snap/prebuilt/openshell-sandbox + install -m 0755 tasks/scripts/snap-gateway-wrapper.sh snap/prebuilt/openshell-gateway-wrapper + cp LICENSE README.md snap/prebuilt/ + cp snap/local/term.desktop snap/prebuilt/meta/gui/term.desktop + cp snap/local/icon.png snap/prebuilt/meta/gui/icon.png + python3 tasks/scripts/release.py get-version --snap > snap/prebuilt/version + + - name: Build snap + run: | + set -euo pipefail + runtime_dir="/run/user/$(id -u)" + sudo install -d -m 0700 -o "$(id -u)" -g "$(id -g)" "$runtime_dir" + export XDG_RUNTIME_DIR="$runtime_dir" + sg lxd -c "XDG_RUNTIME_DIR=${runtime_dir} snapcraft pack -v" + + - name: Upload snapcraft logs on failure + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: snapcraft-logs-${{ matrix.arch }} + path: "~/.local/state/snapcraft/log/snapcraft-*.log" + retention-days: 7 + + - name: Upload snap artifact (${{ matrix.arch }}) + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: snap-linux-${{ matrix.arch }} + path: | + *.snap + *.comp + retention-days: 5 diff --git a/.github/workflows/snap-package.yml b/.github/workflows/snap-package.yml index ed248b79e5..4c2d9eb4fc 100644 --- a/.github/workflows/snap-package.yml +++ b/.github/workflows/snap-package.yml @@ -17,7 +17,6 @@ on: required: true type: string description: "GitHub deployment environment for approval gates (e.g., latest/edge, latest/stable)" - secrets: publish-credentials: required: true @@ -32,7 +31,14 @@ defaults: jobs: build-snap: - name: Build Snap (Linux ${{ matrix.arch }}) + name: Build Snap + uses: ./.github/workflows/snap-build.yml + with: + checkout-ref: ${{ inputs.checkout-ref }} + + publish-snap: + name: Publish Snap (Linux ${{ matrix.arch }}) + needs: [build-snap] strategy: matrix: include: @@ -41,14 +47,9 @@ jobs: - arch: arm64 runner: linux-arm64-cpu8 runs-on: ${{ matrix.runner }} - timeout-minutes: 60 + timeout-minutes: 20 environment: ${{ inputs.github-environment }} steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ inputs.checkout-ref }} - fetch-depth: 0 - - name: Install snapd run: | set -euo pipefail @@ -60,127 +61,30 @@ jobs: sudo systemctl start snapd sudo snap wait system seed.loaded - - name: Install LXD - run: | - set -euo pipefail - sudo snap install lxd - sudo usermod -aG lxd "$USER" - sudo lxd waitready - sudo lxd init --auto - sudo iptables -P FORWARD ACCEPT - sudo chgrp lxd /var/snap/lxd/common/lxd/unix.socket - sudo chmod 660 /var/snap/lxd/common/lxd/unix.socket - - name: Install snapcraft - run: | - set -euo pipefail - sudo snap install snapcraft --classic + run: sudo snap install snapcraft --classic - - name: Download prebuilt CLI binary + - name: Download snap artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: cli-linux-${{ matrix.arch }} - path: prebuilt/cli - - - name: Download prebuilt gateway binary - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: gateway-binary-linux-${{ matrix.arch }} - path: prebuilt/gateway - - - name: Download prebuilt sandbox binary - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: supervisor-binary-linux-${{ matrix.arch }} - path: prebuilt/sandbox - - - name: Extract 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 - ls -laR prebuilt/ - - - name: Prepare snap build directory - run: | - set -euo pipefail - mkdir -p snap/prebuilt - - cp prebuilt/cli/openshell snap/prebuilt/openshell - cp prebuilt/gateway/openshell-gateway snap/prebuilt/openshell-gateway - cp prebuilt/sandbox/openshell-sandbox snap/prebuilt/openshell-sandbox - - cp tasks/scripts/snap-gateway-wrapper.sh snap/prebuilt/openshell-gateway-wrapper - cp LICENSE snap/prebuilt/ - cp README.md snap/prebuilt/ - - mkdir -p snap/prebuilt/meta/gui - cp snap/local/term.desktop snap/prebuilt/meta/gui/term.desktop - cp snap/local/icon.png snap/prebuilt/meta/gui/icon.png - - python3 tasks/scripts/release.py get-version --snap > snap/prebuilt/version - - - name: Build snap - run: | - set -euo pipefail - runtime_dir="/run/user/$(id -u)" - sudo install -d -m 0700 -o "$(id -u)" -g "$(id -g)" "$runtime_dir" - export XDG_RUNTIME_DIR="$runtime_dir" - sg lxd -c "XDG_RUNTIME_DIR=${runtime_dir} snapcraft pack -v" - - - name: Upload snapcraft logs on failure - if: failure() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: snapcraft-logs - path: "~/.local/state/snapcraft/log/snapcraft-*.log" - retention-days: 7 - - - name: Capture snap filename - id: capture - run: | - set -euo pipefail - SNAP_FILE=$(ls -1 *.snap 2>/dev/null | head -1) - if [ -z "$SNAP_FILE" ]; then - echo "ERROR: No .snap file found after snapcraft pack" - exit 1 - fi - echo "snap-file=${SNAP_FILE}" >> $GITHUB_OUTPUT - echo "Built snap: ${SNAP_FILE}" - - - name: Upload snap artifact (${{ matrix.arch }}) - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: snap-linux-${{ matrix.arch }} - path: | - ${{ steps.capture.outputs.snap-file }} - *.comp - retention-days: 5 + path: release/ - name: Upload snap to Snap Store env: SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.publish-credentials }} run: | set -euo pipefail - SNAP_FILE="${{ steps.capture.outputs.snap-file }}" - SNAP_NAME="${SNAP_FILE%.snap}" - SNAP_NAME="${SNAP_NAME%%_*}" + snap_file=$(find release -maxdepth 1 -name '*.snap' -print -quit) + test -n "$snap_file" + snap_name="${snap_file##*/}" + snap_name="${snap_name%.snap}" + snap_name="${snap_name%%_*}" - COMPONENT_ARGS=() + component_args=() shopt -s nullglob - for comp in "${SNAP_NAME}"+*.comp; do - echo "Adding component: $comp" - COMPONENT_ARGS+=(--component "$comp") + for component in release/"${snap_name}"+*.comp; do + component_args+=(--component "$component") done - echo "Uploading $SNAP_FILE to ${{ inputs.upload-channel }}" - snapcraft upload --release "${{ inputs.upload-channel }}" "$SNAP_FILE" "${COMPONENT_ARGS[@]}" + snapcraft upload --release "${{ inputs.upload-channel }}" "$snap_file" "${component_args[@]}" diff --git a/CI.md b/CI.md index aae22f4c4f..3c32734070 100644 --- a/CI.md +++ b/CI.md @@ -12,7 +12,7 @@ PR CI that runs on NVIDIA self-hosted runners uses NVIDIA's copy-pr-bot. The bot Merge queue validation is a second integration gate for `main`. After a PR has passed the required PR-head statuses, a maintainer adds it to the merge queue. GitHub creates a temporary merge-group branch that combines the latest `main`, the queued PR, and any earlier queued PRs. The same required `OpenShell / ...` status contexts are then published against the merge-group SHA before GitHub merges it. -Three opt-in labels enable the long-running E2E suites: +Four opt-in labels enable the long-running E2E suites: - `test:e2e` runs the standard Docker, rootless Podman, Kubernetes, and VM E2E suites in `Branch E2E Checks` @@ -20,6 +20,8 @@ Three opt-in labels enable the long-running E2E suites: - `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` +- `test:snap` builds an amd64 Snap from the branch's prebuilt binaries and + installs it on a fresh Ubuntu runner with Docker Snap connected 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. 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. @@ -75,7 +77,7 @@ Flow: 1. Open the PR. copy-pr-bot mirrors it to `pull-request/` automatically. 2. The mirror push runs `Branch Checks` automatically. `Required CI Gates` keeps the PR blocked until the mirror exists, matches the PR head SHA, and the required push-based workflow succeeds. The first `Branch E2E Checks` run only resolves metadata and skips expensive jobs unless an E2E label is already set. -3. A maintainer applies `test:e2e`, `test:e2e-gpu`, and/or `test:e2e-kubernetes`. `E2E Label Help` posts a comment with a link to the existing gated workflow run. +3. A maintainer applies `test:e2e`, `test:e2e-gpu`, `test:e2e-kubernetes`, and/or `test:snap`. `E2E Label Help` posts a comment with a link to the existing gated workflow run. 4. The maintainer opens that link and clicks **Re-run all jobs**. This time `pr_metadata` sees the label and the build/E2E jobs run. 5. When the run finishes, the matching `OpenShell / ...` gate status flips to green automatically. 6. New commits push to the mirror automatically and re-trigger `Branch Checks` plus any labeled E2E jobs in `Branch E2E Checks`. @@ -136,7 +138,7 @@ The bot's full administrator documentation is internal to NVIDIA. The only comma | File | Role | |---|---| | `.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/branch-e2e.yml` | Standard, GPU, Kubernetes HA, Kubernetes credential-driver, and Snap smoke E2E. PR mirror pushes use `test:e2e`, `test:e2e-gpu`, `test:e2e-kubernetes`, and `test:snap` labels; merge groups run core, GPU, and Snap smoke E2E. | | `.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/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. | | `.github/actions/pr-merge-base/action.yml` | Composite action that resolves and fetches the merge-base commit for `pull-request/` push workflows. |