Repository navigation
storybook applied, fix failed deploy #263
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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' | |
| }) |