-
Notifications
You must be signed in to change notification settings - Fork 6
ci: release train + coverage ratchet (release slice; split from the omnibus) #1317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
SharedQA
wants to merge
29
commits into
constructorfabric:main
from
SharedQA:claude/modest-blackburn-4cc697
Closed
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
a778599
ci: add enforceable quality gates, release train, and docs map
SharedQA f41be7f
ci: address CodeRabbit review — harden gates, fix injection & masking
SharedQA 0b04854
ci: address CodeRabbit re-review — fork-guard AI job, verify kind, sc…
SharedQA 5a1b1c7
ci(release-train): scope OIDC/attestation permissions to validate-art…
SharedQA 9d92c85
ci(security-gates): fix fetch-depth ternary so nightly sweep is full-…
SharedQA 41f21a9
ci(reviews): document 2-random-reviewer assignment via team code-revi…
SharedQA eb08419
ci: fix newly-introduced quality-gate failures
SharedQA 569b54e
ci(security-gates): waive 3 unfixable/transitive RUSTSEC advisories i…
SharedQA 6ad5664
ci(security-gates): add .trivyignore for pre-existing HIGH misconfigs
SharedQA 44a8c9b
ci: move advisory AI failure-investigation to its own PR
SharedQA 10d1257
ci: rework #1317 to lean on existing tooling (review: @ktursunov)
SharedQA 2b47fbc
ci(helm): render the umbrella against a CI values file, not bare defa…
SharedQA 6dbe72b
ci: slim `make check` to a fast cluster-free loop; drop the extra ins…
SharedQA 60added
ci(release): render the released chart with the values it requires
SharedQA e0b1a3b
ci: trim enforce-quality-gates to branch-protection; drop duplicate :…
SharedQA 933bc3c
docs(ci): spell out who runs enforce-quality-gates.sh and when
SharedQA 1ece8c2
ci: run the backend suite once for coverage; fix stale deploy/env notes
SharedQA a6c8ad4
ci(e2e): drop the separate coverage-e2e job (re-ran the suite, captur…
SharedQA 69c8124
ci(helm): tighten chart-version guard to the formats actually used
SharedQA 2ed56e2
Merge branch 'main' into claude/modest-blackburn-4cc697
SharedQA 5b5f36d
ci: fix helm-validate + docs-gates against repo conventions
7386691
ci(security-gates): waive unfixable airbyte-cdk transitive CVEs in pi…
a34333a
ci(helm-validate): complete CI render values (clickhouse/mariadb/redi…
2e330ab
Merge branch 'main' into claude/modest-blackburn-4cc697
SharedQA 1e77861
ci(helm-validate): fix lint loop exit after helmfile/ removal (#1431)
b8ea741
Merge remote-tracking branch 'origin/main' into fix-1317
SharedQA f586bc9
Merge branch 'main' into claude/modest-blackburn-4cc697
SharedQA d086312
docs: regenerate DOCS_MAP.md after merging main
SharedQA 7dade72
ci: slim #1317 to the release-train + coverage slice (split 4/4)
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,269 @@ | ||
| # Release train — once-per-day release candidate, manual promotion. | ||
| # | ||
| # Model: BUILD ONCE, PROMOTE MANY. Every merge already publishes an immutable | ||
| # umbrella chart (build-images.yml). This workflow never rebuilds anything: | ||
| # it SELECTS a published chart version, validates that exact artifact, and | ||
| # (on manual promotion with approval) tags it as the release. | ||
| # | ||
| # nightly cron → latest published chart becomes the Release Candidate: | ||
| # artifact validation + RC prerelease report | ||
| # workflow_dispatch → same, for an explicit version (hotfix / off-cycle), | ||
| # optionally with promote=true | ||
| # promote=true → gated by GitHub Environment `production` | ||
| # (QA lead + Release Manager required reviewers); | ||
| # on approval: annotated tag v<chart-version> + | ||
| # GitHub Release. The TAG IS THE RECORD of the release, | ||
| # created BY the pipeline — never the trigger for it. | ||
| # | ||
| # The team keeps merging to main while an RC is under test: new merges create | ||
| # new chart versions and do not disturb the pinned RC. "Feature freeze" = | ||
| # freezing WHICH VERSION gets promoted, not freezing the branch. | ||
|
|
||
| name: Release Train | ||
|
|
||
| on: | ||
| schedule: | ||
| - cron: "0 3 * * *" # nightly RC cut | ||
| workflow_dispatch: | ||
| inputs: | ||
| chart_version: | ||
| description: "Umbrella chart version to validate (empty = latest published)" | ||
| required: false | ||
| type: string | ||
| promote: | ||
| description: "Promote to release (requires production environment approval)" | ||
| required: false | ||
| type: boolean | ||
| default: false | ||
|
|
||
| # Least privilege at the top; jobs elevate only the scope they actually need | ||
| # (validate-artifact → OIDC/attestations; rc-report + promote → contents: write). | ||
| permissions: | ||
| contents: read | ||
| packages: read # resolve chart versions from GHCR (resolve job) | ||
|
|
||
| concurrency: | ||
| group: release-train | ||
| cancel-in-progress: false | ||
|
|
||
| env: | ||
| CHART_OCI: oci://ghcr.io/constructorfabric/charts/insight | ||
| CHART_PKG: charts%2Finsight | ||
| KUBECONFORM_VERSION: v0.6.7 | ||
| KUBECONFORM_SHA256: 95f14e87aa28c09d5941f11bd024c1d02fdc0303ccaa23f61cef67bc92619d73 | ||
|
|
||
| jobs: | ||
| resolve: | ||
| name: resolve-rc-version | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| outputs: | ||
| version: ${{ steps.pick.outputs.version }} | ||
| steps: | ||
| - name: Pick chart version (input or latest published) | ||
| id: pick | ||
| # INPUT_VERSION via env (never interpolated into the script) + strict | ||
| # semver validation — blocks shell injection from a dispatch input. | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| INPUT_VERSION: ${{ inputs.chart_version }} | ||
| run: | | ||
| set -euo pipefail | ||
| if [[ -n "${INPUT_VERSION:-}" ]]; then | ||
| if [[ ! "$INPUT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | ||
| echo "::error::invalid chart_version '$INPUT_VERSION' (expected semver X.Y.Z)"; exit 1 | ||
| fi | ||
| VERSION="$INPUT_VERSION" | ||
| else | ||
| VERSION=$(gh api "orgs/constructorfabric/packages/container/${CHART_PKG}/versions?per_page=100" \ | ||
| --jq '.[].metadata.container.tags[]' \ | ||
| | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1) | ||
| fi | ||
| [[ -n "$VERSION" ]] || { echo "::error::could not resolve a chart version"; exit 1; } | ||
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | ||
| echo "Release candidate: insight chart $VERSION" | ||
|
|
||
| validate-artifact: | ||
| name: validate-artifact | ||
| needs: resolve | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 30 | ||
| # OIDC + attestation scopes live only here — the only job that verifies SLSA. | ||
| # (Job-level permissions default every unlisted scope to none, so packages | ||
| # is re-declared for the registry login below.) | ||
| permissions: | ||
| packages: read # helm/docker registry login | ||
| id-token: write # OIDC for SLSA provenance verification | ||
| attestations: read # verify SLSA provenance | ||
| steps: | ||
| - uses: azure/setup-helm@fe7b79cd5ee1e45176fcad797de68ecaf3ca4814 # v4.2.0 | ||
| with: | ||
| version: v3.14.0 | ||
|
|
||
| - name: Login registries | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| echo "$GH_TOKEN" | helm registry login ghcr.io -u "$GITHUB_ACTOR" --password-stdin | ||
| echo "$GH_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin | ||
|
|
||
| - name: Pull the EXACT artifact under release | ||
| run: helm pull "$CHART_OCI" --version "${{ needs.resolve.outputs.version }}" | ||
|
|
||
| - name: Verify SLSA provenance of the chart artifact | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| gh attestation verify "oci://ghcr.io/constructorfabric/charts/insight:${{ needs.resolve.outputs.version }}" \ | ||
| --owner constructorfabric | ||
|
|
||
| - name: Render the released chart with required values | ||
| run: | | ||
| set -euo pipefail | ||
| V="${{ needs.resolve.outputs.version }}" | ||
| # The umbrella fail-fasts without required inputs (infra image tags, | ||
| # DB names, tenant) — by design, so a bare render can't succeed. Supply | ||
| # them here; the SERVICE image tags are pinned to the release version V | ||
| # so the image-existence check below verifies the ACTUAL published | ||
| # images. (PR-time render uses charts/insight/ci/ci-values.yaml.) | ||
| helm template insight "insight-$V.tgz" \ | ||
| --namespace insight \ | ||
| --set global.tenantDefaultId=11111111-1111-1111-1111-111111111111 \ | ||
| --set ingestion.reconcile.tenantId=rc-validate \ | ||
| --set clickhouse.database=insight \ | ||
| --set clickhouse.username=default \ | ||
| --set clickhouse.image.tag=25.3 \ | ||
| --set mariadb.database=identity \ | ||
| --set mariadb.username=identity \ | ||
| --set mariadb.auth.username=identity \ | ||
| --set mariadb.auth.database=identity \ | ||
| --set apiGateway.authDisabled=true \ | ||
| --set apiGateway.image.tag="$V" \ | ||
| --set analyticsApi.image.tag="$V" \ | ||
| --set identity.image.tag="$V" \ | ||
| --set frontend.image.tag="$V" \ | ||
| > rendered.yaml | ||
| echo "rendered $(grep -c '^kind:' rendered.yaml) objects" | ||
|
|
||
| - name: kubeconform rendered manifests | ||
| run: | | ||
| set -euo pipefail | ||
| # Verified download: fetch to disk, check SHA256, then extract & run. | ||
| curl -sSL -o kubeconform.tgz \ | ||
| "https://github.com/yannh/kubeconform/releases/download/${KUBECONFORM_VERSION}/kubeconform-linux-amd64.tar.gz" | ||
| echo "${KUBECONFORM_SHA256} kubeconform.tgz" | sha256sum -c - | ||
| tar xzf kubeconform.tgz kubeconform | ||
| ./kubeconform -strict -ignore-missing-schemas -summary rendered.yaml | ||
|
|
||
| - name: Release artifact integrity — every referenced image must exist in the registry | ||
| run: | | ||
| set -euo pipefail | ||
| # The :latest / floating-tag gate lives in helm-validate.yml (it scans | ||
| # the source values.yaml). Here we do the part nothing else does: | ||
| # verify each image in the RENDERED released artifact is actually | ||
| # published. The render pins service tags to the release version, so a | ||
| # floating tag can't reach this step anyway. | ||
| rc=0 | ||
| mapfile -t images < <(grep -E '^\s+image:\s' rendered.yaml | sed -E 's/.*image:\s*"?([^"]+)"?\s*$/\1/' | sort -u) | ||
| echo "checking ${#images[@]} unique image refs" | ||
| for img in "${images[@]}"; do | ||
| if docker manifest inspect "$img" >/dev/null 2>&1; then | ||
| echo " ✓ $img" | ||
| else | ||
| echo "::error::image referenced by chart does not exist: $img"; rc=1 | ||
| fi | ||
| done | ||
| exit $rc | ||
|
|
||
| rc-report: | ||
| name: rc-report | ||
| needs: [resolve, validate-artifact] | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| # Creates/edits the RC prerelease → needs write (resolve & validate are read-only). | ||
| permissions: | ||
| contents: write | ||
| steps: | ||
| - name: Publish / refresh RC prerelease record | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| V: ${{ needs.resolve.outputs.version }} | ||
| run: | | ||
| set -euo pipefail | ||
| TAG="rc-${V}" | ||
| NOTES=$(cat <<EOF | ||
| ## Release Candidate — insight chart \`${V}\` | ||
|
|
||
| Cut by release-train run [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) on $(date -u +%F). | ||
|
|
||
| Artifact validation (the exact OCI artifact, no rebuild): | ||
| - ✅ SLSA provenance verified | ||
| - ✅ helm template renders with production-shaped values | ||
| - ✅ kubeconform manifest validation | ||
| - ✅ all referenced images exist; no \`:latest\` / floating tags | ||
|
|
||
| Pending environment gates before promotion (see Target Operating Model §4/§9): | ||
| - QA regression pack on QA environment | ||
| - Golden-dataset data regression N−1 → N | ||
| - Latest nightly fresh-install green | ||
|
|
||
| Promote via: Actions → Release Train → Run workflow → chart_version=\`${V}\`, promote=\`true\`. | ||
| EOF | ||
| ) | ||
| if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then | ||
| gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --notes "$NOTES" | ||
| else | ||
| gh release create "$TAG" --repo "$GITHUB_REPOSITORY" \ | ||
| --target "$GITHUB_SHA" --prerelease \ | ||
| --title "RC: insight chart ${V}" --notes "$NOTES" | ||
| fi | ||
|
|
||
| promote: | ||
| name: promote-to-release | ||
| # Manual dispatch only, never the cron — and only after artifact validation. | ||
| if: ${{ github.event_name == 'workflow_dispatch' && inputs.promote }} | ||
| needs: [resolve, validate-artifact] | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 10 | ||
| # THE GATE: required reviewers (QA lead + Release Manager) configured by | ||
| # scripts/ci/enforce-quality-gates.sh. This job PAUSES until both approve. | ||
| # The recorded approval is the signed release checklist. | ||
| environment: production | ||
| # Elevated write scoped to this job only — it creates the tag + release. | ||
| permissions: | ||
| contents: write | ||
| steps: | ||
| - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Create annotated release tag (the immutable release record) | ||
| env: | ||
| V: ${{ needs.resolve.outputs.version }} | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| set -euo pipefail | ||
| # persist-credentials:false → authenticate the push explicitly. | ||
| git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" | ||
| git config user.name "release-train" | ||
| git config user.email "release-train@users.noreply.github.com" | ||
| git tag -a "v${V}" -m "Release: insight umbrella chart ${V} (approved via production environment, run ${{ github.run_id }})" | ||
| git push origin "v${V}" | ||
|
|
||
| - name: Finalize GitHub Release | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| V: ${{ needs.resolve.outputs.version }} | ||
| run: | | ||
| set -euo pipefail | ||
| gh release delete "rc-${V}" --repo "$GITHUB_REPOSITORY" --yes 2>/dev/null || true | ||
| gh release create "v${V}" --repo "$GITHUB_REPOSITORY" \ | ||
| --title "insight ${V}" \ | ||
| --notes "Released umbrella chart \`${V}\` (oci://ghcr.io/constructorfabric/charts/insight:${V}). | ||
|
|
||
| Deploy: clusters are deployed from the private \`infra/insight-gitops\` repo (Makefile-driven, OCI-pinned umbrella chart — see docs/components/deployment/gitops/README.md). Bump its pinned umbrella version to \`${V}\`." | ||
|
|
||
| - name: Promotion summary | ||
| run: | | ||
| echo "## ✅ insight ${{ needs.resolve.outputs.version }} promoted" >> "$GITHUB_STEP_SUMMARY" | ||
| echo "Next: deploy via the private infra/insight-gitops repo (bump the pinned umbrella version)." >> "$GITHUB_STEP_SUMMARY" | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.