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
+3 -4
View File
@@ -7,11 +7,10 @@ contact_links:
Vouch Request discussion describing what you want to work on. A
maintainer will approve you with /vouch.
- name: Have a question?
url: https://github.com/NVIDIA/OpenShell/blob/main/CONTRIBUTING.md#agent-skills-for-contributors
url: https://github.com/NVIDIA/OpenShell/blob/main/CONTRIBUTING.md#skills-for-using-openshell
about: >
Point your agent at the repo. It has skills for CLI usage, cluster and
inference debugging, policy generation, and more. See CONTRIBUTING.md
for the full skills table.
Install the public OpenShell skills for CLI usage, cluster and inference
debugging, and policy generation. See CONTRIBUTING.md for details.
- name: Security vulnerability?
url: https://github.com/NVIDIA/OpenShell/blob/main/SECURITY.md
about: >
+14 -70
View File
@@ -14,9 +14,6 @@ inputs:
triple:
description: Rust target triple
required: true
dev-shell:
description: Nix development shell used to build the binary
required: true
cargo-version:
description: Cargo package version embedded in the binary
required: true
@@ -32,10 +29,6 @@ inputs:
description: Additional flags passed to cargo build
required: false
default: ""
interpreter:
description: ELF interpreter for a dynamically linked Linux binary
required: false
default: ""
runs:
using: composite
@@ -43,9 +36,9 @@ runs:
- name: Hash development shell
id: dev-shell
shell: bash
env:
DEV_SHELL: ${{ inputs.dev-shell }}
run: echo "hash=$(nix hash file --type sha256 --base16 "$(nix eval --raw "${DEV_SHELL}.drvPath")")" >> "$GITHUB_OUTPUT"
run: |
shell_drv=$(nix eval --raw --impure .#devShells --apply 'shells: shells.${builtins.currentSystem}.default.drvPath')
echo "hash=$(nix hash file --type sha256 --base16 "$shell_drv")" >> "$GITHUB_OUTPUT"
- name: Cache Rust artifacts
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
@@ -54,90 +47,41 @@ runs:
cache-on-failure: "true"
cache-workspace-crates: "true"
cache-bin: "false"
cmd-format: nix develop ${{ inputs.dev-shell }} -c {0}
cmd-format: nix develop -c {0}
- name: Set version
shell: nix develop ${{ inputs.dev-shell }} -c bash -euo pipefail {0}
shell: nix develop -c bash -euo pipefail {0}
env:
INPUTS_CARGO_VERSION: ${{ inputs.cargo-version }}
run: sed -i "s/^version = \"0\\.0\\.0\"$/version = \"${INPUTS_CARGO_VERSION}\"/" Cargo.toml
- name: Build ${{ inputs.binary }}
shell: nix develop ${{ inputs.dev-shell }} -c bash -euo pipefail {0}
shell: nix develop -c bash -euo pipefail {0}
env:
GIT_DIR: /nonexistent
OPENSHELL_IMAGE_TAG: ${{ inputs.image-tag }}
INPUTS_PACKAGE: ${{ inputs.package }}
INPUTS_BINARY: ${{ inputs.binary }}
INPUTS_TRIPLE: ${{ inputs.triple }}
INPUTS_EXTRA_CARGO_FLAGS: ${{ inputs.extra-cargo-flags }}
run: GIT_DIR=/nonexistent cargo auditable build --release --package "${INPUTS_PACKAGE}" --bin "${INPUTS_BINARY}" ${INPUTS_EXTRA_CARGO_FLAGS}
- name: Normalize Darwin dynamic binary
if: endsWith(inputs.triple, '-apple-darwin')
shell: bash -euo pipefail {0}
env:
INPUTS_BINARY: ${{ inputs.binary }}
run: |
binary=target/release/${INPUTS_BINARY}
nix_iconv_paths=$(otool -L "$binary" | awk '$1 ~ "^/nix/store/.*/lib/libiconv\\.2\\.dylib$" { print $1 }')
if [[ -n "$nix_iconv_paths" ]]; then
while IFS= read -r nix_iconv_path; do
install_name_tool -change "$nix_iconv_path" /usr/lib/libiconv.2.dylib "$binary"
done <<< "$nix_iconv_paths"
codesign --force --sign - "$binary"
fi
- name: Verify Darwin portability
if: endsWith(inputs.triple, '-apple-darwin')
shell: bash -euo pipefail {0}
env:
INPUTS_BINARY: ${{ inputs.binary }}
run: |
binary=target/release/${INPUTS_BINARY}
nix_dependencies=$(otool -L "$binary" | awk '$1 ~ "^/nix/store/" { print $1 }')
if [[ -n "$nix_dependencies" ]]; then
echo "error: $binary has non-portable Nix store dependencies:" >&2
printf ' %s\n' "$nix_dependencies" >&2
exit 1
fi
codesign --verify --verbose=2 "$binary"
run: cargo auditable build --release --target "${INPUTS_TRIPLE}" --package "${INPUTS_PACKAGE}" --bin "${INPUTS_BINARY}" ${INPUTS_EXTRA_CARGO_FLAGS}
- name: Verify ${{ inputs.binary }}
shell: nix develop ${{ inputs.dev-shell }} -c bash -euo pipefail {0}
shell: nix develop -c bash -euo pipefail {0}
env:
INPUTS_BINARY: ${{ inputs.binary }}
INPUTS_TRIPLE: ${{ inputs.triple }}
run: |
# Confirm the binary runs and reports the expected name.
target/release/${INPUTS_BINARY} --version | grep -q "^${INPUTS_BINARY} "
target/${INPUTS_TRIPLE}/release/${INPUTS_BINARY} --version | grep -q "^${INPUTS_BINARY} "
# Confirm Syft can decode the embedded cargo-auditable metadata.
SYFT_CHECK_FOR_APP_UPDATE=false syft file:target/release/${INPUTS_BINARY} -o cyclonedx-json | grep 'pkg:cargo/' > /dev/null
- name: Verify static linkage
if: endsWith(inputs.triple, '-linux-musl')
shell: nix develop ${{ inputs.dev-shell }} -c bash -euo pipefail {0}
env:
INPUTS_BINARY: ${{ inputs.binary }}
run: tasks/scripts/verify-static-binary.sh target/release/${INPUTS_BINARY}
- name: Normalize Linux dynamic binary
if: endsWith(inputs.triple, '-linux-gnu')
shell: nix develop ${{ inputs.dev-shell }} -c bash -euo pipefail {0}
env:
INPUTS_BINARY: ${{ inputs.binary }}
INPUTS_INTERPRETER: ${{ inputs.interpreter }}
run: |
binary=target/release/${INPUTS_BINARY}
# Remove Nix store paths so the binary can run on other distributions.
patchelf --set-interpreter "${INPUTS_INTERPRETER}" --remove-rpath "$binary"
# Z3 must be embedded instead of loaded from the target system.
test -z "$(patchelf --print-needed "$binary" | grep '^libz3')"
# Reject symbols introduced after glibc 2.28.
tasks/scripts/verify-glibc-symbols.sh 2.28 "$binary"
SYFT_CHECK_FOR_APP_UPDATE=false syft file:target/${INPUTS_TRIPLE}/release/${INPUTS_BINARY} -o cyclonedx-json | grep 'pkg:cargo/' > /dev/null
- name: Upload ${{ inputs.binary }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ inputs.artifact-name || format('{0}-{1}', inputs.binary, inputs.triple) }}
path: target/release/${{ inputs.binary }}
path: target/${{ inputs.triple }}/release/${{ inputs.binary }}
compression-level: 0
retention-days: 5
if-no-files-found: error
+41 -22
View File
@@ -4,7 +4,7 @@
name: Release Helm OCI
description: >
Patch chart version/appVersion, refuse duplicate OCI versions on public
releases, package the chart, and push to GHCR OCI.
releases, package the gateway and workspace charts, and push them to GHCR OCI.
inputs:
chart-version:
@@ -51,11 +51,16 @@ runs:
shell: bash
run: |
set -euo pipefail
CHART_DIR="${RUNNER_TEMP}/chart-build"
cp -a deploy/helm/openshell/. "${CHART_DIR}"
sed -i "s/^version:.*/version: ${CHART_VERSION}/" "${CHART_DIR}/Chart.yaml"
sed -i "s/^appVersion:.*/appVersion: \"${APP_VERSION}\"/" "${CHART_DIR}/Chart.yaml"
echo "chart_dir=${CHART_DIR}" >> "$GITHUB_OUTPUT"
GATEWAY_CHART_DIR="${RUNNER_TEMP}/gateway-chart-build"
WORKSPACE_CHART_DIR="${RUNNER_TEMP}/workspace-chart-build"
cp -a deploy/helm/openshell/. "${GATEWAY_CHART_DIR}"
cp -a deploy/helm/openshell-workspace/. "${WORKSPACE_CHART_DIR}"
for chart_dir in "${GATEWAY_CHART_DIR}" "${WORKSPACE_CHART_DIR}"; do
sed -i "s/^version:.*/version: ${CHART_VERSION}/" "${chart_dir}/Chart.yaml"
sed -i "s/^appVersion:.*/appVersion: \"${APP_VERSION}\"/" "${chart_dir}/Chart.yaml"
done
echo "gateway_chart_dir=${GATEWAY_CHART_DIR}" >> "$GITHUB_OUTPUT"
echo "workspace_chart_dir=${WORKSPACE_CHART_DIR}" >> "$GITHUB_OUTPUT"
echo "chart_version=${CHART_VERSION}" >> "$GITHUB_OUTPUT"
- name: Refuse duplicate chart version
@@ -65,38 +70,52 @@ runs:
shell: bash
run: |
set -euo pipefail
OCI_CHART="oci://ghcr.io/nvidia/openshell/helm-chart"
if helm show chart "${OCI_CHART}" --version "${CHART_VERSION}" >/dev/null 2>&1; then
echo "::error::Chart ${CHART_VERSION} is already published. Use a new tag or delete the existing package first."
exit 1
fi
for chart in helm-chart openshell-workspace; do
OCI_CHART="oci://ghcr.io/nvidia/openshell/${chart}"
if helm show chart "${OCI_CHART}" --version "${CHART_VERSION}" >/dev/null 2>&1; then
echo "::error::Chart ${chart}:${CHART_VERSION} is already published. Use a new tag or delete the existing package first."
exit 1
fi
done
- name: Package Helm chart
env:
CHART_DIR: ${{ steps.prep.outputs.chart_dir }}
GATEWAY_CHART_DIR: ${{ steps.prep.outputs.gateway_chart_dir }}
WORKSPACE_CHART_DIR: ${{ steps.prep.outputs.workspace_chart_dir }}
shell: bash
run: |
set -euo pipefail
helm package "${CHART_DIR}" --destination /tmp
ls /tmp/helm-chart-*.tgz
mkdir -p /tmp/helm-charts
helm package "${GATEWAY_CHART_DIR}" --destination /tmp/helm-charts
helm package "${WORKSPACE_CHART_DIR}" --destination /tmp/helm-charts
ls /tmp/helm-charts/*.tgz
- name: Push Helm chart to GHCR OCI
shell: bash
run: |
set -euo pipefail
helm push /tmp/helm-chart-*.tgz oci://ghcr.io/nvidia/openshell
for archive in /tmp/helm-charts/*.tgz; do
helm push "${archive}" oci://ghcr.io/nvidia/openshell
done
- name: Push SHA-pinned chart
if: inputs.pin-sha != ''
env:
PIN_SHA: ${{ inputs.pin-sha }}
CHART_DIR: ${{ steps.prep.outputs.chart_dir }}
GATEWAY_CHART_DIR: ${{ steps.prep.outputs.gateway_chart_dir }}
WORKSPACE_CHART_DIR: ${{ steps.prep.outputs.workspace_chart_dir }}
shell: bash
run: |
set -euo pipefail
SHA_CHART_DIR="${RUNNER_TEMP}/chart-build-sha"
cp -a "${CHART_DIR}/." "${SHA_CHART_DIR}"
sed -i "s/^version:.*/version: 0.0.0-dev.${PIN_SHA}/" "${SHA_CHART_DIR}/Chart.yaml"
sed -i "s/^appVersion:.*/appVersion: \"${PIN_SHA}\"/" "${SHA_CHART_DIR}/Chart.yaml"
helm package "${SHA_CHART_DIR}" --destination /tmp/sha-pin
helm push /tmp/sha-pin/helm-chart-*.tgz oci://ghcr.io/nvidia/openshell
mkdir -p /tmp/sha-pin
for source_dir in "${GATEWAY_CHART_DIR}" "${WORKSPACE_CHART_DIR}"; do
chart_name="$(basename "${source_dir}")"
sha_chart_dir="${RUNNER_TEMP}/${chart_name}-sha"
cp -a "${source_dir}/." "${sha_chart_dir}"
sed -i "s/^version:.*/version: 0.0.0-dev.${PIN_SHA}/" "${sha_chart_dir}/Chart.yaml"
sed -i "s/^appVersion:.*/appVersion: \"${PIN_SHA}\"/" "${sha_chart_dir}/Chart.yaml"
helm package "${sha_chart_dir}" --destination /tmp/sha-pin
done
for archive in /tmp/sha-pin/*.tgz; do
helm push "${archive}" oci://ghcr.io/nvidia/openshell
done
+31 -1
View File
@@ -1,5 +1,11 @@
name: Setup E2E CLI
description: Download an architecture-matched prebuilt OpenShell CLI for E2E tests
description: Download architecture-matched prebuilt OpenShell host CLIs for E2E tests
inputs:
conformance-artifact-prefix:
description: Optional conformance CLI artifact name prefix; linux-<arch> is appended automatically
required: false
default: ""
runs:
using: composite
@@ -17,3 +23,27 @@ runs:
chmod +x "$cli"
"$cli" --version
echo "OPENSHELL_BIN=$cli" >> "$GITHUB_ENV"
- name: Download prebuilt conformance CLI
if: inputs.conformance-artifact-prefix != ''
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ format('{0}-{1}', inputs.conformance-artifact-prefix, runner.arch == 'X64' && 'x86_64-unknown-linux-musl' || 'aarch64-unknown-linux-musl') }}
path: .e2e/prebuilt-conformance
- name: Configure prebuilt conformance CLI
if: inputs.conformance-artifact-prefix != ''
shell: bash
run: | # zizmor: ignore[github-env] validated filename under the trusted workspace path
set -euo pipefail
conformance="$GITHUB_WORKSPACE/.e2e/prebuilt-conformance/openshell-conformance"
if [[ ! -f "$conformance" ]]; then
echo "downloaded artifact is missing $conformance" >&2
exit 1
fi
# TODO: Remove this temporary mode diagnostic after CI confirms artifact permission handling.
echo "conformance binary mode before chmod: $(ls -l "$conformance")"
chmod +x "$conformance"
echo "conformance binary mode after chmod: $(ls -l "$conformance")"
"$conformance" list --output json >/dev/null
echo "OPENSHELL_CONFORMANCE_BIN=$conformance" >> "$GITHUB_ENV"
+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 }}