Thin-render logic for single/split quartet repos (the-eye, emdash, flake-hub, woodpecker-peek) #19

Open
opened 2026-07-09 19:12:27 +03:00 by oleks · 3 comments
Owner

Child of oleks/ci-scripts#17. The staged generator/render_thin_quartet.py only handles kind: matrix repos — it explicitly skips everything else (if manifest.get("kind") != "matrix": continue). So 4 of the 6 quartet repos have no thin-render implementation.

Actual shapes (from manifests/*.yaml):

Repo kind shape
xonsh-image matrix buildx per-arch + manifest consolidate (handled)
csi-s3 matrix buildx per-arch + manifest consolidate (handled)
the-eye single one native-arch build, no manifest step
emdash.kotkanagrilli.fi single one native-arch build, no manifest step
flake-hub split separate per-arch files, nix run .#publish-*, no docker buildx
woodpecker-peek split separate per-arch files, no docker buildx

Work: implement thin-render for the single and split kinds. These do not use buildx, so they map to the plugin's flow: publish (the same shape generator/render_thin_fleet.py renders for the fleet family) — one thin step per pipeline file, image: git.oleks.space/oleks/ci-fleet-publish:v1, settings: {run: <the repo's existing publish command>, flow: publish, cache: <attic on/off>}, backend_options memory kept raw.

Acceptance:

  • render_thin_quartet.py (or a sibling) renders single and split kinds, not just matrix.
  • For each of the 4 repos, render to a scratch dir and produce a behavior diff vs the repo's current .woodpecker file(s): env/secrets, commands, when/labels, backend_options, per-arch file structure for split. Nothing the current pipeline does may be silently dropped.
  • split repos must keep their per-arch file layout (one thin file per arch), not be collapsed.
  • No pushes to the target repos in this issue — staged renders + diffs only. Actual migration happens after review.

Do not touch s390x repos (no-build, oleks/fleet policy) — the fleet family is 100% s390x and out of scope.

**Child of oleks/ci-scripts#17.** The staged `generator/render_thin_quartet.py` only handles `kind: matrix` repos — it explicitly skips everything else (`if manifest.get("kind") != "matrix": continue`). So 4 of the 6 quartet repos have **no thin-render implementation**. **Actual shapes** (from `manifests/*.yaml`): | Repo | kind | shape | |---|---|---| | xonsh-image | matrix | buildx per-arch + manifest consolidate (handled) | | csi-s3 | matrix | buildx per-arch + manifest consolidate (handled) | | **the-eye** | **single** | one native-arch build, no manifest step | | **emdash.kotkanagrilli.fi** | **single** | one native-arch build, no manifest step | | **flake-hub** | **split** | separate per-arch files, `nix run .#publish-*`, no docker buildx | | **woodpecker-peek** | **split** | separate per-arch files, no docker buildx | **Work:** implement thin-render for the `single` and `split` kinds. These do **not** use buildx, so they map to the plugin's `flow: publish` (the same shape `generator/render_thin_fleet.py` renders for the fleet family) — one thin step per pipeline file, `image: git.oleks.space/oleks/ci-fleet-publish:v1`, `settings: {run: <the repo's existing publish command>, flow: publish, cache: <attic on/off>}`, `backend_options` memory kept raw. **Acceptance:** - [ ] `render_thin_quartet.py` (or a sibling) renders `single` and `split` kinds, not just `matrix`. - [ ] For each of the 4 repos, render to a scratch dir and produce a **behavior diff** vs the repo's current `.woodpecker` file(s): env/secrets, commands, `when`/labels, `backend_options`, per-arch file structure for `split`. Nothing the current pipeline does may be silently dropped. - [ ] `split` repos must keep their per-arch file layout (one thin file per arch), not be collapsed. - [ ] No pushes to the target repos in this issue — staged renders + diffs only. Actual migration happens after review. **Do not** touch s390x repos (no-build, oleks/fleet policy) — the fleet family is 100% s390x and out of scope.
oleks added this to the Modern CI Phase 2+3 milestone 2026-07-09 19:12:27 +03:00
Collaborator

generator/render_thin_quartet.py extended to render single and split kinds, alongside the already-done matrix kind. Commit 38bb59e (main). Rendered all 4 target repos to a scratch dir and diffed against each repo's current .woodpecker/ files — no repo pushes made, per the issue's constraint.

Mapping used (mirrors render_thin_fleet.py's flow: publish shape): one thin step per current step/leg, image: git.oleks.space/oleks/ci-fleet-publish:v1, settings: {run: <original commands, minus the arch-banner line>, flow: publish, cache: <"true" if ATTIC_TOKEN is used, else "false">}. backend_options (nodeSelector/resources/labels) kept byte-identical.

Changes common to all 4 (documented in the script's docstring, not silent):

  • image: swaps from the repo-specific nix-ci:latest[-arm64] tag to the plugin's single multi-arch ci-fleet-publish:v1 tag — same convention render_thin_fleet.py already established for the fleet family.
  • The manual echo "▸ arch=$(uname -m)" command is dropped from run:entrypoint.sh already prints that banner unconditionally before any flow runs, so keeping it would double it.
  • The explicit clone: override (PLUGIN_TAGS: "false", PLUGIN_DEPTH: "1") is dropped, relying on Woodpecker's implicit clone — same precedent already set for the fleet family. All 4 repos use the plain shallow/no-tags shape (none hit the tags_full gap render_thin_fleet.py flagged as unresolved), so this isn't a new risk class.
  • No env vars, secrets, when:/branch triggers, or backend_options were dropped or altered — diffs below are run:/commands: restructuring only.

Per-repo diffs (full diffs available on request, this is the substantive part of each):

the-eye (container.yaml, 2 steps: build-and-push, build-and-push-grpc) — commands: [echo, ./ci/local.sh --arch arm64]settings: {run: "./ci/local.sh --arch arm64", flow: publish, cache: "false"} (same for the --component grpc step). REGISTRY_TOKEN/BUILDKIT_ADDR/PUBLISH/BRANCH/VERSION env vars, backend_options (nodeSelector arm64, memory 4Gi/4Gi, commit-branch/commit-sha/pipeline-number labels), and when: push branch:[develop,staging,production] all preserved exactly. Note: release.yaml (goreleaser on tag) and test.yaml (go/js tests on push) are NOT manifest-driven (not in manifests/the-eye.yaml) — out of scope, untouched by this render, still hand-maintained.

emdash.kotkanagrilli.fi (container.yaml, 1 step) — identical shape to the-eye's build-and-push, no grpc step (matches its manifest's single-component components: list).

flake-hub (amd64.yaml, arm64.yaml, kept as separate per-arch files — not collapsed) — commands: [echo, sh ci/setup.sh, PUBLISH=1 nix run .#publish[-aarch64-linux]]settings: {run: "sh ci/setup.sh\n...\nPUBLISH=1 nix run .#publish[-aarch64-linux]", flow: publish, cache: "true"} (no -- --publish flag, matching manifest's publish_flag: false for both legs). ATTIC_TOKEN/GITEA_CLONE_TOKEN env vars, backend_options (nodeSelector, commit-tag/commit-branch/pipeline-number labels, no resources block — matches source, which never had one), and when: push branch:main only (no tag trigger, matching manifest's when_tag_event: false) all preserved exactly.

woodpecker-peek (amd64.yaml, arm64.yaml, kept separate) — same shape as flake-hub, but WITH the -- --publish flag (manifest doesn't set publish_flag, defaults true) and WITH a bare - event: tag trigger alongside push branch:main (manifest doesn't set when_tag_event, defaults true). Both correctly differentiated from flake-hub by reading the manifest's publish_flag/when_tag_event per-leg rather than hardcoding.

Verified: all 6 rendered files (4 target + xonsh-image/csi-s3 matrix, unaffected) parse as valid YAML, and output is byte-identical before/after the repo's ruff pre-commit auto-format pass.

Not closing this issue — migration/review of the actual repo pushes is a separate step per the acceptance criteria.

`generator/render_thin_quartet.py` extended to render `single` and `split` kinds, alongside the already-done `matrix` kind. Commit `38bb59e` (main). Rendered all 4 target repos to a scratch dir and diffed against each repo's current `.woodpecker/` files — no repo pushes made, per the issue's constraint. **Mapping used (mirrors `render_thin_fleet.py`'s `flow: publish` shape):** one thin step per current step/leg, `image: git.oleks.space/oleks/ci-fleet-publish:v1`, `settings: {run: <original commands, minus the arch-banner line>, flow: publish, cache: <"true" if ATTIC_TOKEN is used, else "false">}`. `backend_options` (nodeSelector/resources/labels) kept byte-identical. **Changes common to all 4 (documented in the script's docstring, not silent):** - `image:` swaps from the repo-specific `nix-ci:latest[-arm64]` tag to the plugin's single multi-arch `ci-fleet-publish:v1` tag — same convention `render_thin_fleet.py` already established for the fleet family. - The manual `echo "▸ arch=$(uname -m)"` command is dropped from `run:` — `entrypoint.sh` already prints that banner unconditionally before any flow runs, so keeping it would double it. - The explicit `clone:` override (`PLUGIN_TAGS: "false"`, `PLUGIN_DEPTH: "1"`) is dropped, relying on Woodpecker's implicit clone — same precedent already set for the fleet family. All 4 repos use the plain shallow/no-tags shape (none hit the `tags_full` gap `render_thin_fleet.py` flagged as unresolved), so this isn't a new risk class. - No env vars, secrets, `when:`/branch triggers, or `backend_options` were dropped or altered — diffs below are `run:`/`commands:` restructuring only. **Per-repo diffs** (full diffs available on request, this is the substantive part of each): **the-eye** (`container.yaml`, 2 steps: `build-and-push`, `build-and-push-grpc`) — `commands: [echo, ./ci/local.sh --arch arm64]` → `settings: {run: "./ci/local.sh --arch arm64", flow: publish, cache: "false"}` (same for the `--component grpc` step). `REGISTRY_TOKEN`/`BUILDKIT_ADDR`/`PUBLISH`/`BRANCH`/`VERSION` env vars, `backend_options` (nodeSelector arm64, memory 4Gi/4Gi, commit-branch/commit-sha/pipeline-number labels), and `when: push branch:[develop,staging,production]` all preserved exactly. **Note:** `release.yaml` (goreleaser on tag) and `test.yaml` (go/js tests on push) are NOT manifest-driven (not in `manifests/the-eye.yaml`) — out of scope, untouched by this render, still hand-maintained. **emdash.kotkanagrilli.fi** (`container.yaml`, 1 step) — identical shape to the-eye's `build-and-push`, no grpc step (matches its manifest's single-component `components:` list). **flake-hub** (`amd64.yaml`, `arm64.yaml`, kept as separate per-arch files — not collapsed) — `commands: [echo, sh ci/setup.sh, PUBLISH=1 nix run .#publish[-aarch64-linux]]` → `settings: {run: "sh ci/setup.sh\n...\nPUBLISH=1 nix run .#publish[-aarch64-linux]", flow: publish, cache: "true"}` (no `-- --publish` flag, matching manifest's `publish_flag: false` for both legs). `ATTIC_TOKEN`/`GITEA_CLONE_TOKEN` env vars, `backend_options` (nodeSelector, commit-tag/commit-branch/pipeline-number labels, no resources block — matches source, which never had one), and `when: push branch:main` only (no tag trigger, matching manifest's `when_tag_event: false`) all preserved exactly. **woodpecker-peek** (`amd64.yaml`, `arm64.yaml`, kept separate) — same shape as flake-hub, but WITH the `-- --publish` flag (manifest doesn't set `publish_flag`, defaults true) and WITH a bare `- event: tag` trigger alongside `push branch:main` (manifest doesn't set `when_tag_event`, defaults true). Both correctly differentiated from flake-hub by reading the manifest's `publish_flag`/`when_tag_event` per-leg rather than hardcoding. Verified: all 6 rendered files (4 target + xonsh-image/csi-s3 matrix, unaffected) parse as valid YAML, and output is byte-identical before/after the repo's ruff pre-commit auto-format pass. Not closing this issue — migration/review of the actual repo pushes is a separate step per the acceptance criteria.
Collaborator

Follow-up per team-lead's request: verified the clone: override drop with evidence, not precedent, for each of the 4 repos.

Woodpecker's actual implicit-clone defaults (from woodpecker-ci/plugin-git docs): depth defaults to none/partial-clone (fetches full commit graph, blobs filtered — a superset of a depth: 1 shallow clone, not a subset), tags defaults to false, except on a tag-triggered run, where tags ARE fetched. The explicit override all 4 repos currently carry is PLUGIN_TAGS: "false" / PLUGIN_DEPTH: "1" unconditionally.

Do any of the 4 need git tags or full history? Grepped the actual build/publish targets:

  • the-eye / emdash (ci/local.sh): no git describe/git tag/git rev-list anywhere. The one git call is git rev-parse --abbrev-ref HEAD as a fallback for $BRANCH — dead code in CI, since BRANCH: "${CI_COMMIT_BRANCH}" is always set explicitly as an env var, so the fallback branch never executes. VERSION comes from $CI_PIPELINE_NUMBER, not git. No git-history dependency at all.
  • flake-hub / woodpecker-peek (nix run .#publish[-<arch>]): traced through to parity-lib's mkAtticClosurePublish (lib/builders.nix:1047) — resolves $ATTIC_TOKEN, does nix build on a fixed list of local package names, attic push. No self.rev, self.shortRev, git describe, or any git-metadata read anywhere in either flake.nix or builders.nix. woodpecker-peek's own package version is nixpkgs.lib.fileContents ./VERSION (a checked-in file, not git tags). No git-tag/history dependency in either repo's publish path.

Verdict per repo, with the superset/subset test you asked for:

  • the-eye, emdash, flake-hub (push-triggered only, never tag): implicit clone gives depth=none (superset of depth=1) and tags=false (identical — these never hit a tag event, so the "tags on tag-event" implicit behavior never activates). Strictly equivalent. Safe to drop clone:.
  • woodpecker-peek (push and tag-triggered — flagged as the one to watch): on its tag-triggered runs, implicit clone WILL now fetch tags, where the explicit override currently suppresses that even on a tag event. This is a real (not just theoretical) behavior delta — but per the grep above, nothing in its publish path reads tags, so the extra tag refs are inert data, not a missing dependency (superset, not subset — the failure mode you were checking for would be the reverse). Functionally safe; not byte-identical clone state. Flagging this explicitly rather than asserting full equivalence.

Field-coverage check (item 2): grepped manifests/*.yaml for explicit_clone/timeout — only manifests/csi-s3.yaml carries either (explicit_clone: true, timeout: 240). None of the 4 single/split manifests in my scope (the-eye, emdash.kotkanagrilli.fi, flake-hub, woodpecker-peek) have these fields, and none of their current committed .woodpecker/*.yaml have a timeout: key either. The generator defect audit-worker found on #20 is a matrix-kind-only gap (csi-s3) — it doesn't touch any of the 4 repos in this issue's scope.

No code changes made as a result of this check — the evidence supports the drop as-is for all 4. Flagging one open question for team-lead: does the woodpecker-peek tag-event tag-fetch widening (inert but not identical) warrant pinning clone: back explicitly for that one repo for full parity, or is "superset, no consumer" sufficient? I'm holding off editing render_thin_quartet.py either way since it currently has live uncommitted changes in the shared working tree from audit-worker's #20 fix — will coordinate before touching it.

Still not closing #19.

Follow-up per team-lead's request: verified the `clone:` override drop with evidence, not precedent, for each of the 4 repos. **Woodpecker's actual implicit-clone defaults** (from `woodpecker-ci/plugin-git` docs): `depth` defaults to none/partial-clone (fetches full commit graph, blobs filtered — a *superset* of a `depth: 1` shallow clone, not a subset), `tags` defaults to **false, except on a tag-triggered run, where tags ARE fetched**. The explicit override all 4 repos currently carry is `PLUGIN_TAGS: "false"` / `PLUGIN_DEPTH: "1"` unconditionally. **Do any of the 4 need git tags or full history?** Grepped the actual build/publish targets: - **the-eye / emdash** (`ci/local.sh`): no `git describe`/`git tag`/`git rev-list` anywhere. The one `git` call is `git rev-parse --abbrev-ref HEAD` as a **fallback** for `$BRANCH` — dead code in CI, since `BRANCH: "${CI_COMMIT_BRANCH}"` is always set explicitly as an env var, so the fallback branch never executes. `VERSION` comes from `$CI_PIPELINE_NUMBER`, not git. **No git-history dependency at all.** - **flake-hub / woodpecker-peek** (`nix run .#publish[-<arch>]`): traced through to `parity-lib`'s `mkAtticClosurePublish` (`lib/builders.nix:1047`) — resolves `$ATTIC_TOKEN`, does `nix build` on a fixed list of local package names, `attic push`. No `self.rev`, `self.shortRev`, `git describe`, or any git-metadata read anywhere in either flake.nix or builders.nix. woodpecker-peek's own package version is `nixpkgs.lib.fileContents ./VERSION` (a checked-in file, not git tags). **No git-tag/history dependency in either repo's publish path.** **Verdict per repo, with the superset/subset test you asked for:** - **the-eye, emdash, flake-hub** (push-triggered only, never tag): implicit clone gives depth=none (superset of depth=1) and tags=false (identical — these never hit a tag event, so the "tags on tag-event" implicit behavior never activates). **Strictly equivalent. Safe to drop `clone:`.** - **woodpecker-peek** (push **and** tag-triggered — flagged as the one to watch): on its tag-triggered runs, implicit clone WILL now fetch tags, where the explicit override currently suppresses that even on a tag event. This is a real (not just theoretical) behavior delta — but per the grep above, nothing in its publish path reads tags, so the extra tag refs are inert data, not a missing dependency (superset, not subset — the failure mode you were checking for would be the reverse). Functionally safe; not byte-identical clone state. Flagging this explicitly rather than asserting full equivalence. **Field-coverage check (item 2):** grepped `manifests/*.yaml` for `explicit_clone`/`timeout` — only `manifests/csi-s3.yaml` carries either (`explicit_clone: true`, `timeout: 240`). None of the 4 single/split manifests in my scope (the-eye, emdash.kotkanagrilli.fi, flake-hub, woodpecker-peek) have these fields, and none of their current committed `.woodpecker/*.yaml` have a `timeout:` key either. **The generator defect audit-worker found on #20 is a matrix-kind-only gap (csi-s3) — it doesn't touch any of the 4 repos in this issue's scope.** No code changes made as a result of this check — the evidence supports the drop as-is for all 4. Flagging one open question for team-lead: does the woodpecker-peek tag-event tag-fetch widening (inert but not identical) warrant pinning `clone:` back explicitly for that one repo for full parity, or is "superset, no consumer" sufficient? I'm holding off editing `render_thin_quartet.py` either way since it currently has live uncommitted changes in the shared working tree from audit-worker's #20 fix — will coordinate before touching it. Still not closing #19.
Collaborator

Decision recorded (team-lead, following the evidence in the previous comment): leave woodpecker-peek's clone: drop as-is — no explicit pin.

Reasoning: the tag-event tag-fetch widening is a superset (more refs available on a tag-triggered run) with no consumer in the publish path (confirmed: parity-lib's mkAtticClosurePublish never reads git tags), so it cannot break the build — and if a future change ever starts reading tags, it would find them present, not missing, which is the safe failure direction. Also noted: forcing PLUGIN_TAGS: "false" on a tag-triggered run was the stranger state to begin with; dropping it moves woodpecker-peek toward Woodpecker's own default semantics rather than away from them.

This becomes a watch item on woodpecker-peek's first canary tag run — we verify a real green tag-triggered build before rolling it out further. No code change needed; clone: stays dropped for all 4 repos as rendered.

Decision recorded (team-lead, following the evidence in the previous comment): **leave woodpecker-peek's `clone:` drop as-is — no explicit pin.** Reasoning: the tag-event tag-fetch widening is a superset (more refs available on a tag-triggered run) with no consumer in the publish path (confirmed: `parity-lib`'s `mkAtticClosurePublish` never reads git tags), so it cannot break the build — and if a future change ever starts reading tags, it would find them present, not missing, which is the safe failure direction. Also noted: forcing `PLUGIN_TAGS: "false"` on a *tag-triggered* run was the stranger state to begin with; dropping it moves woodpecker-peek toward Woodpecker's own default semantics rather than away from them. This becomes a **watch item on woodpecker-peek's first canary tag run** — we verify a real green tag-triggered build before rolling it out further. No code change needed; `clone:` stays dropped for all 4 repos as rendered.
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: oleks/ci-scripts#19