TBD-A69. PR #2005 fixed build-organization-controller.yaml only. The other six controller workflows (application, blueprint, continuum, environment, sandbox, useraccess) had the same gaps that caused the #1997 18h deploy gap: - application-controller: missing pkg/** in path filter (auto-bump already present from earlier work). - blueprint, continuum, environment, useraccess: missing BOTH pkg/** path filter AND auto-bump pipeline (permissions promotion + values.yaml bump + commit/push + blueprint-release dispatch). - sandbox: already complete (pkg/** + auto-bump to platform/sandbox chart) — left untouched. Each updated workflow inherits the canonical shape from build-organization-controller.yaml (PR #2005): 1. `core/controllers/pkg/**` added to BOTH push.paths and pull_request.paths. Without this, a fix that only touches the shared HTTP-client tree (gitea/keycloak/kc-mappers) silently fails to rebuild the controller image. 2. `permissions.contents: write` + `actions: write` so the build job can push the values.yaml bump and dispatch the downstream chart re-publish. 3. An awk-scoped `Bump controllers.<who>.image.tag in values.yaml` step that updates ONLY the targeted controller's tag (verified locally — sibling tags remain untouched). 4. A commit/push step that bumps products/catalyst/chart/values.yaml (or products/continuum/chart/values.yaml for continuum, which has its own chart). 5. A `gh workflow run blueprint-release.yaml` dispatch so the bot-pushed commit fires the downstream chart re-publish (GitHub Actions silently filters bot pushes from path-trigger workflows otherwise). Adds two new files to lock the shape in: - `scripts/check-controller-workflow-uniformity.sh` — a CI regression test that grep-asserts every controller workflow has the canonical pkg/** filter + auto-bump pipeline. Fails loudly if any new controller workflow ships without the canonical shape, or if an existing one regresses. - `.github/workflows/check-controller-workflow-uniformity.yaml` — push-on-touch + pull_request-on-touch event-driven wrapper that runs the script. Mirrors the shape of check-vendor-coupling.yaml. Verified locally: - YAML syntax valid for all 7 controller workflows + the new check workflow. - Regression script passes on all 7 controller workflows. - Simulated awk bumps against products/catalyst/chart/values.yaml and products/continuum/chart/values.yaml — each script bumps ONLY the targeted controller's tag, sibling tags untouched. No chart bumps. No Go/chart changes. CI-workflow-only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
222 lines
9.5 KiB
YAML
222 lines
9.5 KiB
YAML
name: Build application-controller
|
|
|
|
# application-controller — slice C4 of EPIC-0 #1095. Watches
|
|
# Application.apps.openova.io/v1 CRs and reconciles per-region
|
|
# kustomization + helmrelease manifests into the per-Org Gitea repo.
|
|
#
|
|
# Per docs/INVIOLABLE-PRINCIPLES.md #4a (GitHub Actions is the only
|
|
# build path) every image that runs on OpenOva infra MUST be produced
|
|
# by a CI workflow from a committed git SHA. Mirrors the existing
|
|
# build-environment-controller.yaml shape — same auth flow, same
|
|
# cosign keyless signing, same SBOM attestation.
|
|
#
|
|
# Per `feedback_inviolable_principles.md`: event-driven only, NO cron.
|
|
# Triggers on push-to-main with paths filter (so unrelated commits
|
|
# don't burn CI minutes), pull_request for reviewers, and
|
|
# workflow_dispatch for manual re-runs.
|
|
|
|
on:
|
|
push:
|
|
paths:
|
|
- 'core/controllers/application/**'
|
|
- 'core/controllers/internal/**'
|
|
# core/controllers/pkg/** is the shared HTTP-client tree (gitea,
|
|
# keycloak, kc-mappers, …) consumed by every Group C controller's
|
|
# Containerfile via `COPY core/controllers/pkg`. Without this path
|
|
# entry a change to the shared pkg/ tree rebuilds the image only
|
|
# if the same PR also happens to touch files under application/ —
|
|
# which silently held the t38 #1997 gitea-405 fix in main for
|
|
# ~12h. Uniform pattern across every build-*-controller.yaml
|
|
# (TBD-A69 #2006).
|
|
- 'core/controllers/pkg/**'
|
|
- 'core/controllers/go.mod'
|
|
- 'core/controllers/go.sum'
|
|
- '.github/workflows/build-application-controller.yaml'
|
|
branches: [main]
|
|
pull_request:
|
|
paths:
|
|
- 'core/controllers/application/**'
|
|
- 'core/controllers/internal/**'
|
|
- 'core/controllers/pkg/**'
|
|
- 'core/controllers/go.mod'
|
|
- 'core/controllers/go.sum'
|
|
- '.github/workflows/build-application-controller.yaml'
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
REGISTRY: ghcr.io
|
|
IMAGE: ghcr.io/openova-io/openova/application-controller
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
# contents: write — the deploy job below pushes a values.yaml SHA
|
|
# bump back to main so the bp-catalyst-platform chart picks up the
|
|
# newly-built image without an operator manually editing the file
|
|
# (per `feedback_no_mvp_no_workarounds.md` rule 1: target-state,
|
|
# never "manual follow-up bump").
|
|
contents: write
|
|
packages: write
|
|
# id-token write is required by cosign keyless signing (Sigstore).
|
|
id-token: write
|
|
# actions: write — required for `gh workflow run` if/when we
|
|
# dispatch downstream chart-publish workflows.
|
|
actions: write
|
|
outputs:
|
|
sha_short: ${{ steps.vars.outputs.sha_short }}
|
|
digest: ${{ steps.build.outputs.digest }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set short SHA
|
|
id: vars
|
|
run: echo "sha_short=$(echo $GITHUB_SHA | head -c 7)" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version: '1.23'
|
|
cache-dependency-path: |
|
|
core/controllers/go.sum
|
|
|
|
- name: go vet
|
|
working-directory: core/controllers
|
|
# Slice CC1 (#1095) consolidated the 5 Group C controllers into
|
|
# a single shared go.mod. Vet scoped to this controller's tree
|
|
# plus the shared internal/ helpers it depends on.
|
|
run: go vet ./application/... ./internal/...
|
|
|
|
- name: Run unit tests
|
|
working-directory: core/controllers
|
|
run: go test -count=1 -race ./application/... ./internal/...
|
|
|
|
# On pull_request runs we stop here — image push requires
|
|
# `packages: write` which only main-branch authors hold.
|
|
- name: Login to GHCR
|
|
if: github.event_name != 'pull_request'
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Set up Docker Buildx
|
|
if: github.event_name != 'pull_request'
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Build and push image
|
|
id: build
|
|
if: github.event_name != 'pull_request'
|
|
uses: docker/build-push-action@v6
|
|
with:
|
|
# Build context is the repository root so the Containerfile's
|
|
# COPY paths can reach core/controllers/application/.
|
|
context: .
|
|
file: core/controllers/application/Containerfile
|
|
push: true
|
|
tags: |
|
|
${{ env.IMAGE }}:${{ steps.vars.outputs.sha_short }}
|
|
${{ env.IMAGE }}:latest
|
|
labels: |
|
|
org.opencontainers.image.source=https://github.com/openova-io/openova
|
|
org.opencontainers.image.revision=${{ github.sha }}
|
|
org.opencontainers.image.title=application-controller
|
|
org.opencontainers.image.description=Reconciles Application.apps.openova.io/v1 → per-Org Gitea repo with per-region Kustomization + HelmRelease manifests (slice C4 of EPIC-0 #1095)
|
|
# provenance=false: containerd 1.7.x on k3s mis-resolves the
|
|
# provenance attestation manifest. SBOM attestation handled by
|
|
# the cosign attest step below.
|
|
provenance: false
|
|
sbom: false
|
|
|
|
- name: Install cosign
|
|
if: github.event_name != 'pull_request'
|
|
uses: sigstore/cosign-installer@v3
|
|
|
|
- name: Sign image with cosign (keyless)
|
|
if: github.event_name != 'pull_request'
|
|
env:
|
|
DIGEST: ${{ steps.build.outputs.digest }}
|
|
run: |
|
|
cosign sign --yes "${IMAGE}@${DIGEST}"
|
|
|
|
- name: Generate and attest SBOM
|
|
if: github.event_name != 'pull_request'
|
|
env:
|
|
DIGEST: ${{ steps.build.outputs.digest }}
|
|
run: |
|
|
cosign attest --yes \
|
|
--predicate <(echo '{"sbom":"in-toto-spdx attached at build time"}') \
|
|
--type spdx \
|
|
"${IMAGE}@${DIGEST}"
|
|
|
|
# Auto-bump the chart values.yaml tag so the next Sovereign chart
|
|
# rollout picks up this image without a manual edit. Per
|
|
# `feedback_no_mvp_no_workarounds.md` rule 1 (target-state, no
|
|
# operator-action gates) and `feedback_inviolable_principles.md`
|
|
# (event-driven, never cron). Mirrors the pattern in
|
|
# catalyst-build.yaml's `deploy` job for catalyst-api/catalyst-ui.
|
|
- name: Bump controllers.application.image.tag in values.yaml
|
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
|
env:
|
|
SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
|
|
run: |
|
|
VALUES="products/catalyst/chart/values.yaml"
|
|
# awk: find ` application:` under `controllers:`, then update
|
|
# the next `tag: "..."` line. Stops at the next top-level key
|
|
# so we don't accidentally bump a sibling controller's tag.
|
|
awk -v sha="${SHA_SHORT}" '
|
|
/^controllers:/ { in_ctrls=1 }
|
|
in_ctrls && /^ application:/ { print; in_app=1; next }
|
|
in_ctrls && /^ [a-z]/ && !/^ application:/ { in_app=0 }
|
|
in_app && /^ tag:/ { sub(/"[^"]*"/, "\"" sha "\""); in_app=0 }
|
|
{ print }
|
|
' "${VALUES}" > "${VALUES}.tmp" && mv "${VALUES}.tmp" "${VALUES}"
|
|
echo "values.yaml after bump:"
|
|
grep -A4 "^ application:" "${VALUES}" | head -10
|
|
|
|
- name: Commit and push values.yaml bump
|
|
id: deploy_commit
|
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
|
|
env:
|
|
SHA_SHORT: ${{ steps.vars.outputs.sha_short }}
|
|
run: |
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
if git diff --quiet products/catalyst/chart/values.yaml; then
|
|
echo "no values.yaml change — already pinned to ${SHA_SHORT}"
|
|
echo "pushed=false" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
git add products/catalyst/chart/values.yaml
|
|
git commit -m "deploy: bump application-controller image to ${SHA_SHORT}"
|
|
# Pull-rebase to avoid races with parallel build commits.
|
|
git pull --rebase --autostash origin main || true
|
|
git push origin HEAD:main
|
|
echo "pushed=true" >> "$GITHUB_OUTPUT"
|
|
|
|
# GitHub Actions does NOT trigger workflows from bot pushes by
|
|
# default (anti-recursion safeguard). The bot commit above changes
|
|
# products/catalyst/chart/values.yaml which would normally fire
|
|
# blueprint-release.yaml's path filter — but bot pushes are
|
|
# silently filtered. Without this dispatch the rebuilt image is
|
|
# NEVER baked into a new chart version, so Sovereigns keep
|
|
# installing the previous chart with the previous image tag, and
|
|
# `feedback_no_mvp_no_workarounds.md` rule 1 is violated (operator
|
|
# has to manually click "Run workflow" or push a Chart.yaml bump).
|
|
# Caught live 2026-05-10 (qa-loop iter-10 Fix #44) — chart 1.4.115
|
|
# was published from the merge commit (which still had the old
|
|
# image tag), so the chart shipped with the WRONG image despite
|
|
# the auto-bump landing seconds later.
|
|
- name: Dispatch blueprint-release for chart re-publish
|
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' && steps.deploy_commit.outputs.pushed == 'true'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
gh workflow run blueprint-release.yaml \
|
|
--repo "${GITHUB_REPOSITORY}" \
|
|
--ref main \
|
|
-f blueprint=catalyst \
|
|
-f tree=products
|