Skip to content

storybook applied, fix failed deploy #263

storybook applied, fix failed deploy

storybook applied, fix failed deploy #263

Workflow file for this run

name: Operator Build
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
cancel-in-progress: ${{ github.ref_name != 'main' }}
on:
push:
branches:
- main
paths-ignore:
- 'VERSION'
- 'Cargo.toml'
- 'Cargo.lock'
- 'docs/**'
- '.github/workflows/docs.yml'
- 'vscode-extension/package.json'
- 'vscode-extension/src/webhook-server.ts'
pull_request:
branches:
- main
paths-ignore:
- 'docs/**'
- '.github/workflows/docs.yml'
env:
CARGO_TERM_COLOR: always
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
permissions:
contents: write
actions: write
jobs:
lint-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install toolchain
uses: dtolnay/rust-toolchain@1.95
with:
components: rustfmt, clippy
- name: Cache cargo
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-
# The frontend is typed against these; generate before anything compiles.
- name: Generate TypeScript bindings and verify they are committed
run: |
scripts/check-bindings-fresh.sh || {
echo "::error::bindings/ is out of date. Run 'make bindings' and commit the result."
exit 1
}
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.14
# oxfmt/oxlint are installed once at the repo root and cover every hand-written JS/TS subproject.
# The type-aware lints resolve each subproject's own node_modules and
# copy-types output, so those have to exist before oxlint runs
- name: Install frontend dependencies
run: |
bun install --frozen-lockfile
(cd webcomponents && bun install --frozen-lockfile && bun run copy-types)
(cd ui && bun install --frozen-lockfile)
(cd vscode-extension && npm ci && npm run copy-types)
- name: Build shared web components
run: |
cd webcomponents
bun install --frozen-lockfile
bun run typecheck
bun test
bun run build
- name: Format and lint frontend
run: |
bun run fmt:check
bun run lint:ui
bun run lint:webcomponents
bun run lint:vscode
bun run lint:agnt
bun run lint:coder-module
- name: Lint shell scripts
run: shellcheck -S warning scripts/*.sh scripts/ci/*.sh .githooks/*
- name: Build UI dist
run: |
cd ui
bun install --frozen-lockfile
bun run typecheck
bun run build
DIST_SIZE=$(du -sb dist/ | cut -f1)
echo "UI dist size: ${DIST_SIZE}B ($(echo "scale=1; $DIST_SIZE/1048576" | bc)MB uncompressed)"
if [ "$DIST_SIZE" -gt 5242880 ]; then
echo "::error::UI dist exceeds 5MB uncompressed budget (${DIST_SIZE}B)"
exit 1
fi
- name: Set up Helm
uses: azure/setup-helm@v5.0.1
- name: Check formatting
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --locked --all-targets --all-features -- -D warnings
- name: Tests
run: cargo test --locked --all-features
- name: cargo-deny
uses: EmbarkStudios/cargo-deny-action@v2
with:
manifest-path: Cargo.toml
arguments: --all-features
command: check
# crates/relay has its own Cargo.lock and is not a workspace member, so
# the root fmt/clippy/deny above never reach it.
- name: crates/relay
run: |
cd crates/relay
cargo fmt -- --check
cargo clippy --locked --all-targets --all-features -- -D warnings
cargo test --locked --all-features
- name: cargo-deny (crates/relay)
uses: EmbarkStudios/cargo-deny-action@v2
with:
manifest-path: crates/relay/Cargo.toml
arguments: --all-features
command: check
- name: Lint and render Helm chart
run: |
helm lint charts/operator
helm template operator charts/operator \
--set ingress.enabled=true \
--set ingress.host=operator.example.com \
--set ingress.tls.secretName=operator-tls \
--set networkPolicy.enabled=true \
--set bootstrap.existingSecret=operator-bootstrap \
> operator-chart.yaml
helm template operator charts/operator \
--set-json 'extraVolumes=[{"name":"custom-ca","configMap":{"name":"custom-ca"}}]' \
--set-json 'extraVolumeMounts=[{"name":"custom-ca","mountPath":"/etc/custom-ca","readOnly":true}]' \
--set-json 'lifecycle={"preStop":{"exec":{"command":["/bin/sh","-c","true"]}}}' \
> operator-chart-extensions.yaml
if helm template operator charts/operator --set terminationGracePeriodSeconds=75; then
echo "::error::invalid shutdown budget rendered successfully"
exit 1
fi
if helm template operator charts/operator \
--set-json 'extraVolumes=[{"name":"workspace","emptyDir":{}}]'; then
echo "::error::chart-owned volume collision rendered successfully"
exit 1
fi
if helm template operator charts/operator \
--set-json 'extraVolumeMounts=[{"name":"one","mountPath":"/shared"},{"name":"two","mountPath":"/shared"}]'; then
echo "::error::duplicate mount path rendered successfully"
exit 1
fi
- name: Scan rendered Helm chart
uses: aquasecurity/trivy-action@v0.36.0
with:
scan-type: config
scan-ref: operator-chart.yaml
severity: HIGH,CRITICAL
exit-code: '1'
# Storybook renders every story in a real browser and runs axe over it.
# Deliberately a peer of lint-test with no `needs:`, so it stays a required PR check
storybook:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install toolchain
uses: dtolnay/rust-toolchain@1.95
- name: Cache cargo
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-storybook-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-storybook-
${{ runner.os }}-cargo-
# The stories are typed against these; generate before they compile.
# lint-test owns the "bindings are committed and fresh" gate.
- name: Generate TypeScript bindings
run: cargo test --locked export_bindings_
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.14
- name: Install dependencies
run: cd webcomponents && bun install --frozen-lockfile
- name: Typecheck stories
run: cd webcomponents && bun run typecheck:stories
- name: Install Chromium
run: cd webcomponents && bunx playwright install --with-deps chromium
# Keep byte-identical to the `storybook` Makefile target.
- name: Build and test Storybook
run: |
cd webcomponents
bun run storybook:build
bun run test:storybook
# Compute the next version once, before anything is built, so the compiled
# binaries embed the same version that the release tag + Docker tag will use.
version:
needs: lint-test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
outputs:
version: ${{ steps.calc.outputs.version }}
steps:
- uses: actions/checkout@v7
- id: calc
run: |
V=$(cat VERSION)
IFS='.' read -r major minor patch <<< "$V"
NEW_VERSION="$major.$minor.$((patch + 1))"
echo "version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
echo "Next version: $V -> $NEW_VERSION"
coverage:
needs: lint-test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@1.95
with:
components: llvm-tools-preview
- name: Set up Helm
uses: azure/setup-helm@v5.0.1
- name: Cache cargo
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-coverage-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-coverage-
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@v2.85.4
with:
tool: cargo-llvm-cov
- name: Configure Git for tests
run: |
git config user.email "ci@example.com"
git config user.name "CI"
git remote set-head origin --auto || true
- name: Generate coverage
env:
# Jira integration test credentials
OPERATOR_JIRA_DOMAIN: ${{ secrets.OPERATOR_JIRA_DOMAIN }}
OPERATOR_JIRA_EMAIL: ${{ secrets.OPERATOR_JIRA_EMAIL }}
OPERATOR_JIRA_API_KEY: ${{ secrets.OPERATOR_JIRA_API_KEY }}
OPERATOR_JIRA_TEST_PROJECT: ${{ secrets.OPERATOR_JIRA_TEST_PROJECT }}
# Linear integration test credentials
OPERATOR_LINEAR_API_KEY: ${{ secrets.OPERATOR_LINEAR_API_KEY }}
OPERATOR_LINEAR_TEST_TEAM: ${{ secrets.OPERATOR_LINEAR_TEST_TEAM }}
# Git integration test flags
OPERATOR_GIT_TEST_ENABLED: 'true'
OPERATOR_GIT_PUSH_ENABLED: 'true'
run: cargo llvm-cov --locked --all-features --codecov --output-path codecov.json -- --test-threads=1
- name: Cleanup optest branches
if: always()
run: |
git fetch --prune origin
for branch in $(git branch -r | grep 'origin/optest/' | sed 's|origin/||' | xargs); do
echo "Cleaning up orphaned test branch: $branch"
git push origin --delete "$branch" || true
done
- name: Upload to Codecov
uses: codecov/codecov-action@v7
with:
files: codecov.json
flags: rust
fail_ci_if_error: true
launch-integration:
needs: lint-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install toolchain
uses: dtolnay/rust-toolchain@1.95
- name: Install tmux
run: sudo apt-get update && sudo apt-get install -y tmux
- name: Cache cargo
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-launch-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-launch-
- name: Build operator
run: cargo build --locked
- name: Run launch integration tests
env:
OPERATOR_LAUNCH_TEST_ENABLED: 'true'
run: cargo test --locked --test launch_integration -- --test-threads=1 --nocapture
- name: Cleanup sessions
if: always()
run: |
tmux list-sessions -F '#{session_name}' 2>/dev/null | grep '^op' | xargs -I {} tmux kill-session -t {} || true
zellij delete-all-sessions --yes --force 2>/dev/null || true
relay-integration:
needs: lint-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install toolchain
uses: dtolnay/rust-toolchain@1.95
- name: Cache cargo
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-relay-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-relay-
- name: Build opr8r (provides relay subcommand)
run: cargo build --locked --manifest-path opr8r/Cargo.toml
- name: Run relay integration tests
env:
OPERATOR_RELAY_INTEGRATION_TEST_ENABLED: 'true'
run: cargo test --locked --test relay_integration -- --nocapture --test-threads=1
build:
needs: [lint-test, version]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact_name: operator-linux-x86_64
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
artifact_name: operator-linux-arm64
- os: macos-14
target: aarch64-apple-darwin
artifact_name: operator-macos-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
artifact_name: operator-windows-x86_64.exe
- os: windows-11-arm
target: aarch64-pc-windows-msvc
artifact_name: operator-windows-arm64.exe
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- name: Install toolchain
uses: dtolnay/rust-toolchain@1.95
with:
targets: ${{ matrix.target }}
- name: Cache cargo
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-${{ matrix.target }}-cargo-
# Install Bun (for embedded UI build)
- name: Install Bun
uses: oven-sh/setup-bun@v2
with:
# Pinned to match .tool-versions
bun-version: 1.3.14
# Shared components first: ui/ imports @operator/webcomponents from its dist.
- name: Build shared web components
run: |
cd webcomponents
bun install --frozen-lockfile
bun run build
# Build embedded web UI for operator binary
- name: Build UI for embedding
run: |
cd ui
bun install --frozen-lockfile
bun run build
# Embed the release version so the binary's --version matches its tag.
# Portable across the linux/macos/windows matrix (sed-to-tempfile, no -i).
- name: Set release version
shell: bash
run: |
V="${{ needs.version.outputs.version }}"
echo "$V" > VERSION
sed 's/^version = ".*"/version = "'"$V"'"/' Cargo.toml > Cargo.toml.tmp && mv Cargo.toml.tmp Cargo.toml
cargo update --workspace
# Build release with embedded UI
- name: Build release
run: cargo build --locked --release --features embed-ui --target ${{ matrix.target }}
- name: Rename binary
shell: bash
run: |
if [[ "${{ matrix.target }}" == *"windows"* ]]; then
cp target/${{ matrix.target }}/release/operator.exe ${{ matrix.artifact_name }}
else
cp target/${{ matrix.target }}/release/operator ${{ matrix.artifact_name }}
fi
# Apple code signing + notarization (macOS only)
- name: Codesign operator binary
if: matrix.os == 'macos-14'
env:
APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: scripts/codesign.sh "${{ matrix.artifact_name }}"
- name: Notarize operator binary
if: matrix.os == 'macos-14'
env:
APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
run: scripts/notarize.sh "${{ matrix.artifact_name }}"
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.artifact_name }}
path: ${{ matrix.artifact_name }}
build-opr8r:
needs: [lint-test, version]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact_name: opr8r-linux-x86_64
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
artifact_name: opr8r-linux-arm64
- os: macos-14
target: aarch64-apple-darwin
artifact_name: opr8r-macos-arm64
- os: windows-latest
target: x86_64-pc-windows-msvc
artifact_name: opr8r-windows-x86_64.exe
- os: windows-11-arm
target: aarch64-pc-windows-msvc
artifact_name: opr8r-windows-arm64.exe
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: opr8r
steps:
- uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@1.95
- name: Cache cargo registry
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
opr8r/target
key: opr8r-${{ matrix.artifact_name }}-cargo-${{ hashFiles('opr8r/Cargo.lock') }}
restore-keys: |
opr8r-${{ matrix.artifact_name }}-cargo-
# Embed the release version (working-directory is opr8r/, so this edits
# opr8r/Cargo.toml). Portable across the build matrix.
- name: Set release version
shell: bash
run: |
V="${{ needs.version.outputs.version }}"
sed 's/^version = ".*"/version = "'"$V"'"/' Cargo.toml > Cargo.toml.tmp && mv Cargo.toml.tmp Cargo.toml
cargo update --workspace
- name: Build release binary
run: cargo build --locked --release
- name: Strip binary (Linux/macOS)
if: runner.os != 'Windows'
run: strip target/release/opr8r || true
# Apple code signing + notarization (macOS only)
- name: Codesign opr8r binary
if: matrix.os == 'macos-14'
working-directory: .
env:
APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: scripts/codesign.sh opr8r/target/release/opr8r
- name: Notarize opr8r binary
if: matrix.os == 'macos-14'
working-directory: .
env:
APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
run: scripts/notarize.sh opr8r/target/release/opr8r
- name: Rename binary
shell: bash
run: |
if [[ "${{ matrix.target }}" == *"windows"* ]]; then
cp target/release/opr8r.exe ../${{ matrix.artifact_name }}
else
cp target/release/opr8r ../${{ matrix.artifact_name }}
fi
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.artifact_name }}
path: ${{ matrix.artifact_name }}
sign-windows:
needs: [build, build-opr8r]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
strategy:
matrix:
artifact:
- operator-windows-x86_64.exe
- operator-windows-arm64.exe
- opr8r-windows-x86_64.exe
- opr8r-windows-arm64.exe
uses: ./.github/workflows/sign-windows.yaml
with:
artifact-name: ${{ matrix.artifact }}
secrets:
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
AZ_PIPELINES_PAT: ${{ secrets.AZ_PIPELINES_PAT }}
AZ_ORG: ${{ secrets.AZ_ORG }}
AZ_PROJECT: ${{ secrets.AZ_PROJECT }}
AZ_PIPELINE_DEFINITION_ID: ${{ secrets.AZ_PIPELINE_DEFINITION_ID }}
release:
needs: [version, build, build-opr8r, sign-windows]
runs-on: ubuntu-latest
outputs:
version: ${{ needs.version.outputs.version }}
commit: ${{ steps.version_commit.outputs.commit }}
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Download all artifacts
uses: actions/download-artifact@v8
with:
path: artifacts
# Version was computed in the `version` job and already baked into the
# built binaries; one script revs every tracked manifest and verifies each
# one landed, so a bad rewrite fails here instead of half-tagging a release.
# The script can be run locally for manual version revs.
- name: Bump versioned manifests
env:
VERSION: ${{ needs.version.outputs.version }}
run: ./scripts/ci/bump-version.sh "$VERSION"
# openapi.json's version is code-derived (env!("CARGO_PKG_VERSION")); the
# already-built linux binary embeds the new version, so regenerate the
# committed spec from it instead of recompiling.
- name: Regenerate OpenAPI spec
run: |
BIN=$(find artifacts -type f -name 'operator-linux-x86_64' | head -1)
chmod +x "$BIN"
"$BIN" docs --only openapi
- name: Commit version bump
id: version_commit
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add VERSION Cargo.toml Cargo.lock docs/_config.yml \
charts/operator/Chart.yaml \
vscode-extension/package.json \
vscode-extension/src/webhook-server.ts \
opr8r/Cargo.toml opr8r/Cargo.lock \
zed-extension/Cargo.toml zed-extension/extension.toml zed-extension/Cargo.lock \
agnt-plugin/package.json agnt-plugin/manifest.json \
coder-module/main.tf \
docs/schemas/openapi.json
git commit -m "chore: bump version to v${{ needs.version.outputs.version }} [skip ci]"
git push
echo "commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Create tag
run: |
git tag "v${{ needs.version.outputs.version }}"
git push origin "v${{ needs.version.outputs.version }}"
- name: Prepare release assets
run: |
mkdir -p release-assets
# Copy operator binaries
find artifacts -type f -name 'operator-*' -exec cp {} release-assets/ \;
# Copy opr8r binaries
find artifacts -type f -name 'opr8r-*' -exec cp {} release-assets/ \;
chmod +x release-assets/*
ls -la release-assets/
- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "v${{ needs.version.outputs.version }}" \
--title "v${{ needs.version.outputs.version }}" \
--generate-notes \
release-assets/*
- name: Generate checksums.yml
run: |
OP_MACOS=$(sha256sum release-assets/operator-macos-arm64 | cut -d' ' -f1)
OP_LINUX_ARM=$(sha256sum release-assets/operator-linux-arm64 | cut -d' ' -f1)
OP_LINUX_X86=$(sha256sum release-assets/operator-linux-x86_64 | cut -d' ' -f1)
OP_WIN=$(sha256sum release-assets/operator-windows-x86_64.exe | cut -d' ' -f1)
OP_WIN_ARM=$(sha256sum release-assets/operator-windows-arm64.exe | cut -d' ' -f1)
cat > docs/_data/checksums.yml << EOF
# SHA256 checksums - AUTO-GENERATED BY CI
# Do not edit manually - regenerated on each release
operator:
macos_arm64: "${OP_MACOS}"
linux_arm64: "${OP_LINUX_ARM}"
linux_x86_64: "${OP_LINUX_X86}"
windows_x86_64: "${OP_WIN}"
windows_arm64: "${OP_WIN_ARM}"
EOF
- name: Commit checksums
run: |
git add docs/_data/checksums.yml
git commit -m "chore: update checksums for v${{ needs.version.outputs.version }} [skip ci]" || true
git push
docker:
needs: [build, build-opr8r, release]
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-latest
operator_artifact: operator-linux-x86_64
opr8r_artifact: opr8r-linux-x86_64
- arch: arm64
runner: ubuntu-24.04-arm
operator_artifact: operator-linux-arm64
opr8r_artifact: opr8r-linux-arm64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.release.outputs.commit }}
- name: Download operator binary
uses: actions/download-artifact@v8
with:
name: ${{ matrix.operator_artifact }}
path: bins
- name: Download opr8r binary
uses: actions/download-artifact@v8
with:
name: ${{ matrix.opr8r_artifact }}
path: bins
- name: Stage binaries for build context
run: |
cp "bins/${{ matrix.operator_artifact }}" "operator-linux-${{ matrix.arch }}"
cp "bins/${{ matrix.opr8r_artifact }}" "opr8r-linux-${{ matrix.arch }}"
chmod +x "operator-linux-${{ matrix.arch }}" "opr8r-linux-${{ matrix.arch }}"
- uses: docker/setup-buildx-action@v4
- name: Build image for scanning
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/${{ matrix.arch }}
load: true
tags: operator-ci:${{ needs.release.outputs.version }}-${{ matrix.arch }}
- name: Scan container image
uses: aquasecurity/trivy-action@v0.36.0
with:
image-ref: operator-ci:${{ needs.release.outputs.version }}-${{ matrix.arch }}
scanners: vuln
severity: HIGH,CRITICAL
ignore-unfixed: true
format: sarif
output: trivy-${{ matrix.arch }}.sarif
exit-code: '1'
- name: Upload scan report
if: always()
uses: actions/upload-artifact@v7
with:
name: trivy-${{ matrix.arch }}
path: trivy-${{ matrix.arch }}.sarif
if-no-files-found: ignore
retention-days: 14
- name: Export scanned image
run: docker save "operator-ci:${{ needs.release.outputs.version }}-${{ matrix.arch }}" -o "operator-${{ matrix.arch }}.tar"
- name: Upload scanned image
uses: actions/upload-artifact@v7
with:
name: operator-image-${{ matrix.arch }}
path: operator-${{ matrix.arch }}.tar
compression-level: 0
retention-days: 1
- name: Report scan result
if: always()
run: echo "### linux/${{ matrix.arch }} blocking Trivy scan — ${{ job.status }}" >> "$GITHUB_STEP_SUMMARY"
docker-publish:
needs: [release, docker]
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.manifest.outputs.digest }}
steps:
- uses: actions/download-artifact@v8
with:
pattern: operator-image-*
path: images
merge-multiple: true
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Push scanned platform images
id: platforms
run: |
for arch in amd64 arm64; do
docker load -i "images/operator-${arch}.tar"
source="operator-ci:${{ needs.release.outputs.version }}-${arch}"
candidate="untra/operator:${{ needs.release.outputs.version }}-${arch}-${{ github.run_id }}"
docker tag "$source" "$candidate"
docker push "$candidate"
digest=$(docker buildx imagetools inspect "$candidate" --format '{{json .Manifest.Digest}}' | tr -d '"')
echo "${arch}=untra/operator@${digest}" >> "$GITHUB_OUTPUT"
done
- name: Publish multi-platform manifests
id: manifest
run: |
version="${{ needs.release.outputs.version }}"
amd64="${{ steps.platforms.outputs.amd64 }}"
arm64="${{ steps.platforms.outputs.arm64 }}"
docker buildx imagetools create -t "untra/operator:${version}" -t untra/operator:latest "$amd64" "$arm64"
digest=$(docker buildx imagetools inspect "untra/operator:${version}" --format '{{json .Manifest.Digest}}' | tr -d '"')
echo "digest=$digest" >> "$GITHUB_OUTPUT"
docker buildx imagetools inspect "untra/operator:${version}"
docker buildx imagetools inspect "untra/operator:${version}" --raw \
| jq -e '[.manifests[].platform | select(.os == "linux") | .architecture] | sort == ["amd64", "arm64"]'
echo "### Published untra/operator:${version} at ${digest}" >> "$GITHUB_STEP_SUMMARY"
chart:
needs: [release, docker-publish]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.release.outputs.commit }}
- name: Set up Helm
uses: azure/setup-helm@v5.0.1
- name: Log in to GHCR
run: echo "${{ github.token }}" | helm registry login ghcr.io --username "${{ github.actor }}" --password-stdin
- name: Package and push Helm chart
run: |
helm package charts/operator --destination dist
helm push "dist/operator-${{ needs.release.outputs.version }}.tgz" oci://ghcr.io/untra/charts
- name: Verify public chart
run: |
mkdir -p /tmp/helm-public
export HELM_REGISTRY_CONFIG=/tmp/helm-public/config.json
version="${{ needs.release.outputs.version }}"
helm pull oci://ghcr.io/untra/charts/operator --version "$version" --destination /tmp/helm-public
chart_version=$(helm show chart "/tmp/helm-public/operator-${version}.tgz" | awk '/^version:/ {print $2}')
image=$(helm template verify "/tmp/helm-public/operator-${version}.tgz" | awk '/image: "untra\/operator:/ {print $2; exit}' | tr -d '"')
test "$chart_version" = "$version"
test "$image" = "untra/operator:$version"
echo "### Published and anonymously verified operator chart ${version}" >> "$GITHUB_STEP_SUMMARY"
deploy-docs:
needs: [release, chart]
runs-on: ubuntu-latest
steps:
- name: Trigger docs workflow
uses: actions/github-script@v9
with:
script: |
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'docs.yml',
ref: 'main'
})