Fix release preflight: use startswith for matrix job name matching #2
Workflow file for this run
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: Release | |
| on: | |
| push: | |
| tags: ["v*"] | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: "Dry run (skip publish)" | |
| type: boolean | |
| default: false | |
| concurrency: | |
| group: release-${{ github.ref }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| packages: write | |
| id-token: write | |
| attestations: write | |
| checks: read | |
| statuses: read | |
| jobs: | |
| preflight: | |
| name: Release Preflight Check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Verify CI passed on this commit | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| SHA="${{ github.sha }}" | |
| echo "Checking CI status for commit ${SHA}..." | |
| # Query check runs for required CI jobs | |
| REQUIRED_CHECKS=( | |
| "Go Build & Test" | |
| "Python Test & Lint" | |
| "Security Regression Tests" | |
| "Dependency Vulnerability Audit" | |
| "Test Count Drift Check" | |
| "Documentation Validation" | |
| ) | |
| PASS=0 | |
| FAIL=0 | |
| for check in "${REQUIRED_CHECKS[@]}"; do | |
| # Use startswith to handle matrix jobs (e.g. "Go Build & Test (registry)") | |
| RESULT=$(gh api "repos/${{ github.repository }}/commits/${SHA}/check-runs" \ | |
| --jq "[.check_runs[] | select(.name | startswith(\"${check}\")) | .conclusion] | if length == 0 then [\"not_found\"] else . end | if all(. == \"success\") then \"success\" else .[0] end" \ | |
| 2>/dev/null || echo "not_found") | |
| if [ "$RESULT" = "success" ]; then | |
| echo "OK: ${check} = ${RESULT}" | |
| PASS=$((PASS + 1)) | |
| else | |
| echo "::warning::${check}: ${RESULT:-not_found}" | |
| FAIL=$((FAIL + 1)) | |
| fi | |
| done | |
| if [ "$FAIL" -gt 0 ]; then | |
| echo "" | |
| echo "::error::${FAIL} required CI check(s) did not show success." | |
| echo "Cannot create a release from a commit with failing or missing CI checks." | |
| echo "Ensure CI is green before tagging a release." | |
| exit 1 | |
| fi | |
| echo "Preflight complete: ${PASS}/${#REQUIRED_CHECKS[@]} checks confirmed passing" | |
| build-go: | |
| name: Build Go Services | |
| needs: [preflight] | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| service: [airlock, registry, tool-firewall, gpu-integrity-watch, mcp-firewall, policy-engine, runtime-attestor, integrity-monitor, incident-recorder] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 | |
| with: | |
| go-version: "1.25" | |
| cache-dependency-path: services/${{ matrix.service }}/go.sum | |
| - name: Build (linux/amd64) | |
| working-directory: services/${{ matrix.service }} | |
| run: | | |
| CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ | |
| go build -ldflags="-s -w -X main.version=${{ github.ref_name }}" \ | |
| -o ../../dist/${{ matrix.service }}-linux-amd64 . | |
| - name: Build (linux/arm64) | |
| working-directory: services/${{ matrix.service }} | |
| run: | | |
| CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \ | |
| go build -ldflags="-s -w -X main.version=${{ github.ref_name }}" \ | |
| -o ../../dist/${{ matrix.service }}-linux-arm64 . | |
| - name: Generate SBOM (Syft) | |
| uses: anchore/sbom-action@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0.23.1 | |
| with: | |
| path: services/${{ matrix.service }} | |
| format: cyclonedx-json | |
| output-file: dist/${{ matrix.service }}-sbom.cdx.json | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@ea165f8d65b6db9a6b7e75b195508afaf57ec3c7 # v4.6.2 | |
| with: | |
| name: go-${{ matrix.service }} | |
| path: dist/ | |
| build-python: | |
| name: Build Python Service SBOMs | |
| needs: [preflight] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Install Syft | |
| run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin | |
| - name: Generate Python SBOMs | |
| run: | | |
| mkdir -p dist | |
| for svc in agent ui quarantine common; do | |
| if [ -d "services/${svc}" ]; then | |
| syft dir:services/${svc} -o cyclonedx-json=dist/${svc}-sbom.cdx.json | |
| fi | |
| done | |
| # Diffusion worker and search mediator | |
| for svc in diffusion-worker search-mediator; do | |
| if [ -d "services/${svc}" ]; then | |
| syft dir:services/${svc} -o cyclonedx-json=dist/${svc}-sbom.cdx.json | |
| fi | |
| done | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@ea165f8d65b6db9a6b7e75b195508afaf57ec3c7 # v4.6.2 | |
| with: | |
| name: python-sboms | |
| path: dist/ | |
| provenance: | |
| name: SLSA Provenance & Attestation | |
| runs-on: ubuntu-latest | |
| needs: [build-go, build-python] | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | |
| with: | |
| path: dist/ | |
| merge-multiple: true | |
| - name: Record release image digest | |
| run: | | |
| IMAGE_REF="ghcr.io/${{ github.repository }}" | |
| TAG="${{ github.ref_name }}" | |
| DIGEST=$(skopeo inspect "docker://${IMAGE_REF}:${TAG}" 2>/dev/null | jq -r '.Digest' || echo "") | |
| if [ -n "$DIGEST" ] && [ "$DIGEST" != "null" ]; then | |
| echo "${DIGEST}" > dist/IMAGE_DIGEST | |
| echo "${IMAGE_REF}@${DIGEST}" > dist/IMAGE_REF_PINNED | |
| echo "## Install with digest pinning" >> "$GITHUB_STEP_SUMMARY" | |
| echo '```bash' >> "$GITHUB_STEP_SUMMARY" | |
| echo "sudo bash secai-bootstrap.sh --digest ${DIGEST}" >> "$GITHUB_STEP_SUMMARY" | |
| echo '```' >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo "WARNING: Could not extract image digest for tag ${TAG}" | |
| echo "unknown" > dist/IMAGE_DIGEST | |
| fi | |
| - name: Generate release manifest | |
| run: | | |
| cd dist | |
| # Collect binary names + SHA256 hashes | |
| BINARIES_JSON="[]" | |
| for bin in *-linux-*; do | |
| [ -f "$bin" ] || continue | |
| HASH=$(sha256sum "$bin" | awk '{print $1}') | |
| BINARIES_JSON=$(echo "$BINARIES_JSON" | jq \ | |
| --arg name "$bin" --arg sha256 "$HASH" \ | |
| '. + [{"name": $name, "sha256": $sha256}]') | |
| done | |
| # Collect SBOM filenames | |
| SBOMS_JSON="[]" | |
| for sbom in *-sbom.cdx.json; do | |
| [ -f "$sbom" ] || continue | |
| SBOMS_JSON=$(echo "$SBOMS_JSON" | jq \ | |
| --arg name "$sbom" \ | |
| '. + [$name]') | |
| done | |
| # Read image digest | |
| IMAGE_DIGEST="unknown" | |
| if [ -f IMAGE_DIGEST ]; then | |
| IMAGE_DIGEST=$(cat IMAGE_DIGEST) | |
| fi | |
| IMAGE_REF_PINNED="" | |
| if [ -f IMAGE_REF_PINNED ]; then | |
| IMAGE_REF_PINNED=$(cat IMAGE_REF_PINNED) | |
| fi | |
| # Build manifest JSON | |
| jq -n \ | |
| --arg schema_version "1" \ | |
| --arg tag "${{ github.ref_name }}" \ | |
| --arg image_ref "ghcr.io/${{ github.repository }}" \ | |
| --arg image_digest "$IMAGE_DIGEST" \ | |
| --arg image_ref_pinned "$IMAGE_REF_PINNED" \ | |
| --argjson binaries "$BINARIES_JSON" \ | |
| --argjson sboms "$SBOMS_JSON" \ | |
| --arg provenance_type "https://slsa.dev/provenance/v1" \ | |
| --arg checksum_file "SHA256SUMS" \ | |
| --arg signature_file "SHA256SUMS.sig" \ | |
| --arg commit_sha "${{ github.sha }}" \ | |
| --arg workflow_run "${{ github.run_id }}" \ | |
| --arg workflow_ref "${{ github.workflow_ref }}" \ | |
| --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ | |
| '{ | |
| schema_version: $schema_version, | |
| tag: $tag, | |
| image: { | |
| ref: $image_ref, | |
| digest: $image_digest, | |
| ref_pinned: $image_ref_pinned | |
| }, | |
| binaries: $binaries, | |
| sboms: $sboms, | |
| provenance: { | |
| type: $provenance_type, | |
| attested: true | |
| }, | |
| checksums: { | |
| file: $checksum_file, | |
| signature: $signature_file | |
| }, | |
| build: { | |
| commit_sha: $commit_sha, | |
| workflow_run: $workflow_run, | |
| workflow_ref: $workflow_ref, | |
| timestamp: $timestamp | |
| } | |
| }' > RELEASE_MANIFEST.json | |
| echo "--- RELEASE_MANIFEST.json ---" | |
| cat RELEASE_MANIFEST.json | |
| - name: Generate SHA256 checksums | |
| run: | | |
| cd dist | |
| sha256sum * > SHA256SUMS | |
| cat SHA256SUMS | |
| - name: Sign checksums with cosign | |
| run: | | |
| cosign sign-blob --yes \ | |
| --key env://COSIGN_PRIVATE_KEY \ | |
| --output-signature dist/SHA256SUMS.sig \ | |
| dist/SHA256SUMS | |
| env: | |
| COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }} | |
| - name: Attest build provenance | |
| uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 | |
| with: | |
| subject-path: "dist/*-linux-*" | |
| - name: Attest SBOMs | |
| run: | | |
| for sbom in dist/*-sbom.cdx.json; do | |
| service=$(basename "$sbom" -sbom.cdx.json) | |
| cosign attest --yes --type cyclonedx \ | |
| --predicate "$sbom" \ | |
| --key env://COSIGN_PRIVATE_KEY \ | |
| ghcr.io/${{ github.repository }}:${{ github.ref_name }}-${service} || \ | |
| echo "WARN: cosign attest skipped for ${service} (no matching image)" | |
| done | |
| env: | |
| COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }} | |
| - name: Create GitHub Release | |
| if: ${{ !inputs.dry_run }} | |
| uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2 | |
| with: | |
| files: | | |
| dist/*-linux-* | |
| dist/*-sbom.cdx.json | |
| dist/SHA256SUMS | |
| dist/SHA256SUMS.sig | |
| dist/IMAGE_DIGEST | |
| dist/IMAGE_REF_PINNED | |
| dist/RELEASE_MANIFEST.json | |
| generate_release_notes: true | |
| fail_on_unmatched_files: false |