chore: merge main into supervisor middleware SDK branch

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
This commit is contained in:
Piotr Mlocek
2026-09-10 14:59:57 -07:00
670 changed files with 61231 additions and 38593 deletions
+34
View File
@@ -41,6 +41,7 @@ jobs:
needs: pr_metadata
if: needs.pr_metadata.outputs.should_run == 'true'
runs-on: linux-amd64-cpu8
timeout-minutes: 30
container:
image: ghcr.io/nvidia/openshell/ci:latest
credentials:
@@ -65,6 +66,7 @@ jobs:
needs: pr_metadata
if: needs.pr_metadata.outputs.should_run == 'true'
runs-on: linux-amd64-cpu8
timeout-minutes: 30
container:
image: ghcr.io/nvidia/openshell/ci:latest
credentials:
@@ -84,6 +86,7 @@ jobs:
needs: pr_metadata
if: needs.pr_metadata.outputs.should_run == 'true'
runs-on: linux-amd64-cpu8
timeout-minutes: 30
defaults:
run:
shell: nix develop .#devShells.x86_64-linux.default -c bash -euo pipefail {0}
@@ -117,6 +120,7 @@ jobs:
- runner: macos-15-xlarge
system: aarch64-darwin
runs-on: ${{ matrix.runner }}
timeout-minutes: 90
defaults:
run:
shell: nix develop .#devShells.${{ matrix.system }}.default -c bash -euo pipefail {0}
@@ -154,12 +158,14 @@ jobs:
cargo fmt --all -- --check
cargo fmt --manifest-path e2e/rust/Cargo.toml --all -- --check
cargo fmt --manifest-path examples/governance-interceptor/Cargo.toml --all -- --check
cargo fmt --manifest-path examples/supervisor-middleware-content-guard/Cargo.toml --all -- --check
- name: Lint
run: |
cargo clippy --workspace --all-targets -- -D warnings
cargo clippy --manifest-path e2e/rust/Cargo.toml --all-targets -- -D warnings
cargo check --manifest-path examples/governance-interceptor/Cargo.toml --all-targets
cargo check --manifest-path examples/supervisor-middleware-content-guard/Cargo.toml --all-targets
- name: Test
env:
@@ -167,6 +173,30 @@ jobs:
run: |
cargo nextest run --profile ci --workspace --features openshell-server/test-support
- name: Verify telemetry can be compiled out
run: |
cargo build -p openshell-gateway --bin openshell-gateway
tasks/scripts/verify-telemetry-compiled-out.sh present target/debug/openshell-gateway
cargo build -p openshell-gateway --bin openshell-gateway --no-default-features --features defaults-without-telemetry
tasks/scripts/verify-telemetry-compiled-out.sh absent target/debug/openshell-gateway
cargo build -p openshell-sandbox --bin openshell-sandbox --no-default-features --features defaults-without-telemetry
tasks/scripts/verify-telemetry-compiled-out.sh absent target/debug/openshell-sandbox
- name: Verify the defaults-without-telemetry feature alias tracks the default feature set
run: tasks/scripts/verify-defaults-without-telemetry.sh
- name: Verify system CA roots build mode compiles and excludes bundled Mozilla roots
run: |
cargo check -p openshell-sandbox --all-targets --no-default-features --features system-ca-roots
if cargo tree -p openshell-sandbox -i webpki-roots --no-default-features --features system-ca-roots 2>/dev/null | grep -q webpki-roots; then
echo "ERROR: webpki-roots found in system CA roots build" >&2
exit 1
fi
if cargo tree -p openshell-sandbox -i webpki-root-certs --no-default-features --features system-ca-roots 2>/dev/null | grep -q webpki-root-certs; then
echo "ERROR: webpki-root-certs found in system CA roots build" >&2
exit 1
fi
python:
name: Python (${{ matrix.runner }})
needs: pr_metadata
@@ -176,6 +206,7 @@ jobs:
matrix:
runner: [linux-amd64-cpu8, linux-arm64-cpu8]
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
container:
image: ghcr.io/nvidia/openshell/ci:latest
credentials:
@@ -207,6 +238,7 @@ jobs:
needs: pr_metadata
if: needs.pr_metadata.outputs.should_run == 'true'
runs-on: linux-amd64-cpu8
timeout-minutes: 30
container:
image: ghcr.io/nvidia/openshell/ci:latest
credentials:
@@ -226,6 +258,7 @@ jobs:
needs: pr_metadata
if: needs.pr_metadata.outputs.should_run == 'true'
runs-on: linux-amd64-cpu8
timeout-minutes: 30
container:
image: ghcr.io/nvidia/openshell/ci:latest
credentials:
@@ -245,6 +278,7 @@ jobs:
needs: pr_metadata
if: needs.pr_metadata.outputs.should_run == 'true'
runs-on: linux-amd64-cpu8
timeout-minutes: 30
container:
image: ghcr.io/nvidia/openshell/ci:latest
credentials:
+50 -26
View File
@@ -105,6 +105,28 @@ jobs:
cargo-version: ${{ needs.version.outputs.cargo }}
secrets: inherit
build-conformance:
needs: [pr_metadata, version]
if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_any_e2e == 'true'
permissions:
contents: read
strategy:
matrix:
include:
- triple: x86_64-unknown-linux-musl
runner: linux-amd64-cpu8
- triple: aarch64-unknown-linux-musl
runner: linux-arm64-cpu8
uses: ./.github/workflows/build-binaries.yml
with:
package: openshell-conformance-cli
binary: openshell-conformance
artifact-name: openshell-conformance-${{ matrix.triple }}
triple: ${{ matrix.triple }}
runner: ${{ matrix.runner }}
cargo-version: ${{ needs.version.outputs.cargo }}
secrets: inherit
build-sandbox:
needs: version
permissions:
@@ -133,23 +155,17 @@ jobs:
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:
package: openshell-server
package: openshell-gateway
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:
@@ -163,9 +179,7 @@ jobs:
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:
@@ -179,9 +193,7 @@ jobs:
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:
@@ -195,9 +207,7 @@ jobs:
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:
@@ -241,7 +251,7 @@ jobs:
secrets: inherit
docker-e2e:
needs: [pr_metadata, build-cli, build-gateway, build-supervisor-image]
needs: [pr_metadata, build-cli, build-conformance, build-gateway, build-supervisor-image]
if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true'
permissions:
actions: read
@@ -251,9 +261,10 @@ jobs:
with:
image-tag: ${{ github.sha }}
runner: linux-arm64-cpu8
conformance-artifact-prefix: openshell-conformance
podman-e2e:
needs: [pr_metadata, build-cli, build-gateway, build-supervisor-image]
needs: [pr_metadata, build-cli, build-conformance, build-gateway, build-supervisor-image]
if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true'
permissions:
actions: read
@@ -262,18 +273,21 @@ jobs:
uses: ./.github/workflows/e2e-podman-test.yml
with:
image-tag: ${{ github.sha }}
conformance-artifact-prefix: openshell-conformance
vm-e2e:
needs: [pr_metadata, build-cli, build-gateway, build-vm-driver]
needs: [pr_metadata, build-cli, build-conformance, 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
with:
conformance-artifact-prefix: openshell-conformance
docker-external-driver-e2e:
needs: [pr_metadata, build-cli, build-gateway-plain, build-driver-docker, build-supervisor-image]
needs: [pr_metadata, build-cli, build-conformance, 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
@@ -285,11 +299,12 @@ jobs:
runner: linux-arm64-cpu8
gateway-artifact: openshell-gateway-plain-aarch64-unknown-linux-gnu
external-driver-binary: openshell-driver-docker
conformance-artifact-prefix: openshell-conformance
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]
needs: [pr_metadata, build-cli, build-conformance, 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
@@ -300,11 +315,12 @@ jobs:
image-tag: ${{ github.sha }}
gateway-artifact: openshell-gateway-plain-x86_64-unknown-linux-gnu
external-driver-binary: openshell-driver-podman
conformance-artifact-prefix: openshell-conformance
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]
needs: [pr_metadata, build-cli, build-conformance, 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
@@ -315,9 +331,10 @@ jobs:
gateway-artifact: openshell-gateway-plain-x86_64-unknown-linux-gnu
suite-name: external-driver
e2e-task: e2e:vm:external-driver
conformance-artifact-prefix: openshell-conformance
gpu-e2e:
needs: [pr_metadata, build-cli, build-gateway, build-supervisor-image]
needs: [pr_metadata, build-cli, build-conformance, build-gateway, build-supervisor-image]
if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_gpu_e2e == 'true'
permissions:
actions: read
@@ -326,9 +343,10 @@ jobs:
uses: ./.github/workflows/e2e-gpu-test.yaml
with:
image-tag: ${{ github.sha }}
conformance-artifact-prefix: openshell-conformance
kubernetes-e2e:
needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image]
needs: [pr_metadata, build-cli, build-conformance, 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
@@ -356,9 +374,10 @@ 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 }}
conformance-artifact-prefix: openshell-conformance
kubernetes-workspace-managed-e2e:
needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image]
needs: [pr_metadata, build-cli, build-conformance, 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
@@ -369,9 +388,10 @@ jobs:
image-tag: ${{ github.sha }}
job-name: Kubernetes E2E (workspace managed mode)
e2e-task: e2e:kubernetes:workspace-managed
conformance-artifact-prefix: openshell-conformance
kubernetes-external-driver-e2e:
needs: [pr_metadata, build-cli, build-gateway-plain, build-driver-kubernetes, build-supervisor-image]
needs: [pr_metadata, build-cli, build-conformance, 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
@@ -385,9 +405,10 @@ jobs:
gateway-artifact: openshell-gateway-plain-x86_64-unknown-linux-gnu
external-driver-binary: openshell-driver-kubernetes
cluster-images: supervisor
conformance-artifact-prefix: openshell-conformance
kubernetes-workspace-operator-e2e:
needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image]
needs: [pr_metadata, build-cli, build-conformance, 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
@@ -398,9 +419,10 @@ jobs:
image-tag: ${{ github.sha }}
job-name: Kubernetes E2E (workspace operator mode)
e2e-task: e2e:kubernetes:workspace-operator
conformance-artifact-prefix: openshell-conformance
kubernetes-ha-e2e:
needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image]
needs: [pr_metadata, build-cli, build-conformance, 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
@@ -412,9 +434,10 @@ 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
conformance-artifact-prefix: openshell-conformance
kubernetes-credential-drivers-e2e:
needs: [pr_metadata, build-cli, build-gateway-image, build-supervisor-image]
needs: [pr_metadata, build-cli, build-conformance, 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
@@ -425,6 +448,7 @@ jobs:
image-tag: ${{ github.sha }}
job-name: Kubernetes Credential Drivers E2E
e2e-task: e2e:kubernetes:credential-drivers
conformance-artifact-prefix: openshell-conformance
core-e2e-result:
name: Core E2E result
-9
View File
@@ -18,9 +18,6 @@ on:
runner:
required: true
type: string
dev-shell:
required: true
type: string
cargo-version:
required: true
type: string
@@ -36,10 +33,6 @@ on:
required: false
type: string
default: ""
interpreter:
required: false
type: string
default: ""
checkout-ref:
required: false
type: string
@@ -70,9 +63,7 @@ jobs:
package: ${{ inputs.package }}
binary: ${{ inputs.binary }}
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 }}
-4
View File
@@ -31,20 +31,16 @@ jobs:
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:
package: openshell-cli
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 }}
+1 -9
View File
@@ -31,25 +31,17 @@ jobs:
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:
package: openshell-server
package: openshell-gateway
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
+123
View File
@@ -0,0 +1,123 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
name: Build RPM
on:
workflow_call:
inputs:
checkout-ref:
required: true
type: string
arch:
required: true
type: string
runner:
required: true
type: string
cli-target:
required: true
type: string
gateway-target:
required: true
type: string
rpm-version:
required: false
type: string
default: ""
rpm-release:
required: false
type: string
default: ""
cargo-version:
required: false
type: string
default: ""
permissions:
contents: read
defaults:
run:
shell: bash
jobs:
build:
name: Build RPM Package (Linux ${{ inputs.arch }})
runs-on: ${{ inputs.runner }}
timeout-minutes: 60
container:
image: docker.io/library/fedora:44@sha256:43b29f65a41eb9c35e1cd5323e3bdf3b655c2357a9f4f1ff2f9c2798e5045d80
steps:
- name: Install packaging dependencies
run: |
dnf install -y \
packit rpm-build \
cargo cargo-rpm-macros git-core \
pandoc python3-devel systemd-rpm-macros
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.checkout-ref }}
fetch-depth: 0
- name: Cache Cargo dependencies
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
shared-key: rpm-vendor-${{ inputs.arch }}
cache-targets: "false"
cache-bin: "false"
cache-on-failure: "true"
- name: Download CLI artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: openshell-${{ inputs.cli-target }}
path: package-binaries/
- name: Download gateway artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: openshell-gateway-${{ inputs.gateway-target }}
path: package-binaries/
- name: Configure package inputs
run: |
set -euo pipefail
chmod +x package-binaries/openshell{,-gateway}
ls -lah package-binaries
- name: Mark workspace safe for git
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Fetch tags
run: git fetch --tags --force
- name: Build RPMs via Packit
env:
OPENSHELL_RPM_VERSION: ${{ inputs.rpm-version }}
OPENSHELL_RPM_RELEASE: ${{ inputs.rpm-release }}
OPENSHELL_CARGO_VERSION: ${{ inputs.cargo-version }}
OPENSHELL_PREBUILT_BINARIES_DIR: ${{ github.workspace }}/package-binaries
run: packit build locally
- name: Collect RPM artifacts
run: |
set -euo pipefail
mkdir -p artifacts
mapfile -t rpms < <(find "$GITHUB_WORKSPACE" -maxdepth 3 -type f -name '*.rpm' ! -name '*.src.rpm' | sort)
if [ "${#rpms[@]}" -eq 0 ]; then
echo "::error::No RPM artifacts found under $GITHUB_WORKSPACE"
find "$GITHUB_WORKSPACE" -maxdepth 3 -type f | sort
exit 1
fi
cp "${rpms[@]}" artifacts/
echo "=== Built RPMs ==="
ls -lah artifacts/
- name: Upload RPM artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: rpm-linux-${{ inputs.arch }}
path: artifacts/*.rpm
retention-days: 5
@@ -31,17 +31,14 @@ jobs:
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:
package: openshell-sandbox
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 }}
+1 -8
View File
@@ -33,22 +33,17 @@ jobs:
- 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}
shell: nix develop -c bash -euo pipefail {0}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
@@ -82,7 +77,5 @@ jobs:
package: openshell-driver-vm
binary: openshell-driver-vm
triple: ${{ matrix.triple }}
dev-shell: ${{ matrix.dev_shell }}
cargo-version: ${{ inputs['cargo-version'] }}
image-tag: ${{ inputs['image-tag'] }}
interpreter: ${{ matrix.interpreter }}
+286
View File
@@ -0,0 +1,286 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
name: Codex Security Release Qualification
on:
push:
tags:
- "v*.*.*-pre.*"
workflow_call:
inputs:
candidate_ref:
description: Pre-release tag to scan (vMAJOR.MINOR.PATCH-pre.N)
required: true
type: string
stable_ref:
description: Optional previous stable tag override
required: false
default: ""
type: string
allow_full_bootstrap:
description: Allow a full scan when no previous stable tag exists
required: false
default: false
type: boolean
outputs:
base_sha:
description: Previous stable commit, empty for a full bootstrap scan
value: ${{ jobs.analyze.outputs.base_sha }}
candidate_sha:
description: Qualified pre-release commit
value: ${{ jobs.analyze.outputs.candidate_sha }}
category:
description: Code Scanning category for the release train
value: ${{ jobs.analyze.outputs.category }}
train:
description: Stable version targeted by the pre-release train
value: ${{ jobs.analyze.outputs.train }}
secrets:
CODEX_SECURITY_API_KEY:
required: true
workflow_dispatch:
inputs:
candidate_ref:
description: Pre-release tag to scan (vMAJOR.MINOR.PATCH-pre.N)
required: true
type: string
stable_ref:
description: Optional previous stable tag override
required: false
type: string
upload_sarif:
description: Upload manual-run results to Code Scanning
required: false
default: false
type: boolean
allow_full_bootstrap:
description: Allow a full scan when no previous stable tag exists
required: false
default: false
type: boolean
permissions:
actions: read
contents: read
security-events: write
concurrency:
group: codex-security-release-qualification
cancel-in-progress: true
env:
CODEX_SECURITY_MAX_CONCURRENT_THREADS: "8"
CODEX_SECURITY_REASONING_EFFORT: medium
NVIDIA_INFERENCE_BASE_URL: https://inference-api.nvidia.com/v1
NVIDIA_INFERENCE_MODEL: openai/openai/gpt-5.6-sol
jobs:
analyze:
name: Codex Security (${{ inputs.candidate_ref || github.ref_name }})
# The agent executes no shell commands on the repository self-hosted runner,
# so its preflight never scopes the diff and it seals no draft.
runs-on: ubuntu-latest
timeout-minutes: 120
outputs:
base_sha: ${{ steps.range.outputs.base_sha }}
candidate_sha: ${{ steps.range.outputs.candidate_sha }}
category: ${{ steps.range.outputs.category }}
train: ${{ steps.range.outputs.train }}
steps:
# Install the trusted scanner before repository-controlled files exist in
# the workspace, and invoke it later through its absolute path.
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "26"
package-manager-cache: false
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
# Codex confines model-run commands with bubblewrap, which needs
# unprivileged user namespaces. Ubuntu 24.04 restricts those through
# AppArmor, so bubblewrap cannot set up the sandbox network namespace and
# the scan agent executes nothing at all.
- name: Allow unprivileged user namespaces
run: |
set -euo pipefail
if [ -e /proc/sys/kernel/apparmor_restrict_unprivileged_userns ]; then
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
fi
- name: Install Codex Security
run: |
set -euo pipefail
npm install \
--prefix "$RUNNER_TEMP/codex-security" \
--ignore-scripts \
--no-audit \
--no-fund \
@openai/codex-security@0.1.24
- name: Verify Codex Security
env:
CODEX_SECURITY_BIN: ${{ runner.temp }}/codex-security/node_modules/.bin/codex-security
run: |
set -euo pipefail
test -x "$CODEX_SECURITY_BIN"
"$CODEX_SECURITY_BIN" --version
# The range resolver has to come from the workflow's own revision. A
# scanned candidate predates it, and running the resolver from the
# revision under scan would let that revision pick its own scan range.
- name: Check out the workflow revision
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: workflow-revision
sparse-checkout: tasks/scripts
persist-credentials: false
- name: Stage the range resolver
run: |
set -euo pipefail
install -d -m 700 "$RUNNER_TEMP/range-resolver"
cp workflow-revision/tasks/scripts/release.py \
workflow-revision/tasks/scripts/codex_security_range.py \
"$RUNNER_TEMP/range-resolver/"
rm -rf workflow-revision
- name: Check out the pre-release
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.candidate_ref || github.ref }}
fetch-depth: 0
persist-credentials: false
- name: Resolve release range
id: range
env:
ALLOW_FULL_BOOTSTRAP: ${{ inputs.allow_full_bootstrap || false }}
CANDIDATE_REF: ${{ inputs.candidate_ref || github.ref_name }}
STABLE_REF: ${{ inputs.stable_ref || '' }}
run: |
set -euo pipefail
git show-ref --verify --quiet refs/remotes/origin/main
args=(
--candidate "$CANDIDATE_REF"
--main-ref origin/main
)
if [ -n "$STABLE_REF" ]; then
args+=(--stable "$STABLE_REF")
fi
if [ "$ALLOW_FULL_BOOTSTRAP" = "true" ]; then
args+=(--allow-full-bootstrap)
fi
python3 "$RUNNER_TEMP/range-resolver/codex_security_range.py" "${args[@]}"
- name: Scan changes since the previous stable
env:
BASE_SHA: ${{ steps.range.outputs.base_sha }}
CODEX_SECURITY_BIN: ${{ runner.temp }}/codex-security/node_modules/.bin/codex-security
CODEX_SECURITY_STATE_DIR: ${{ runner.temp }}/codex-security-state-${{ github.run_id }}-${{ github.run_attempt }}
HEAD_SHA: ${{ steps.range.outputs.candidate_sha }}
NVIDIA_INFERENCE_API_KEY: ${{ secrets.CODEX_SECURITY_API_KEY }}
OPENAI_API_KEY: ${{ secrets.CODEX_SECURITY_API_KEY }}
SCAN_DIR: ${{ runner.temp }}/codex-security-results-${{ github.run_id }}-${{ github.run_attempt }}
SCAN_SCOPE: ${{ steps.range.outputs.scan_scope }}
run: |
set -euo pipefail
install -d -m 700 "$CODEX_SECURITY_STATE_DIR" "$SCAN_DIR"
target_args=()
if [ "$SCAN_SCOPE" = "diff" ]; then
target_args=(--diff "$BASE_SHA" --head "$HEAD_SHA")
elif [ "$SCAN_SCOPE" != "full" ]; then
echo "::error::Unsupported Codex Security scan scope: $SCAN_SCOPE"
exit 2
fi
"$CODEX_SECURITY_BIN" scan . \
"${target_args[@]}" \
--auth api-key \
--model "$NVIDIA_INFERENCE_MODEL" \
--effort "$CODEX_SECURITY_REASONING_EFFORT" \
--codex 'model_provider="nvidia"' \
--codex 'model_providers.nvidia.name="NVIDIA Inference"' \
--codex "model_providers.nvidia.base_url=\"$NVIDIA_INFERENCE_BASE_URL\"" \
--codex 'model_providers.nvidia.env_key="NVIDIA_INFERENCE_API_KEY"' \
--codex 'model_providers.nvidia.wire_api="responses"' \
--codex 'model_providers.nvidia.supports_websockets=false' \
--codex "features.multi_agent_v2.max_concurrent_threads_per_session=$CODEX_SECURITY_MAX_CONCURRENT_THREADS" \
--codex 'approval_policy="never"' \
--output-dir "$SCAN_DIR" \
--headless > /dev/null
- name: Export SARIF
env:
CODEX_SECURITY_BIN: ${{ runner.temp }}/codex-security/node_modules/.bin/codex-security
SARIF_FILE: ${{ runner.temp }}/codex-security.sarif
SCAN_DIR: ${{ runner.temp }}/codex-security-results-${{ github.run_id }}-${{ github.run_attempt }}
run: |
set -euo pipefail
"$CODEX_SECURITY_BIN" export "$SCAN_DIR" \
--export-format sarif \
--source-root "$GITHUB_WORKSPACE" \
--output "$SARIF_FILE"
- name: Summarize findings
env:
BASE_TAG: ${{ steps.range.outputs.base_tag }}
CANDIDATE_TAG: ${{ steps.range.outputs.candidate_tag }}
COMMIT_COUNT: ${{ steps.range.outputs.commit_count }}
SARIF_FILE: ${{ runner.temp }}/codex-security.sarif
SCAN_SCOPE: ${{ steps.range.outputs.scan_scope }}
TRAIN: ${{ steps.range.outputs.train }}
run: |
set -euo pipefail
finding_count=$(jq '[.runs[]?.results[]?] | length' "$SARIF_FILE")
{
echo "### Codex Security release qualification"
echo
echo "- Train: \`$TRAIN\`"
echo "- Candidate: \`$CANDIDATE_TAG\`"
echo "- Maximum concurrent agent threads: $CODEX_SECURITY_MAX_CONCURRENT_THREADS"
if [ "$SCAN_SCOPE" = "diff" ]; then
echo "- Previous stable: \`$BASE_TAG\`"
echo "- Commits in cumulative diff: $COMMIT_COUNT"
else
echo "- Scope: approved full bootstrap scan"
fi
echo "- Coverage: complete"
echo "- Findings: $finding_count"
echo
echo "Findings are informational during the observation phase."
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload SARIF to Code Scanning
if: ${{ github.event_name != 'workflow_dispatch' || inputs.upload_sarif }}
uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
with:
sarif_file: ${{ runner.temp }}/codex-security.sarif
ref: refs/heads/main
sha: ${{ steps.range.outputs.candidate_sha }}
category: ${{ steps.range.outputs.category }}
result:
name: OpenShell / Codex Security (informational)
if: ${{ always() }}
needs: analyze
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Evaluate scanner execution
env:
ANALYZE_RESULT: ${{ needs.analyze.result }}
run: |
set -euo pipefail
if [ "$ANALYZE_RESULT" != "success" ]; then
echo "::error::Codex Security release qualification did not complete successfully."
exit 1
fi
echo "Codex Security completed; findings remain informational."
+179
View File
@@ -0,0 +1,179 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
name: Conformance
on:
workflow_dispatch: {}
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
pr_metadata:
name: Resolve PR metadata
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
should_run: ${{ steps.gate.outputs.should_run }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- id: gate
uses: ./.github/actions/pr-gate
version:
needs: pr_metadata
if: needs.pr_metadata.outputs.should_run == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
outputs:
cargo: ${{ steps.version.outputs.cargo }}
rpm_version: ${{ steps.version.outputs.rpm_version }}
rpm_release: ${{ steps.version.outputs.rpm_release }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Compute versions
id: version
run: |
cargo="$(python3 tasks/scripts/release.py get-version --cargo)"
rpm_version="$(python3 tasks/scripts/release.py get-version --rpm-version)"
rpm_release="$(python3 tasks/scripts/release.py get-version --rpm-release)"
{
echo "cargo=$cargo"
echo "rpm_version=$rpm_version"
echo "rpm_release=$rpm_release"
} >> "$GITHUB_OUTPUT"
build-cli:
needs: version
permissions:
contents: read
uses: ./.github/workflows/build-binaries.yml
with:
package: openshell-cli
binary: openshell
triple: x86_64-unknown-linux-musl
runner: linux-amd64-cpu8
cargo-version: ${{ needs.version.outputs.cargo }}
checkout-ref: ${{ github.sha }}
secrets: inherit
build-conformance:
needs: version
permissions:
contents: read
uses: ./.github/workflows/build-binaries.yml
with:
package: openshell-conformance-cli
binary: openshell-conformance
triple: x86_64-unknown-linux-musl
runner: linux-amd64-cpu8
cargo-version: ${{ needs.version.outputs.cargo }}
checkout-ref: ${{ github.sha }}
secrets: inherit
build-gateway:
needs: version
permissions:
contents: read
uses: ./.github/workflows/build-binaries.yml
with:
package: openshell-gateway
binary: openshell-gateway
triple: x86_64-unknown-linux-gnu
runner: linux-amd64-cpu8
cargo-version: ${{ needs.version.outputs.cargo }}
image-tag: dev
checkout-ref: ${{ github.sha }}
secrets: inherit
build-rpm:
needs: [version, build-cli, build-gateway]
permissions:
contents: read
uses: ./.github/workflows/build-rpm.yml
with:
checkout-ref: ${{ github.sha }}
arch: x86_64
runner: linux-amd64-cpu8
cli-target: x86_64-unknown-linux-musl
gateway-target: x86_64-unknown-linux-gnu
cargo-version: ${{ needs.version.outputs.cargo }}
rpm-version: ${{ needs.version.outputs.rpm_version }}
rpm-release: ${{ needs.version.outputs.rpm_release }}
fedora:
name: Fedora with Rootless Podman
needs: [build-conformance, build-rpm]
runs-on: ubuntu-24.04
timeout-minutes: 45
permissions:
actions: read
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Enable KVM access
run: |
set -euo pipefail
if [[ ! -c /dev/kvm ]]; then
echo "::error::The runner did not expose /dev/kvm"
exit 1
fi
sudo chmod 0666 /dev/kvm
exec 3<>/dev/kvm
exec 3>&-
- uses: ./.github/actions/setup-nix
- name: Download RPM artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: rpm-linux-x86_64
path: rpm-input
- name: Download conformance CLI
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: openshell-conformance-x86_64-unknown-linux-musl
path: conformance-input
- name: Run RPM gateway continuity conformance
shell: bash
run: |
set -euo pipefail
chmod +x conformance-input/openshell-conformance
shopt -s nullglob
candidate_cli_package=(rpm-input/openshell-[0-9]*.rpm)
candidate_gateway_package=(rpm-input/openshell-gateway-[0-9]*.rpm)
if [[ ${#candidate_cli_package[@]} -ne 1 || ${#candidate_gateway_package[@]} -ne 1 ]]; then
echo "expected one candidate CLI and gateway RPM" >&2
printf 'RPM artifacts:\n' >&2
printf ' %s\n' rpm-input/* >&2
exit 1
fi
OPENSHELL_TEST_GUEST_CACHE_DISABLE=1 nix run .#test-guest -- \
--distro fedora \
--with podman-rootless \
--with selinux \
--copy "${candidate_cli_package[0]}:/var/lib/openshell-conformance/candidate/openshell.rpm" \
--copy "${candidate_gateway_package[0]}:/var/lib/openshell-conformance/candidate/openshell-gateway.rpm" \
--copy conformance-input/openshell-conformance:/tmp/openshell-conformance \
--copy nix/test-guest/conformance-plans/gateway-upgrade-restart.toml:/tmp/conformance-plan.toml \
--provision openshell-rpm-latest-release \
--provision gateway-podman \
--provision openshell-rpm-gateway-upgrade \
-- /tmp/openshell-conformance run --plan /tmp/conformance-plan.toml
+6
View File
@@ -25,6 +25,10 @@ on:
required: false
type: string
default: ""
conformance-artifact-prefix:
required: false
type: string
default: ""
suite-matrix:
required: false
type: string
@@ -66,6 +70,8 @@ jobs:
persist-credentials: false
- uses: ./.github/actions/setup-e2e-cli
with:
conformance-artifact-prefix: ${{ inputs.conformance-artifact-prefix }}
- uses: ./.github/actions/setup-e2e-gateway
with:
+7
View File
@@ -7,6 +7,11 @@ on:
description: "Image tag to test (typically the commit SHA)"
required: true
type: string
conformance-artifact-prefix:
description: "Optional prebuilt conformance CLI artifact prefix (artifact suffix is <triple>)"
required: false
type: string
default: ""
permissions:
actions: read
@@ -61,6 +66,8 @@ jobs:
- name: Use prebuilt OpenShell CLI
uses: ./.github/actions/setup-e2e-cli
with:
conformance-artifact-prefix: ${{ inputs.conformance-artifact-prefix }}
- name: Use prebuilt OpenShell gateway
uses: ./.github/actions/setup-e2e-gateway
+19
View File
@@ -42,6 +42,16 @@ on:
required: false
type: string
default: "e2e:kubernetes"
test-name:
description: "Rust e2e test target to run (sets OPENSHELL_E2E_KUBE_TEST)"
required: false
type: string
default: ""
kubernetes-features:
description: "Cargo feature list for the Kubernetes e2e crate (uses script defaults when empty)"
required: false
type: string
default: ""
mise-version:
description: "mise version to install on the bare Kubernetes e2e runner"
required: false
@@ -52,6 +62,11 @@ on:
required: false
type: string
default: ""
conformance-artifact-prefix:
description: "Optional prebuilt conformance CLI artifact prefix (artifact suffix is <triple>)"
required: false
type: string
default: ""
external-driver-binary:
description: "Optional standalone compute driver binary used to compose a local test image"
required: false
@@ -92,6 +107,8 @@ jobs:
- name: Use prebuilt OpenShell CLI
uses: ./.github/actions/setup-e2e-cli
with:
conformance-artifact-prefix: ${{ inputs.conformance-artifact-prefix }}
- name: Use prebuilt OpenShell gateway
if: inputs.gateway-artifact != ''
@@ -121,6 +138,8 @@ jobs:
OPENSHELL_E2E_KUBE_CONTEXT: kind-${{ env.KIND_CLUSTER_NAME }}
OPENSHELL_E2E_KUBE_EXTRA_VALUES: ${{ inputs.extra-helm-values }}
OPENSHELL_E2E_KUBE_EXTERNAL_POSTGRES_SECRET: ${{ inputs.external-postgres-secret }}
OPENSHELL_E2E_KUBE_TEST: ${{ inputs.test-name }}
OPENSHELL_E2E_KUBERNETES_FEATURES: ${{ inputs.kubernetes-features }}
IMAGE_TAG: ${{ inputs.image-tag }}
OPENSHELL_REGISTRY: ghcr.io/nvidia/openshell
E2E_TASK: ${{ inputs.e2e-task }}
+6
View File
@@ -21,6 +21,10 @@ on:
required: false
type: string
default: ""
conformance-artifact-prefix:
required: false
type: string
default: ""
suite-matrix:
required: false
type: string
@@ -53,6 +57,8 @@ jobs:
persist-credentials: false
- uses: ./.github/actions/setup-e2e-cli
with:
conformance-artifact-prefix: ${{ inputs.conformance-artifact-prefix }}
- uses: ./.github/actions/setup-e2e-gateway
with:
+6
View File
@@ -22,6 +22,10 @@ on:
required: false
type: string
default: e2e:vm
conformance-artifact-prefix:
required: false
type: string
default: ""
permissions:
actions: read
@@ -43,6 +47,8 @@ jobs:
persist-credentials: false
- uses: ./.github/actions/setup-e2e-cli
with:
conformance-artifact-prefix: ${{ inputs.conformance-artifact-prefix }}
- uses: ./.github/actions/setup-e2e-gateway
with:
@@ -25,6 +25,7 @@ permissions:
concurrency:
group: docs-website
cancel-in-progress: false
queue: max
defaults:
run:
+28 -3
View File
@@ -2,6 +2,11 @@ name: Release Canary
on:
workflow_dispatch:
inputs:
release-dev-run-id:
description: "Successful Release Dev run ID whose Snap artifact to test"
required: false
type: string
workflow_run:
workflows: ["Release Dev"]
types: [completed]
@@ -154,7 +159,7 @@ jobs:
ubuntu-snap:
name: Ubuntu Snap
if: ${{ github.event.workflow_run.conclusion == 'success' }}
if: ${{ github.event.workflow_run.conclusion == 'success' || inputs.release-dev-run-id != '' }}
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
@@ -176,7 +181,7 @@ jobs:
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
github-token: ${{ github.token }}
run-id: ${{ github.event.workflow_run.id }}
run-id: ${{ inputs.release-dev-run-id || github.event.workflow_run.id }}
pattern: snap-linux-amd64
path: release/
merge-multiple: true
@@ -202,7 +207,27 @@ jobs:
sudo snap services openshell
openshell gateway add http://127.0.0.1:17670 --local --name snap-docker
openshell gateway select snap-docker
openshell status
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 )'
kubernetes:
name: Kubernetes Helm (kind)
+54 -8
View File
@@ -28,6 +28,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
outputs:
python_version: ${{ steps.v.outputs.python }}
docs_version: ${{ steps.v.outputs.docs_version }}
cargo_version: ${{ steps.v.outputs.cargo }}
deb_version: ${{ steps.v.outputs.deb }}
rpm_version: ${{ steps.v.outputs.rpm_version }}
@@ -47,11 +48,15 @@ jobs:
id: v
run: |
set -euo pipefail
echo "python=$(uv run python tasks/scripts/release.py get-version --python)" >> "$GITHUB_OUTPUT"
echo "cargo=$(uv run python tasks/scripts/release.py get-version --cargo)" >> "$GITHUB_OUTPUT"
echo "deb=$(uv run python tasks/scripts/release.py get-version --deb)" >> "$GITHUB_OUTPUT"
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"
python_version=$(uv run python tasks/scripts/release.py get-version --dev --python)
{
echo "python=${python_version}"
echo "docs_version=${python_version%%+*}"
echo "cargo=$(uv run python tasks/scripts/release.py get-version --dev --cargo)"
echo "deb=$(uv run python tasks/scripts/release.py get-version --dev --deb)"
echo "rpm_version=$(uv run python tasks/scripts/release.py get-version --dev --rpm-version)"
echo "rpm_release=$(uv run python tasks/scripts/release.py get-version --dev --rpm-release)"
} >> "$GITHUB_OUTPUT"
build-cli:
needs: compute-versions
@@ -63,6 +68,27 @@ jobs:
image-tag: dev
secrets: inherit
build-conformance:
needs: compute-versions
permissions:
contents: read
strategy:
matrix:
include:
- triple: x86_64-unknown-linux-musl
runner: linux-amd64-cpu8
- triple: aarch64-unknown-linux-musl
runner: linux-arm64-cpu8
uses: ./.github/workflows/build-binaries.yml
with:
package: openshell-conformance-cli
binary: openshell-conformance
artifact-name: openshell-conformance-${{ matrix.triple }}
triple: ${{ matrix.triple }}
runner: ${{ matrix.runner }}
cargo-version: ${{ needs.compute-versions.outputs.cargo_version }}
secrets: inherit
build-gateway:
needs: compute-versions
permissions:
@@ -125,7 +151,7 @@ jobs:
secrets: inherit
docker-e2e:
needs: [build-cli, build-gateway, build-supervisor-image]
needs: [build-cli, build-conformance, build-gateway, build-supervisor-image]
permissions:
actions: read
contents: read
@@ -134,9 +160,10 @@ jobs:
with:
image-tag: ${{ github.sha }}
runner: linux-arm64-cpu8
conformance-artifact-prefix: openshell-conformance
podman-e2e:
needs: [build-cli, build-gateway, build-supervisor-image]
needs: [build-cli, build-conformance, build-gateway, build-supervisor-image]
permissions:
actions: read
contents: read
@@ -144,14 +171,17 @@ jobs:
uses: ./.github/workflows/e2e-podman-test.yml
with:
image-tag: ${{ github.sha }}
conformance-artifact-prefix: openshell-conformance
vm-e2e:
needs: [build-cli, build-gateway, build-vm-driver]
needs: [build-cli, build-conformance, build-gateway, build-vm-driver]
permissions:
actions: read
contents: read
packages: read
uses: ./.github/workflows/e2e-vm-test.yml
with:
conformance-artifact-prefix: openshell-conformance
tag-ghcr-dev:
name: Tag GHCR Images as Dev
@@ -600,6 +630,22 @@ jobs:
release-kind: dev
pin-sha: ${{ github.sha }}
publish-fern-docs:
name: Sync and Publish Fern Docs
needs: [compute-versions, release-dev, release-helm, trigger-wheel-publish]
permissions:
contents: write
uses: ./.github/workflows/sync-docs.yml
with:
operation: sync
channel: dev
source_ref: ${{ github.sha }}
release_version: ${{ needs.compute-versions.outputs.docs_version }}
display_name: Dev
availability: beta
publish: true
secrets: inherit
trigger-wheel-publish:
name: Trigger Wheel Publish
needs: [compute-versions, release-dev]
+91 -44
View File
@@ -48,6 +48,7 @@ jobs:
semver: ${{ steps.v.outputs.semver }}
# Commit resolved from RELEASE_TAG, used for image tags and downstream metadata
source_sha: ${{ steps.v.outputs.source_sha }}
is_prerelease: ${{ steps.v.outputs.is_prerelease }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
@@ -64,14 +65,25 @@ jobs:
id: v
run: |
set -euo pipefail
echo "python=$(uv run python tasks/scripts/release.py get-version --python)" >> "$GITHUB_OUTPUT"
echo "cargo=$(uv run python tasks/scripts/release.py get-version --cargo)" >> "$GITHUB_OUTPUT"
echo "npm=$(uv run python tasks/scripts/release.py get-version --npm)" >> "$GITHUB_OUTPUT"
echo "deb=$(uv run python tasks/scripts/release.py get-version --deb)" >> "$GITHUB_OUTPUT"
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"
echo "semver=${RELEASE_TAG#v}" >> "$GITHUB_OUTPUT"
echo "source_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
if [[ "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-pre\.[1-9][0-9]*$ ]]; then
is_prerelease=true
elif [[ "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
is_prerelease=false
else
echo "Unsupported release tag: ${RELEASE_TAG}" >&2
exit 1
fi
{
echo "is_prerelease=${is_prerelease}"
echo "python=$(uv run python tasks/scripts/release.py get-version --python)"
echo "cargo=$(uv run python tasks/scripts/release.py get-version --cargo)"
echo "npm=$(uv run python tasks/scripts/release.py get-version --npm)"
echo "deb=$(uv run python tasks/scripts/release.py get-version --deb)"
echo "rpm_version=$(uv run python tasks/scripts/release.py get-version --rpm-version)"
echo "rpm_release=$(uv run python tasks/scripts/release.py get-version --rpm-release)"
echo "semver=${RELEASE_TAG#v}"
echo "source_sha=$(git rev-parse HEAD)"
} >> "$GITHUB_OUTPUT"
build-cli:
needs: compute-versions
@@ -84,6 +96,28 @@ jobs:
checkout-ref: ${{ inputs.tag || github.ref }}
secrets: inherit
build-conformance:
needs: compute-versions
permissions:
contents: read
strategy:
matrix:
include:
- triple: x86_64-unknown-linux-musl
runner: linux-amd64-cpu8
- triple: aarch64-unknown-linux-musl
runner: linux-arm64-cpu8
uses: ./.github/workflows/build-binaries.yml
with:
package: openshell-conformance-cli
binary: openshell-conformance
artifact-name: openshell-conformance-${{ matrix.triple }}
triple: ${{ matrix.triple }}
runner: ${{ matrix.runner }}
cargo-version: ${{ needs.compute-versions.outputs.cargo_version }}
checkout-ref: ${{ inputs.tag || github.ref }}
secrets: inherit
build-gateway:
needs: compute-versions
permissions:
@@ -153,7 +187,7 @@ jobs:
secrets: inherit
docker-e2e:
needs: [compute-versions, build-cli, build-gateway, build-supervisor-image]
needs: [compute-versions, build-cli, build-conformance, build-gateway, build-supervisor-image]
permissions:
actions: read
contents: read
@@ -163,9 +197,10 @@ jobs:
image-tag: ${{ needs.compute-versions.outputs.source_sha }}
checkout-ref: ${{ inputs.tag || github.ref }}
runner: linux-arm64-cpu8
conformance-artifact-prefix: openshell-conformance
podman-e2e:
needs: [compute-versions, build-cli, build-gateway, build-supervisor-image]
needs: [compute-versions, build-cli, build-conformance, build-gateway, build-supervisor-image]
permissions:
actions: read
contents: read
@@ -174,9 +209,10 @@ jobs:
with:
image-tag: ${{ needs.compute-versions.outputs.source_sha }}
checkout-ref: ${{ inputs.tag || github.ref }}
conformance-artifact-prefix: openshell-conformance
vm-e2e:
needs: [compute-versions, build-cli, build-gateway, build-vm-driver]
needs: [compute-versions, build-cli, build-conformance, build-gateway, build-vm-driver]
permissions:
actions: read
contents: read
@@ -184,9 +220,10 @@ jobs:
uses: ./.github/workflows/e2e-vm-test.yml
with:
checkout-ref: ${{ inputs.tag || github.ref }}
conformance-artifact-prefix: openshell-conformance
tag-ghcr-release:
name: Tag GHCR Images for Release
name: Tag GHCR Images
needs: [compute-versions, build-gateway-image, build-supervisor-image, release]
runs-on: linux-amd64-cpu8
timeout-minutes: 10
@@ -194,22 +231,27 @@ jobs:
- name: Log in to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${GITHUB_ACTOR}" --password-stdin
- name: Tag images with version and latest
- name: Tag images with release version
env:
IS_PRERELEASE: ${{ needs.compute-versions.outputs.is_prerelease }}
run: |
set -euo pipefail
REGISTRY="ghcr.io/nvidia/openshell"
VERSION="${{ needs.compute-versions.outputs.semver }}"
SOURCE_TAG="${{ needs.compute-versions.outputs.source_sha }}"
for component in gateway supervisor; do
echo "Tagging ${REGISTRY}/${component}:${SOURCE_TAG} as ${VERSION} and latest..."
echo "Tagging ${REGISTRY}/${component}:${SOURCE_TAG} as ${VERSION}..."
docker buildx imagetools create \
--prefer-index=false \
-t "${REGISTRY}/${component}:${VERSION}" \
"${REGISTRY}/${component}:${SOURCE_TAG}"
docker buildx imagetools create \
--prefer-index=false \
-t "${REGISTRY}/${component}:latest" \
"${REGISTRY}/${component}:${SOURCE_TAG}"
if [[ "${IS_PRERELEASE}" != "true" ]]; then
echo "Tagging ${REGISTRY}/${component}:${SOURCE_TAG} as latest..."
docker buildx imagetools create \
--prefer-index=false \
-t "${REGISTRY}/${component}:latest" \
"${REGISTRY}/${component}:${SOURCE_TAG}"
fi
done
build-python-wheel:
@@ -265,8 +307,9 @@ jobs:
uses: ./.github/workflows/snap-package.yml
with:
checkout-ref: ${{ inputs.tag || github.ref }}
upload-channel: latest/stable
github-environment: latest/stable
upload-channel: ${{ needs.compute-versions.outputs.is_prerelease == 'true' && 'latest/edge' || 'latest/stable' }}
github-environment: ${{ needs.compute-versions.outputs.is_prerelease == 'true' && 'latest/edge' || 'latest/stable' }}
publish: ${{ needs.compute-versions.outputs.is_prerelease != 'true' }}
secrets:
publish-credentials: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
@@ -529,6 +572,7 @@ jobs:
cat openshell-sandbox-checksums-sha256.txt
- name: Generate Homebrew formula
if: needs.compute-versions.outputs.is_prerelease != 'true'
run: |
set -euo pipefail
python3 tasks/scripts/release.py generate-homebrew-formula \
@@ -547,6 +591,7 @@ jobs:
release/*.whl
- name: Prune removed VM checksum asset
if: needs.compute-versions.outputs.is_prerelease != 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
@@ -569,6 +614,7 @@ jobs:
}
- name: Create GitHub Release
if: needs.compute-versions.outputs.is_prerelease != 'true'
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
name: OpenShell ${{ env.RELEASE_TAG }}
@@ -605,35 +651,35 @@ jobs:
release/openshell-gateway-checksums-sha256.txt
release/openshell-sandbox-checksums-sha256.txt
- name: Upload pre-release artifacts
if: needs.compute-versions.outputs.is_prerelease == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: openshell-${{ env.RELEASE_TAG }}
path: release/
retention-days: 90
if-no-files-found: error
publish-fern-docs:
name: Publish Fern Docs
needs: [release]
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.tag || github.ref }}
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "24"
- name: Install Fern CLI
run: |
FERN_VERSION=$(node -p "require('./fern/fern.config.json').version")
npm install -g "fern-api@${FERN_VERSION}"
- name: Publish Fern docs
env:
FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
working-directory: ./fern
run: fern generate --docs
name: Sync and Publish Fern Docs
needs: [compute-versions, release, publish-sdk-typescript, release-helm, trigger-wheel-publish]
if: needs.compute-versions.outputs.is_prerelease != 'true'
permissions:
contents: write
uses: ./.github/workflows/sync-docs.yml
with:
operation: sync
channel: latest
source_ref: ${{ needs.compute-versions.outputs.source_sha }}
release_version: ${{ needs.compute-versions.outputs.semver }}
display_name: Latest (v${{ needs.compute-versions.outputs.semver }})
publish: true
secrets: inherit
publish-sdk-typescript:
name: Publish TypeScript SDK
needs: [compute-versions, release]
if: needs.compute-versions.outputs.is_prerelease != 'true'
runs-on: linux-amd64-cpu8
timeout-minutes: 15
permissions:
@@ -690,6 +736,7 @@ jobs:
trigger-wheel-publish:
name: Trigger Wheel Publish
needs: [compute-versions, release]
if: needs.compute-versions.outputs.is_prerelease != 'true'
runs-on: [self-hosted, nv]
timeout-minutes: 10
steps:
+12 -75
View File
@@ -37,83 +37,20 @@ jobs:
matrix:
include:
- arch: x86_64
artifact_arch: amd64
runner: linux-amd64-cpu8
cli_target: x86_64-unknown-linux-musl
gnu_target: x86_64-unknown-linux-gnu
gateway_target: x86_64-unknown-linux-gnu
- arch: aarch64
artifact_arch: arm64
runner: linux-arm64-cpu8
cli_target: aarch64-unknown-linux-musl
gnu_target: aarch64-unknown-linux-gnu
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
container:
image: fedora:latest
steps:
- name: Install build dependencies
run: |
dnf install -y \
packit rpm-build \
rust cargo gcc gcc-c++ make cmake pkg-config \
clang-devel z3-devel systemd-rpm-macros \
pandoc python3-devel git-core \
cargo-rpm-macros
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ inputs.checkout-ref }}
fetch-depth: 0
- name: Download CLI artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: openshell-${{ matrix.cli_target }}
path: package-binaries/
- name: Download gateway artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: openshell-gateway-${{ matrix.gnu_target }}
path: package-binaries/
- name: Configure package inputs
run: |
set -euo pipefail
chmod +x package-binaries/openshell{,-gateway}
ls -lah package-binaries
- name: Mark workspace safe for git
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Fetch tags
run: git fetch --tags --force
- name: Build RPMs via Packit
env:
OPENSHELL_RPM_VERSION: ${{ inputs['rpm-version'] }}
OPENSHELL_RPM_RELEASE: ${{ inputs['rpm-release'] }}
OPENSHELL_CARGO_VERSION: ${{ inputs['cargo-version'] }}
OPENSHELL_PREBUILT_BINARIES_DIR: ${{ github.workspace }}/package-binaries
run: packit build locally
- name: Collect RPM artifacts
run: |
set -euo pipefail
mkdir -p artifacts
mapfile -t rpms < <(find "$GITHUB_WORKSPACE" -maxdepth 3 -type f -name '*.rpm' ! -name '*.src.rpm' | sort)
if [ "${#rpms[@]}" -eq 0 ]; then
echo "::error::No RPM artifacts found under $GITHUB_WORKSPACE"
find "$GITHUB_WORKSPACE" -maxdepth 3 -type f | sort
exit 1
fi
cp "${rpms[@]}" artifacts/
echo "=== Built RPMs ==="
ls -lah artifacts/
- name: Upload RPM artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: rpm-linux-${{ matrix.arch }}
path: artifacts/*.rpm
retention-days: 5
gateway_target: aarch64-unknown-linux-gnu
uses: ./.github/workflows/build-rpm.yml
with:
checkout-ref: ${{ inputs.checkout-ref }}
arch: ${{ matrix.arch }}
runner: ${{ matrix.runner }}
cli-target: ${{ matrix.cli_target }}
gateway-target: ${{ matrix.gateway_target }}
rpm-version: ${{ inputs.rpm-version }}
rpm-release: ${{ inputs.rpm-release }}
cargo-version: ${{ inputs.cargo-version }}
-72
View File
@@ -1,72 +0,0 @@
name: Rust Cache Seed
on:
push:
branches:
- main
workflow_dispatch:
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: "0"
MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SCCACHE_GHA_ENABLED: "true"
permissions:
contents: read
packages: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
rust:
name: Rust (${{ matrix.runner }})
strategy:
fail-fast: false
matrix:
runner: [linux-amd64-cpu8, linux-arm64-cpu8]
runs-on: ${{ matrix.runner }}
env:
SCCACHE_GHA_VERSION: branch-checks-rust-${{ matrix.runner }}
container:
image: ghcr.io/nvidia/openshell/ci:latest
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install tools
run: mise install --locked
- name: Configure GHA sccache backend
uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10
- name: Cache Rust target and registry
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
with:
shared-key: rust-checks-${{ matrix.runner }}
cache-on-failure: true
- name: Format
run: mise run rust:format:check
- name: Lint
run: mise run rust:lint
- name: Test
run: mise run test:rust
- 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
+6
View File
@@ -17,6 +17,11 @@ on:
required: true
type: string
description: "GitHub deployment environment for approval gates (e.g., latest/edge, latest/stable)"
publish:
required: false
type: boolean
default: true
description: "Whether to upload the built snap to the Snap Store"
secrets:
publish-credentials:
@@ -161,6 +166,7 @@ jobs:
retention-days: 5
- name: Upload snap to Snap Store
if: inputs.publish
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.publish-credentials }}
INPUTS_UPLOAD_CHANNEL: ${{ inputs.upload-channel }}
+95 -4
View File
@@ -4,6 +4,49 @@
name: Sync Docs Website
on:
workflow_call:
inputs:
operation:
description: "Whether to sync or remove a docs snapshot"
required: true
type: string
channel:
description: "Docs channel to update or remove"
required: true
type: string
source_ref:
description: "Source commit SHA, branch, or tag to snapshot when operation=sync"
required: false
type: string
release_version:
description: "Release version used to order mutable channels"
required: false
type: string
version_slug:
description: "Version slug when channel=version"
required: false
type: string
display_name:
description: "Optional version selector display name"
required: false
type: string
availability:
description: "Optional Fern availability status"
required: false
type: string
publish:
description: "Publish production docs after syncing"
required: false
default: false
type: boolean
allow_rollback:
description: "Allow an explicitly requested mutable-channel rollback"
required: false
default: false
type: boolean
secrets:
FERN_TOKEN:
required: false
workflow_dispatch:
inputs:
operation:
@@ -21,19 +64,38 @@ on:
options:
- dev
- latest
- stable
- version
source_ref:
description: "Source commit SHA, branch, or tag to snapshot when operation=sync"
required: false
type: string
release_version:
description: "Release version, e.g. 0.1.2 or 0.1.3.dev4"
required: false
type: string
version_slug:
description: "Version slug when channel=version, e.g. v0.0.36"
required: false
type: string
display_name:
description: "Optional version selector display name"
description: "Optional selector name, e.g. Dev"
required: false
type: string
availability:
description: "Optional Fern status: beta, deprecated, ga, or stable"
required: false
type: string
publish:
description: "Publish production docs after syncing"
required: false
default: false
type: boolean
allow_rollback:
description: "Allow an explicitly requested mutable-channel rollback"
required: false
default: false
type: boolean
permissions:
contents: write
@@ -41,6 +103,7 @@ permissions:
concurrency:
group: docs-website
cancel-in-progress: false
queue: max
defaults:
run:
@@ -65,6 +128,7 @@ jobs:
OPERATION: ${{ inputs.operation }}
CHANNEL: ${{ inputs.channel }}
SOURCE_REF: ${{ inputs.source_ref }}
RELEASE_VERSION: ${{ inputs.release_version }}
VERSION_SLUG: ${{ inputs.version_slug }}
run: |
set -euo pipefail
@@ -72,8 +136,12 @@ jobs:
echo "source_ref is required when operation=sync" >&2
exit 1
fi
if [[ "$CHANNEL" == "version" && -z "$VERSION_SLUG" ]]; then
echo "version_slug is required when channel=version" >&2
if [[ "$CHANNEL" =~ ^(dev|latest|stable)$ && -z "$RELEASE_VERSION" ]]; then
echo "release_version is required for dev, latest, and stable channels" >&2
exit 1
fi
if [[ "$CHANNEL" =~ ^(stable|version)$ && -z "$VERSION_SLUG" ]]; then
echo "version_slug is required for stable and version channels" >&2
exit 1
fi
@@ -96,6 +164,7 @@ jobs:
uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0
with:
version: "0.10.12"
python-version: "3.13"
- name: Update docs snapshot
# Inputs flow in as quoted env vars to avoid shell injection; see the
@@ -104,17 +173,32 @@ jobs:
OPERATION: ${{ inputs.operation }}
CHANNEL: ${{ inputs.channel }}
SOURCE_REF: ${{ inputs.source_ref }}
RELEASE_VERSION: ${{ inputs.release_version }}
VERSION_SLUG: ${{ inputs.version_slug }}
DISPLAY_NAME: ${{ inputs.display_name }}
AVAILABILITY: ${{ inputs.availability }}
ALLOW_ROLLBACK: ${{ inputs.allow_rollback }}
run: |
SOURCE_SHA=""
if [[ "$OPERATION" == "sync" ]]; then
SOURCE_SHA=$(git -C source rev-parse HEAD)
fi
rollback_args=()
if [[ "$ALLOW_ROLLBACK" == "true" ]]; then
rollback_args+=(--allow-rollback)
fi
uv run automation/tasks/scripts/sync_docs_website.py \
--operation "$OPERATION" \
--source-root source \
--docs-website-root docs-website \
--channel "$CHANNEL" \
--source-ref "$SOURCE_REF" \
--source-sha "$SOURCE_SHA" \
--release-version "$RELEASE_VERSION" \
--version-slug "$VERSION_SLUG" \
--display-name "$DISPLAY_NAME"
--display-name "$DISPLAY_NAME" \
--availability "$AVAILABILITY" \
"${rollback_args[@]}"
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
@@ -160,3 +244,10 @@ jobs:
git commit -m "docs(website): remove ${target} docs"
fi
git push origin HEAD:docs-website
- name: Publish Fern docs
if: ${{ inputs.publish }}
env:
FERN_TOKEN: ${{ secrets.FERN_TOKEN }}
working-directory: docs-website/fern
run: fern generate --docs
+171
View File
@@ -0,0 +1,171 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
name: Trivy Changes
on:
pull_request:
merge_group:
types: [checks_requested]
workflow_dispatch:
inputs:
base_sha:
description: Base commit SHA to compare
required: true
type: string
head_sha:
description: Candidate commit SHA to compare
required: true
type: string
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
changes:
name: Detect deployment configuration changes
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
should_run: ${{ steps.default.outputs.should_run || steps.changed.outputs.any_modified }}
steps:
- id: default
if: github.event_name != 'pull_request'
run: echo "should_run=true" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
if: github.event_name == 'pull_request'
with:
persist-credentials: false
- id: changed
if: github.event_name == 'pull_request'
uses: tj-actions/changed-files@aa08304bd477b800d468db44fe10f6c61f7f7b11 # v42.1.0
with:
# `any_modified` covers deletions, which `any_changed` omits, and a
# failed diff has to fail the job: both otherwise report no relevant
# change, and removing the scanner or a value fixture would skip the
# scan behind a green status.
fail_on_initial_diff_error: true
files: |
deploy/docker/**
deploy/helm/**
deploy/kube/**
.trivyignore.yaml
flake.nix
flake.lock
tasks/scripts/trivy-scan.sh
tasks/scripts/trivy-scan-test.sh
tasks/scripts/trivy-config-report.jq
.github/workflows/trivy-scan.yml
.github/workflows/trivy-changes.yml
scan:
name: Scan changed deployment configuration
needs: changes
if: needs.changes.outputs.should_run == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
env:
BASE_REF: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || inputs.base_sha }}
HEAD_REF: ${{ inputs.head_sha || github.sha }}
defaults:
run:
shell: nix develop --command bash -euo pipefail {0}
steps:
- name: Check out candidate
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.HEAD_REF }}
persist-credentials: false
- name: Check out baseline
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ env.BASE_REF }}
path: .trivy-base
persist-credentials: false
- name: Set up Nix
uses: ./.github/actions/setup-nix
- name: Test report comparison
run: tasks/scripts/trivy-scan-test.sh
- name: Validate candidate ignore policy
run: tasks/scripts/trivy-scan.sh validate-ignore
# Ignore-policy changes take effect only after merge. Applying the
# baseline policy to both scans prevents a candidate from exempting a new
# finding in the same change that introduces it.
- name: Prepare baseline ignore policy
run: |
if [ -f .trivy-base/.trivyignore.yaml ]; then
cp .trivy-base/.trivyignore.yaml "$RUNNER_TEMP/trivy-baseline-ignore.yaml"
else
printf 'misconfigurations: []\n' >"$RUNNER_TEMP/trivy-baseline-ignore.yaml"
fi
- name: Scan baseline
env:
TRIVY_SOURCE_ROOT: ${{ github.workspace }}/.trivy-base
TRIVY_IGNORE_FILE: ${{ runner.temp }}/trivy-baseline-ignore.yaml
TRIVY_REPORT_DIR: ${{ runner.temp }}/trivy-base
run: |
mkdir -p "$TRIVY_REPORT_DIR"
"$GITHUB_WORKSPACE/tasks/scripts/trivy-scan.sh" config
- name: Scan candidate
env:
TRIVY_IGNORE_FILE: ${{ runner.temp }}/trivy-baseline-ignore.yaml
TRIVY_REPORT_DIR: ${{ runner.temp }}/trivy-head
run: |
mkdir -p "$TRIVY_REPORT_DIR"
tasks/scripts/trivy-scan.sh config
- name: Reject new high or critical findings
run: |
tasks/scripts/trivy-scan.sh gate-config-diff \
"$RUNNER_TEMP/trivy-base" "$RUNNER_TEMP/trivy-head"
- name: Upload reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: trivy-changes-${{ github.run_id }}
path: |
${{ runner.temp }}/trivy-base
${{ runner.temp }}/trivy-head
if-no-files-found: ignore
retention-days: 14
result:
name: OpenShell / Trivy Changes
needs: [changes, scan]
if: always()
runs-on: ubuntu-latest
steps:
- name: Check scan result
env:
CHANGES_RESULT: ${{ needs.changes.result }}
SHOULD_RUN: ${{ needs.changes.outputs.should_run }}
SCAN_RESULT: ${{ needs.scan.result }}
run: |
set -euo pipefail
if [ "$CHANGES_RESULT" != "success" ]; then
echo "::error::Change detection concluded $CHANGES_RESULT."
exit 1
fi
if [ "$SHOULD_RUN" = "true" ] && [ "$SCAN_RESULT" != "success" ]; then
echo "::error::Trivy scan concluded $SCAN_RESULT."
exit 1
fi
if [ "$SHOULD_RUN" != "true" ]; then
echo "No Helm or Dockerfile changes to scan."
fi
+219
View File
@@ -0,0 +1,219 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
name: Trivy Scan
# Manual or reusable scan of deployment configuration and supplied OCI
# artifacts. Findings are informational by default; scanner errors remain fatal.
on:
workflow_call:
inputs:
images:
description: Newline-separated image references to scan
type: string
default: ""
charts:
description: Newline-separated packaged Helm chart OCI references
type: string
default: ""
severity:
description: Severities that fail the workflow
type: string
default: HIGH,CRITICAL
ignore-unfixed:
description: Ignore image vulnerabilities with no upstream fix
type: boolean
default: true
fail-on-findings:
description: Fail the run on findings instead of warning
type: boolean
default: false
upload-sarif:
description: Upload results to GitHub Code Scanning
type: boolean
default: true
secrets:
CACHIX_AUTH_TOKEN:
description: Token used to write Nix build outputs to Cachix
workflow_dispatch:
inputs:
images:
description: Newline-separated image references to scan
type: string
default: ""
charts:
description: Newline-separated packaged Helm chart OCI references to scan
type: string
default: ""
severity:
description: Severities that fail the workflow
type: string
default: HIGH,CRITICAL
ignore-unfixed:
description: Ignore image vulnerabilities with no upstream fix
type: boolean
default: true
fail-on-findings:
description: Fail the run on findings instead of warning
type: boolean
default: false
upload-sarif:
description: Upload results to GitHub Code Scanning
type: boolean
default: true
permissions:
contents: read
defaults:
run:
shell: nix develop --command bash -euo pipefail {0}
env:
TRIVY_SEVERITY: ${{ inputs.severity || 'HIGH,CRITICAL' }}
TRIVY_REPORT_DIR: reports/trivy
jobs:
scan:
name: OpenShell / Trivy (informational)
runs-on: ubuntu-latest
timeout-minutes: 60
outputs:
sarif-batches: ${{ steps.sarif.outputs.batches }}
artifact-id: ${{ steps.reports.outputs.artifact-id }}
permissions:
contents: read
packages: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Set up Nix
uses: ./.github/actions/setup-nix
with:
cachix-auth-token: ${{ github.event_name != 'pull_request' && secrets.CACHIX_AUTH_TOKEN || '' }}
- name: Test scan reporting
run: tasks/scripts/trivy-scan-test.sh
- name: Scan configuration
id: config
env:
CHARTS: ${{ inputs.charts }}
run: |
args=()
while IFS= read -r ref; do
[ -n "$ref" ] || continue
args+=(--chart-ref "$ref")
done <<<"$CHARTS"
tasks/scripts/trivy-scan.sh config "${args[@]}"
- name: Scan images
id: images
if: ${{ !cancelled() }}
env:
IMAGES: ${{ inputs.images }}
TRIVY_IGNORE_UNFIXED: ${{ inputs.ignore-unfixed }}
run: |
refs=()
while IFS= read -r ref; do
[ -n "$ref" ] || continue
refs+=("$ref")
done <<<"$IMAGES"
if [ "${#refs[@]}" -gt 0 ]; then
tasks/scripts/trivy-scan.sh images "${refs[@]}"
fi
- name: Prepare consolidated SARIF uploads
id: sarif
if: >-
${{
!cancelled()
&& steps.config.conclusion == 'success'
&& steps.images.conclusion == 'success'
&& inputs.upload-sarif
}}
run: tasks/scripts/trivy-scan.sh prepare-sarif
- name: Upload reports
id: reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: trivy-${{ github.run_id }}
path: reports/trivy
if-no-files-found: ignore
retention-days: 14
- name: Report findings
if: >-
${{
!cancelled()
&& steps.config.conclusion == 'success'
&& steps.images.conclusion == 'success'
}}
env:
FAIL_ON_FINDINGS: ${{ inputs.fail-on-findings }}
run: |
set +e
tasks/scripts/trivy-scan.sh gate
status=$?
set -e
case "$status" in
0) echo "No findings at ${TRIVY_SEVERITY}." ;;
10)
if [ "$FAIL_ON_FINDINGS" = "true" ]; then
echo "::error::Trivy findings at ${TRIVY_SEVERITY}."
exit 1
fi
echo "::warning::Trivy findings at ${TRIVY_SEVERITY}; this check is informational."
;;
*) echo "::error::Trivy could not evaluate the reports (exit $status)."; exit "$status" ;;
esac
upload-sarif:
name: Publish Trivy SARIF (${{ matrix.batch }})
needs: scan
# Findings may fail the scan job after reports are prepared. Publish those
# complete results too, but never publish an incomplete/failed scan.
if: >-
${{
!cancelled()
&& inputs.upload-sarif
&& needs.scan.outputs.sarif-batches != ''
&& needs.scan.outputs.artifact-id != ''
}}
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
batch: ${{ fromJSON(needs.scan.outputs.sarif-batches) }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
artifact-ids: ${{ needs.scan.outputs.artifact-id }}
merge-multiple: true
path: reports/trivy
# One configuration run, plus one run per image/platform or packaged
# chart. Each batch holds at most GitHub's limit of 20 SARIF runs.
- uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
with:
sarif_file: reports/trivy/code-scanning/uploads/${{ matrix.batch }}