Skip to content
Draft
73 changes: 73 additions & 0 deletions .github/actions/resolve-sha-artifacts/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Resolve SHA Artifacts
description: Download and verify a digest-pinned OpenShell SHA artifact bundle from GHCR.

inputs:
manifest-ref:
description: "Digest-pinned OCI artifact manifest reference"
required: true
source-sha:
description: "Expected full source commit SHA"
required: true
token:
description: "Token with packages:read permission"
required: true
path:
description: "Directory into which the verified bundle is extracted"
required: false
default: .sha-artifacts

outputs:
path:
description: "Directory containing the verified artifact files"
value: ${{ steps.resolve.outputs.path }}

runs:
using: composite
steps:
- name: Install ORAS
uses: oras-project/setup-oras@22ce207df3b08e061f537244349aac6ae1d214f6 # v2.0.1

- name: Download and verify SHA artifacts
id: resolve
shell: bash
env:
MANIFEST_REF: ${{ inputs.manifest-ref }}
EXPECTED_SOURCE_SHA: ${{ inputs.source-sha }}
GH_TOKEN: ${{ inputs.token }}
OUTPUT_PATH: ${{ inputs.path }}
run: |
set -euo pipefail
test "${#EXPECTED_SOURCE_SHA}" -eq 40
case "$MANIFEST_REF" in
ghcr.io/*@sha256:*) ;;
*)
echo "manifest-ref must be a digest-pinned GHCR reference" >&2
exit 1
;;
esac
if [ -e "$OUTPUT_PATH" ]; then
echo "artifact output path already exists: $OUTPUT_PATH" >&2
exit 1
fi
mkdir -p "$OUTPUT_PATH"
echo "$GH_TOKEN" | oras login ghcr.io -u "$GITHUB_ACTOR" --password-stdin
oras pull "$MANIFEST_REF" -o "$OUTPUT_PATH"
manifest="$OUTPUT_PATH/sha-artifacts.json"
bundle="$OUTPUT_PATH/sha-artifacts.tar.gz"
test -f "$manifest"
test -f "$bundle"
actual_source_sha="$(jq -r '.source.sha' "$manifest")"
if [ "$actual_source_sha" != "$EXPECTED_SOURCE_SHA" ]; then
echo "artifact manifest source SHA does not match the expected commit" >&2
exit 1
fi
jq -e '.schema_version == 1 and (.artifacts | type == "array" and length > 0)' "$manifest" >/dev/null
mkdir "$OUTPUT_PATH/bundle"
tar -xzf "$bundle" --no-same-owner -C "$OUTPUT_PATH/bundle"
jq -e 'all(.artifacts[]; (.sha256 | test("^[0-9a-f]{64}$")) and (.path | test("^(?!/)(?!.*(^|/)\\.\\.(/|$)).+$")))' "$manifest" >/dev/null
jq -r '.artifacts[] | "\(.sha256) \(.path)"' "$manifest" > "$OUTPUT_PATH/checksums.txt"
(
cd "$OUTPUT_PATH/bundle"
sha256sum -c ../checksums.txt
)
echo "path=$OUTPUT_PATH/bundle" >> "$GITHUB_OUTPUT"
56 changes: 55 additions & 1 deletion .github/workflows/branch-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,17 @@ jobs:
component: gateway
image-tag: ${{ github.sha }}

build-gateway-external:
needs: [pr_metadata]
if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true'
permissions:
contents: read
packages: write
uses: ./.github/workflows/docker-build.yml
with:
component: gateway-external
image-tag: ${{ github.sha }}

build-supervisor:
needs: [pr_metadata]
if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_any_e2e == 'true'
Expand Down Expand Up @@ -123,8 +134,50 @@ jobs:
driver-targets: >-
[{"arch":"amd64","runner":"linux-amd64-cpu8","target":"x86_64-unknown-linux-gnu","zig_target":"x86_64-unknown-linux-gnu.2.28","platform":"linux-x86_64","guest_arch":"x86_64"}]

publish-sha-artifacts:
needs: [pr_metadata, build-gateway, build-gateway-external, build-cli]
if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true'
permissions:
actions: read
attestations: write
contents: read
id-token: write
packages: write
uses: ./.github/workflows/publish-sha-artifacts.yml
with:
source-sha: ${{ github.sha }}
artifact-set: branch-e2e
secrets: inherit

verify-sha-artifacts:
needs: [pr_metadata, publish-sha-artifacts]
if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Resolve published SHA artifacts
id: artifacts
uses: ./.github/actions/resolve-sha-artifacts
with:
manifest-ref: ${{ needs.publish-sha-artifacts.outputs.manifest_ref }}
source-sha: ${{ github.sha }}
token: ${{ secrets.GITHUB_TOKEN }}

- name: Verify amd64 binaries execute
env:
ARTIFACTS_DIR: ${{ steps.artifacts.outputs.path }}
run: |
set -euo pipefail
"$ARTIFACTS_DIR/rust-binary-cli-linux-amd64/openshell" --version
"$ARTIFACTS_DIR/rust-binary-gateway-linux-amd64/openshell-gateway" --version
"$ARTIFACTS_DIR/rust-binary-gateway-external-linux-amd64/openshell-gateway" --version

e2e:
needs: [pr_metadata, build-gateway, build-supervisor, build-cli, build-driver-vm-linux]
needs: [pr_metadata, build-gateway, build-gateway-external, build-supervisor, build-cli, build-driver-vm-linux]
if: needs.pr_metadata.outputs.should_run == 'true' && needs.pr_metadata.outputs.run_core_e2e == 'true'
permissions:
actions: read
Expand All @@ -136,6 +189,7 @@ jobs:
runner: linux-arm64-cpu8
cli-artifact-prefix: rust-binary-cli
gateway-artifact-prefix: rust-binary-gateway
external-gateway-artifact-prefix: rust-binary-gateway-external
vm-driver-artifact-name: driver-vm-linux-amd64

gpu-e2e:
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ jobs:
artifact_prefix: ${{ steps.resolve.outputs.artifact_prefix }}
image_tag_base: ${{ steps.resolve.outputs.image_tag_base }}
features: ${{ steps.resolve.outputs.features }}
no_default_features: ${{ steps.resolve.outputs.no_default_features }}
has_image: ${{ steps.resolve.outputs.has_image }}
steps:
- name: Resolve component and platform matrix
Expand All @@ -92,6 +93,13 @@ jobs:
features="bundled-z3"
has_image=true
;;
gateway-external)
binary_component=gateway
binary_name=openshell-gateway
features="telemetry"
no_default_features=true
has_image=false
;;
supervisor)
binary_component=sandbox
binary_name=openshell-sandbox
Expand Down Expand Up @@ -162,6 +170,7 @@ jobs:
echo "artifact_prefix=rust-binary-${component}"
echo "image_tag_base=$image_tag_base"
echo "features=$features"
echo "no_default_features=${no_default_features:-false}"
echo "has_image=$has_image"
} >> "$GITHUB_OUTPUT"

Expand All @@ -182,6 +191,7 @@ jobs:
image-tag: ${{ needs.resolve.outputs.image_tag_base }}
checkout-ref: ${{ inputs['checkout-ref'] }}
features: ${{ needs.resolve.outputs.features }}
no-default-features: ${{ needs.resolve.outputs.no_default_features == 'true' }}
auditable: ${{ inputs.auditable }}
artifact-name: ${{ needs.resolve.outputs.artifact_prefix }}-linux-${{ matrix.arch }}
secrets: inherit
Expand Down
35 changes: 29 additions & 6 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ on:
required: false
type: string
default: ""
external-gateway-artifact-prefix:
description: "Prebuilt gateway artifact prefix for external-driver suites"
required: false
type: string
default: ""
vm-driver-artifact-name:
description: "Optional prebuilt VM driver artifact name"
required: false
Expand Down Expand Up @@ -60,7 +65,7 @@ jobs:
cmd: "mise run --no-deps --skip-deps e2e:rust"
apt_packages: "openssh-client"
- suite: rust-docker-external-driver
cmd: "env -u OPENSHELL_GATEWAY_BIN mise run --no-deps --skip-deps e2e:docker:external-driver"
cmd: "mise run --no-deps --skip-deps e2e:docker:external-driver"
apt_packages: "openssh-client"
- suite: mcp
cmd: "mise run --no-deps --skip-deps e2e:mcp"
Expand Down Expand Up @@ -95,11 +100,17 @@ jobs:
artifact-prefix: ${{ inputs.cli-artifact-prefix }}

- name: Use prebuilt OpenShell gateway
if: inputs.gateway-artifact-prefix != ''
if: inputs.gateway-artifact-prefix != '' && matrix.suite != 'rust-docker-external-driver'
uses: ./.github/actions/setup-e2e-gateway
with:
artifact-prefix: ${{ inputs.gateway-artifact-prefix }}

- name: Use prebuilt external-driver gateway
if: inputs.external-gateway-artifact-prefix != '' && matrix.suite == 'rust-docker-external-driver'
uses: ./.github/actions/setup-e2e-gateway
with:
artifact-prefix: ${{ inputs.external-gateway-artifact-prefix }}

- name: Check out MCP conformance tests
if: matrix.suite == 'mcp'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -156,7 +167,7 @@ jobs:
podman_major: "5"
podman_package_version: "5.7.0+ds2-3build1"
conmon_package_version: "2.1.13+ds1-2"
cmd: "env -u OPENSHELL_GATEWAY_BIN mise run --no-deps --skip-deps e2e:podman:external-driver"
cmd: "mise run --no-deps --skip-deps e2e:podman:external-driver"
- suite: provider-refresh-keycloak
runner: ubuntu-26.04
podman_major: "5"
Expand Down Expand Up @@ -185,11 +196,17 @@ jobs:
artifact-prefix: ${{ inputs.cli-artifact-prefix }}

- name: Use prebuilt OpenShell gateway
if: inputs.gateway-artifact-prefix != ''
if: inputs.gateway-artifact-prefix != '' && matrix.suite != 'external-driver'
uses: ./.github/actions/setup-e2e-gateway
with:
artifact-prefix: ${{ inputs.gateway-artifact-prefix }}

- name: Use prebuilt external-driver gateway
if: inputs.external-gateway-artifact-prefix != '' && matrix.suite == 'external-driver'
uses: ./.github/actions/setup-e2e-gateway
with:
artifact-prefix: ${{ inputs.external-gateway-artifact-prefix }}

- name: Install mise
run: |
curl https://mise.run | MISE_VERSION=v2026.4.25 sh
Expand Down Expand Up @@ -325,7 +342,7 @@ jobs:
- suite: managed
cmd: "mise run --no-deps --skip-deps e2e:vm"
- suite: external-driver
cmd: "env -u OPENSHELL_GATEWAY_BIN mise run --no-deps --skip-deps e2e:vm:external-driver"
cmd: "mise run --no-deps --skip-deps e2e:vm:external-driver"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MISE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -342,11 +359,17 @@ jobs:
artifact-prefix: ${{ inputs.cli-artifact-prefix }}

- name: Use prebuilt OpenShell gateway
if: inputs.gateway-artifact-prefix != ''
if: inputs.gateway-artifact-prefix != '' && matrix.suite != 'external-driver'
uses: ./.github/actions/setup-e2e-gateway
with:
artifact-prefix: ${{ inputs.gateway-artifact-prefix }}

- name: Use prebuilt external-driver gateway
if: inputs.external-gateway-artifact-prefix != '' && matrix.suite == 'external-driver'
uses: ./.github/actions/setup-e2e-gateway
with:
artifact-prefix: ${{ inputs.external-gateway-artifact-prefix }}

- name: Use prebuilt OpenShell VM driver
if: inputs.vm-driver-artifact-name != ''
uses: ./.github/actions/setup-e2e-vm-driver
Expand Down
95 changes: 95 additions & 0 deletions .github/workflows/publish-sha-artifacts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: Publish SHA Artifacts

on:
workflow_call:
inputs:
source-sha:
description: "Full source commit SHA represented by the artifacts"
required: true
type: string
artifact-set:
description: "Named build-profile variant for this artifact set"
required: true
type: string
artifact-pattern:
description: "GitHub Actions artifact pattern to publish"
required: false
type: string
default: "rust-binary-*"
outputs:
manifest_ref:
description: "Digest-pinned OCI manifest reference"
value: ${{ jobs.publish.outputs.manifest_ref }}

permissions:
actions: read
attestations: write
contents: read
id-token: write
packages: write

jobs:
publish:
name: Publish SHA artifact manifest
runs-on: ubuntu-latest
timeout-minutes: 15
outputs:
manifest_ref: ${{ steps.publish.outputs.manifest_ref }}
steps:
- name: Download build artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: ${{ inputs.artifact-pattern }}
path: artifacts

- name: Install ORAS
uses: oras-project/setup-oras@22ce207df3b08e061f537244349aac6ae1d214f6 # v2.0.1

- name: Assemble deterministic artifact bundle
env:
SOURCE_SHA: ${{ inputs.source-sha }}
ARTIFACT_SET: ${{ inputs.artifact-set }}
run: |
set -euo pipefail
test "${#SOURCE_SHA}" -eq 40
mkdir -p bundle
cp -a artifacts/. bundle/
find bundle -type f -print0 | sort -z | xargs -0 sha256sum > artifact-checksums.txt
jq -n \
--arg schema_version "1" \
--arg source_sha "$SOURCE_SHA" \
--arg artifact_set "$ARTIFACT_SET" \
--argjson artifacts "$(jq -R -s -c 'split("\n") | map(select(length > 0) | capture("^(?<sha256>[0-9a-f]{64}) (?<path>.+)$"))' artifact-checksums.txt)" \
'{schema_version: ($schema_version | tonumber), source: {repository: env.GITHUB_REPOSITORY, sha: $source_sha}, artifact_set: $artifact_set, artifacts: $artifacts}' \
> sha-artifacts.json
tar --sort=name --mtime='@0' --owner=0 --group=0 --numeric-owner -C bundle -cf - . | gzip -n > sha-artifacts.tar.gz

- name: Publish immutable OCI artifact
id: publish
env:
SOURCE_SHA: ${{ inputs.source-sha }}
ARTIFACT_SET: ${{ inputs.artifact-set }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
repository="ghcr.io/${GITHUB_REPOSITORY,,}/ci-artifacts"
tag="sha-${SOURCE_SHA}-${ARTIFACT_SET}"
ref="${repository}:${tag}"
echo "$GH_TOKEN" | oras login ghcr.io -u "$GITHUB_ACTOR" --password-stdin
if oras manifest fetch --descriptor "$ref" >/dev/null 2>&1; then
echo "refusing to overwrite existing SHA artifact manifest: $ref" >&2
exit 1
fi
digest="$(oras push --format json \
--artifact-type application/vnd.nvidia.openshell.sha-artifacts.v1 \
--annotation "org.opencontainers.image.revision=${SOURCE_SHA}" \
--annotation "io.openshell.artifact-set=${ARTIFACT_SET}" \
"$ref" \
sha-artifacts.json:application/vnd.nvidia.openshell.sha-artifacts.manifest.v1+json \
sha-artifacts.tar.gz:application/vnd.nvidia.openshell.sha-artifacts.bundle.v1+gzip | jq -r '.digest')"
echo "manifest_ref=${repository}@${digest}" >> "$GITHUB_OUTPUT"

- name: Attest SHA artifact manifest
uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4.2.1
with:
subject-path: sha-artifacts.json
Loading
Loading