ci: converge pipeline with rendered-vs-deployed diff gate (Phase 2) #8

Open
erwin wants to merge 1 commit from erwin/converge-pipeline into main
First-time contributor

MERGE LAST, EYES OPEN: merging this arms converge-on-push — the merge commit itself triggers the first gated converge of all:!workstations.

Plan-on-PR (fails on removals, diff to ntfy), converge-on-push (diff posted BEFORE apply; removals block unless workflow_dispatch acknowledge_removals=true — the interim for Forgejo 15's missing environments), workflow-level concurrency, post-apply endpoint verification (fail-closed), vault password lifetime = one step (trap).

Prerequisites: milton merged AND converged; secrets ANSIBLE_VAULT_PASSWORD + NTFY_CD_TOKEN (done); branch protection requiring the plan check (Settings → Branches); checkout action resolvable. Supersedes feature/gitops-cd — close it after this merges.

MERGE LAST, EYES OPEN: merging this arms converge-on-push — the merge commit itself triggers the first gated converge of all:!workstations. Plan-on-PR (fails on removals, diff to ntfy), converge-on-push (diff posted BEFORE apply; removals block unless workflow_dispatch acknowledge_removals=true — the interim for Forgejo 15's missing environments), workflow-level concurrency, post-apply endpoint verification (fail-closed), vault password lifetime = one step (trap). Prerequisites: milton merged AND converged; secrets ANSIBLE_VAULT_PASSWORD + NTFY_CD_TOKEN (done); branch protection requiring the plan check (Settings → Branches); checkout action resolvable. Supersedes feature/gitops-cd — close it after this merges.
ci: converge pipeline with rendered-vs-deployed diff gate
Some checks failed
converge / plan (pull_request) Failing after 30s
converge / converge (pull_request) Has been skipped
b5d245eebd
Supersedes the unmerged feature/gitops-cd branch. Its good bones are
kept - the homelab-converge concurrency group (at WORKFLOW level:
Forgejo honours concurrency only there, and without a group the
platform default cancels an in-flight run on a new push - a converge
killed mid-apply is exactly the partially-converged-fleet failure this
pipeline exists to prevent), the all:!workstations scope,
workflow_dispatch with a limit input - but plan/converge no longer run
blind.

Why: on 2026-06-28 a branch-only live deploy of familie.gierz.eu was
silently amputated by a later converge from main and nothing noticed.
Two rules now hold structurally in the workflow:

1. Never deploy ahead of main. push triggers are filtered to main and
   the converge job hard-fails any workflow_dispatch on another ref.
2. No converge applies blind. Both jobs run ansible in check+diff
   mode first, parse the unified-diff hunks out of the output with one
   shared parser (scripts/extract_check_diff.py - hunk line counts
   from the @@ headers, so content lines starting with "-" cannot be
   misread; unparseable output fails closed), and post the diff to
   ntfy topic homelab-cd before anything else happens. On PRs,
   removals of deployed lines fail the check with the removed lines
   printed in the log. On converge, the apply step sits after the ntfy
   post (a failed post aborts the job) and removals stop the run red
   with an urgent notification unless explicitly acknowledged.

Removal acknowledgement is the workflow_dispatch boolean input
acknowledge_removals, derived from the event name so a plain push can
never satisfy it. This is interim: the preferred mechanism, a Forgejo
protected environment with required approval, does not exist yet -
verified 2026-07-08 against code.gierz.xyz (Forgejo 15.0.1) and the
docs, where jobs.<job_id>.environment appears only in the context
table with no protection semantics. Revisit when Forgejo grows
environments.

Guard rails beyond the gate: dispatch --limit always gets
:!workstations appended (gierz-laptop is ansible_connection: local -
a stray "all" would converge the laptop stack on the runner itself);
post-apply endpoint verification runs under set -e so a broken URL
extraction fails the step instead of reporting success; the vault
password is written, used and removed inside each step that needs it
(trap EXIT), with the always() cleanup as backstop; the PR plan's ntfy
post is advisory so an ntfy outage cannot redden a clean check, while
the converge pre-apply post stays a hard gate by design.

Requires (documented in the header): erwin/milton-runner converged
first, repo secrets ANSIBLE_VAULT_PASSWORD + NTFY_CD_TOKEN, checkout
action resolvable, branch protection requiring the plan check.

Signed-off-by: erwin <erwin@gierz.eu>
Co-Authored-By: LLM <noreply@invalid>
Claude-Session: https://claude.ai/code/session_01UVBwAUedQi9YJ6S9UJomMS
Some checks failed
converge / plan (pull_request) Failing after 30s
converge / converge (pull_request) Has been skipped
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin erwin/converge-pipeline:erwin/converge-pipeline
git switch erwin/converge-pipeline

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff erwin/converge-pipeline
git switch erwin/converge-pipeline
git rebase main
git switch main
git merge --ff-only erwin/converge-pipeline
git switch erwin/converge-pipeline
git rebase main
git switch main
git merge --no-ff erwin/converge-pipeline
git switch main
git merge --squash erwin/converge-pipeline
git switch main
git merge --ff-only erwin/converge-pipeline
git switch main
git merge erwin/converge-pipeline
git push origin main
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
homelab/infra!8
No description provided.