openova/.github/workflows/build-environment-controller.yaml
hatiyildiz 354ddd5711 chore(ci): add auto-bump-images + pkg/** path filter to all build-*-controller workflows (Closes #2006)
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>
2026-05-20 02:08:14 +02:00

213 lines
9.0 KiB
YAML

name: Build environment-controller
# environment-controller — slice C2 of EPIC-0 #1095. Watches
# Environment.catalyst.openova.io/v1 CRs and reconciles per-vCluster
# Flux GitRepository 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-cert-manager-dynadot-webhook.yaml shape — same auth flow,
# same cosign keyless signing, same SBOM attestation.
on:
push:
paths:
- 'core/controllers/environment/**'
- '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 environment/ —
# 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-environment-controller.yaml'
branches: [main]
pull_request:
paths:
- 'core/controllers/environment/**'
- 'core/controllers/internal/**'
- 'core/controllers/pkg/**'
- 'core/controllers/go.mod'
- 'core/controllers/go.sum'
- '.github/workflows/build-environment-controller.yaml'
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE: ghcr.io/openova-io/openova/environment-controller
jobs:
build:
runs-on: ubuntu-latest
permissions:
# contents: write — the deploy step 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"). Pre-#2006 this workflow shipped
# without auto-bump — same deploy-gap class as #1997.
contents: write
packages: write
# id-token write is required by cosign keyless signing (Sigstore).
id-token: write
# actions: write — required for `gh workflow run` to dispatch the
# downstream blueprint-release chart re-publish workflow.
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 ./environment/... ./internal/...
- name: Run unit tests
working-directory: core/controllers
run: go test -count=1 -race ./environment/... ./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/environment/.
context: .
file: core/controllers/environment/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=environment-controller
org.opencontainers.image.description=Reconciles Environment.catalyst.openova.io/v1 → Gitea + Flux GitRepository (slice C2 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
# build-application-controller.yaml + build-organization-controller.yaml.
# Added as part of TBD-A69 (#2006) — pre-#2006 this workflow shipped
# without auto-bump, so the same deploy-gap class as #1997 was live
# for every environment-controller code fix.
- name: Bump controllers.environment.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 ` environment:` 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 && /^ environment:/ { print; in_env=1; next }
in_ctrls && /^ [a-z]/ && !/^ environment:/ { in_env=0 }
in_env && /^ tag:/ { sub(/"[^"]*"/, "\"" sha "\""); in_env=0 }
{ print }
' "${VALUES}" > "${VALUES}.tmp" && mv "${VALUES}.tmp" "${VALUES}"
echo "values.yaml after bump:"
grep -A4 "^ environment:" "${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 environment-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). 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 (`feedback_no_mvp_no_workarounds.md` rule 1 violation).
- 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