feat: reusable kustomize tag update script for CI pipelines #205

Merged
forgejo_admin merged 3 commits from 204-image-tag-automation into main 2026-03-27 20:53:27 +00:00

Summary

After CI pushes a new image to Harbor, the kustomize overlay newTag in pal-e-deployments is never updated -- every deploy requires a manual PR or kubectl set image. Issue #148 was marked done but the automation was never built (false-done). This PR adds a reusable shell script and Woodpecker step reference that each app repo can adopt to close the gap.

Changes

  • scripts/update-kustomize-tag.sh -- Standalone POSIX shell script that:

    • Clones pal-e-deployments via internal Forgejo URL (no hairpin)
    • Updates all newTag entries in the target overlay's kustomization.yaml via sed
    • Handles idempotency (no-op if tag already matches)
    • Commits with [skip ci] prefix to prevent CI loops
    • Pushes to main so ArgoCD can sync
  • scripts/woodpecker-update-tag-step.yaml -- Copy-paste Woodpecker step template with:

    • Complete overlay directory mapping for all 9 services
    • Inline implementation (no external script dependency)
    • Correct depends_on and when guards (only runs on push to main after build-and-push)

Test Plan

  • Verified sed pattern works on all kustomization.yaml variants (bare SHA, quoted tags, latest, multiple newTag entries)
  • Tested with mock kustomization.yaml containing dual images blocks (basketball-api pattern)
  • Manual: after per-repo rollout, push no-op commit to westside-app, verify tag updates in pal-e-deployments
  • Manual: verify ArgoCD syncs the new tag (depends on #203 for source URL fix)

Review Checklist

Prerequisites for cross-repo rollout (child issues needed):

Repo Overlay Has forgejo_token secret? Needs step added?
westside-app westsidekingsandqueens No Yes
basketball-api basketball-api No Yes
pal-e-app pal-e-app Yes (has broken update-deployment-tag targeting wrong repo) Yes (replace existing)
pal-e-docs pal-e-docs Yes Yes
pal-e-mail pal-e-mail No Yes
mcd-tracker-api mcd-tracker No Yes
mcd-tracker-app mcd-tracker-app No Yes
gcal-scheduler gcal-scheduler No Yes
platform-validation platform-validation No Yes

Action items for rollout:

  1. Add forgejo_token as a Woodpecker global secret (or per-repo for repos that lack it)
  2. Add update-kustomize-tag step to each repo's .woodpecker.yaml using the reference template
  3. Remove the broken update-deployment-tag step from pal-e-app (it updates k8s/deployment.yaml in the app repo instead of the kustomize overlay)
  • Closes #204
  • Supersedes #148 (false-done -- automation was never built)
  • Depends on #203 (ArgoCD source URLs) for full end-to-end verification
  • Discovered scope: 9 child issues needed for per-repo .woodpecker.yaml rollout + forgejo_token secret provisioning
## Summary After CI pushes a new image to Harbor, the kustomize overlay `newTag` in `pal-e-deployments` is never updated -- every deploy requires a manual PR or `kubectl set image`. Issue #148 was marked done but the automation was never built (false-done). This PR adds a reusable shell script and Woodpecker step reference that each app repo can adopt to close the gap. ## Changes - **`scripts/update-kustomize-tag.sh`** -- Standalone POSIX shell script that: - Clones `pal-e-deployments` via internal Forgejo URL (no hairpin) - Updates all `newTag` entries in the target overlay's `kustomization.yaml` via sed - Handles idempotency (no-op if tag already matches) - Commits with `[skip ci]` prefix to prevent CI loops - Pushes to main so ArgoCD can sync - **`scripts/woodpecker-update-tag-step.yaml`** -- Copy-paste Woodpecker step template with: - Complete overlay directory mapping for all 9 services - Inline implementation (no external script dependency) - Correct `depends_on` and `when` guards (only runs on push to main after build-and-push) ## Test Plan - [x] Verified sed pattern works on all kustomization.yaml variants (bare SHA, quoted tags, `latest`, multiple `newTag` entries) - [x] Tested with mock kustomization.yaml containing dual `images` blocks (basketball-api pattern) - [ ] Manual: after per-repo rollout, push no-op commit to westside-app, verify tag updates in pal-e-deployments - [ ] Manual: verify ArgoCD syncs the new tag (depends on #203 for source URL fix) ## Review Checklist **Prerequisites for cross-repo rollout (child issues needed):** | Repo | Overlay | Has `forgejo_token` secret? | Needs step added? | |------|---------|----------------------------|-------------------| | westside-app | westsidekingsandqueens | No | Yes | | basketball-api | basketball-api | No | Yes | | pal-e-app | pal-e-app | Yes (has broken `update-deployment-tag` targeting wrong repo) | Yes (replace existing) | | pal-e-docs | pal-e-docs | Yes | Yes | | pal-e-mail | pal-e-mail | No | Yes | | mcd-tracker-api | mcd-tracker | No | Yes | | mcd-tracker-app | mcd-tracker-app | No | Yes | | gcal-scheduler | gcal-scheduler | No | Yes | | platform-validation | platform-validation | No | Yes | **Action items for rollout:** 1. Add `forgejo_token` as a Woodpecker global secret (or per-repo for repos that lack it) 2. Add `update-kustomize-tag` step to each repo's `.woodpecker.yaml` using the reference template 3. Remove the broken `update-deployment-tag` step from pal-e-app (it updates `k8s/deployment.yaml` in the app repo instead of the kustomize overlay) ## Related Notes - Closes #204 - Supersedes #148 (false-done -- automation was never built) - Depends on #203 (ArgoCD source URLs) for full end-to-end verification - Discovered scope: 9 child issues needed for per-repo `.woodpecker.yaml` rollout + `forgejo_token` secret provisioning
feat: add reusable kustomize tag update script for CI pipelines
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
86febce621
After CI pushes a new image to Harbor, the kustomize overlay newTag in
pal-e-deployments must be updated so ArgoCD can sync the new image.
This was never automated (#148 was false-done).

Adds:
- scripts/update-kustomize-tag.sh: standalone script that clones
  pal-e-deployments, updates newTag via sed, commits and pushes
- scripts/woodpecker-update-tag-step.yaml: copy-paste Woodpecker step
  reference with overlay mapping for all 9 services

Each app repo needs a child issue to add the step to its .woodpecker.yaml
and ensure forgejo_token secret is available.

Closes #204

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

QA Review

Findings (self-review, QA agent unavailable)

Fixed in follow-up commit:

  1. Race condition on concurrent pushes (severity: high) -- If two repos push images simultaneously, both clone pal-e-deployments at the same commit. The second git push would fail with a non-fast-forward error. Added retry loop (3 attempts) with git pull --rebase + re-apply sed pattern.

  2. Shallow clone too shallow for rebase (severity: medium) -- git clone --depth 1 cannot perform git pull --rebase because there is no common ancestor. Changed to --depth 5 so the retry loop has room to rebase.

Noted, not fixed (no action needed):

  1. alpine/git:latest image tag not pinned (severity: low) -- Using latest is standard practice for this CI step across the repo. Pinning can be done as a platform-wide hardening pass later.

  2. Standalone script vs inline step divergence (severity: info) -- The .sh script uses \\( (shell double-escape) while the YAML inline step uses \( (single-escape). Both are correct for their respective contexts -- shell file needs double-escape, YAML string does not.

VERDICT: APPROVE (after self-review fixes pushed)

## QA Review ### Findings (self-review, QA agent unavailable) **Fixed in follow-up commit:** 1. **Race condition on concurrent pushes (severity: high)** -- If two repos push images simultaneously, both clone pal-e-deployments at the same commit. The second `git push` would fail with a non-fast-forward error. Added retry loop (3 attempts) with `git pull --rebase` + re-apply sed pattern. 2. **Shallow clone too shallow for rebase (severity: medium)** -- `git clone --depth 1` cannot perform `git pull --rebase` because there is no common ancestor. Changed to `--depth 5` so the retry loop has room to rebase. **Noted, not fixed (no action needed):** 3. **`alpine/git:latest` image tag not pinned (severity: low)** -- Using `latest` is standard practice for this CI step across the repo. Pinning can be done as a platform-wide hardening pass later. 4. **Standalone script vs inline step divergence (severity: info)** -- The `.sh` script uses `\\(` (shell double-escape) while the YAML inline step uses `\(` (single-escape). Both are correct for their respective contexts -- shell file needs double-escape, YAML string does not. ### VERDICT: APPROVE (after self-review fixes pushed)
fix: add push retry loop for concurrent pipeline safety
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
7f6b65344d
QA review found a race condition: if two pipelines update
pal-e-deployments simultaneously, the second push fails with
non-fast-forward. Added 3-attempt retry with pull --rebase +
re-apply sed. Also bumped clone depth from 1 to 5 so rebase
has a common ancestor to work with.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

PR #205 Review

DOMAIN REVIEW

Tech stack: POSIX shell scripts + Woodpecker CI YAML (CI/CD automation domain). The PR adds two new files under scripts/ -- a standalone shell script and a Woodpecker step template. Both target kustomize overlays in the pal-e-deployments repo.

File paths are clean. Both files are at scripts/ in the repo root, not nested under a worktree. Good.

Shell script quality (scripts/update-kustomize-tag.sh):

  • POSIX #!/bin/sh with set -eu -- correct for CI containers (Alpine/busybox)
  • Required env var validation via ${VAR:?msg} -- good pattern
  • Idempotency via git diff --quiet -- correct
  • [skip ci] commit prefix to prevent CI loops -- correct
  • Retry logic with rebase + re-apply sed on conflict -- well thought out for concurrent pipeline pushes
  • --depth 5 instead of --depth 1 to support rebase -- correct, with clear comment explaining why

Woodpecker step template (scripts/woodpecker-update-tag-step.yaml):

  • Complete overlay directory mapping for all 9 services -- useful reference
  • Notes westside-contracts has no overlay yet -- good
  • depends_on: build-and-push and when: push to main guards -- correct

Sed pattern analysis against real kustomization.yaml files:

Pattern: s/^\( newTag:\s*\).*$/\1${IMAGE_TAG}/

Validated against all 13 newTag lines across 9 overlay prod kustomization.yaml files:

  • Bare SHA tags (e.g., newTag: 4102552649e4...) -- matches correctly
  • Short SHAs (e.g., newTag: a81d881) -- matches correctly
  • latest (e.g., newTag: latest) -- matches correctly
  • Quoted tags (e.g., newTag: "3ecec95" in platform-validation) -- matches and replaces with unquoted tag. This is a minor format change but functionally equivalent for kustomize. Acceptable.

Multi-image overlay behavior:

Several overlays have 2 newTag entries (basketball-api, pal-e-docs, mcd-tracker, pal-e-mail). The sed replaces ALL newTag lines to the same value. This is documented as intentional in the PR ("update all newTag entries"). Currently all dual entries share the same tag, so this is correct. However, if a future overlay needs different tags per image block, this script would need modification. The PR body's review checklist adequately calls this out as known scope.

BLOCKERS

1. DRY violation: full logic duplication between .sh script and .yaml template

scripts/update-kustomize-tag.sh (98 lines) contains the complete implementation. scripts/woodpecker-update-tag-step.yaml (79 lines) re-implements the same logic inline under commands: rather than calling the shell script. The YAML template comment says "Inline implementation (no external script dependency)" -- but this creates two copies of the same logic that will diverge over time.

This is a DRY violation in a CI/security-adjacent path (pushing commits with a token to a deployment repo). If a bug is found in the retry logic, the sed pattern, or the git auth, it must be fixed in two places. The .sh script exists specifically to be reusable -- the YAML template should call it (e.g., wget or curl the script from pal-e-platform, or have each consuming repo copy the .sh and call it).

Recommendation: Either (a) have the YAML template call the .sh script (fetch from raw Forgejo URL or expect it vendored), or (b) remove the .sh script entirely and document that the YAML inline block IS the canonical implementation. Two copies of push-with-token retry logic is a divergence risk.

2. No test coverage for the shell script

The script performs git clone, sed, commit, push with retry logic. There are no automated tests. The Test Plan shows manual verification only ("Verified sed pattern works on all kustomization.yaml variants") but no reproducible test. For a script that pushes commits to a deployment repo with a token, this is a blocker per the review criteria ("New functionality with zero test coverage").

At minimum, the sed pattern should have a test: create a mock kustomization.yaml with the known variants (bare SHA, quoted tag, latest, multiple newTag entries), run the sed, and assert the output. This could be a simple shell script in scripts/tests/ or a CI step that runs on PR.

NITS

  1. alpine/git:latest image tag in the YAML template (line 20): Using latest for CI images is fragile. Consider pinning to a specific version (e.g., alpine/git:2.47) to prevent unexpected breakage when the image updates. This is noted in the k8s security checklist (image tags should be pinned).

  2. OVERLAY_ENV defaulting to prod in the .sh script but hardcoded to prod in the YAML template: The .sh script supports OVERLAY_ENV as an optional override, but the YAML template hardcodes overlays/${OVERLAY}/prod/kustomization.yaml. If the .sh script is the intended reusable interface, the YAML template's hardcoding is fine as a prod-specific reference. But if someone copies the YAML and wants a different env, they'd need to edit the path manually. Minor inconsistency.

  3. Commit email ci@pal-e.dev: Not a real domain MX record (presumably). Fine for git attribution but worth noting this is a noreply-style address. No action needed.

  4. The token is passed via HTTP URL (http://ci-token:${FORGEJO_TOKEN}@...): This is the internal cluster URL (port 80), not external HTTPS. The token will appear in git remote -v output inside the container. Since this is an ephemeral CI container, the risk is low, but if Woodpecker logs git commands verbosely, the token could leak into CI logs. Consider using .netrc or git credential.helper instead of URL-embedded credentials (the existing .woodpecker.yaml clone step already uses the .netrc pattern -- see lines 9-10 of the existing file). This would be consistent with the existing pattern in this repo.

  5. Missing westside-contracts overlay: The YAML template notes "(no overlay yet -- create one first)". This is documented discovered scope. Should have a corresponding Forgejo issue if it doesn't already.

SOP COMPLIANCE

  • Branch named after issue: 204-image-tag-automation references issue #204
  • PR body has Summary, Changes, Test Plan, Related sections
  • Related references parent issue: "Closes #204", also references #148 (supersedes) and #203 (depends on)
  • No plan slug referenced -- PR body says no plan, which is acceptable for bug/fix work per feedback_todos_plan_pipeline.md
  • No secrets committed -- token is referenced via from_secret, not hardcoded
  • No unnecessary file changes -- 2 new files, both directly related to the issue
  • Commit scope is focused on the stated problem (image tag automation gap)
  • Discovered scope documented: "9 child issues needed for per-repo rollout"

PROCESS OBSERVATIONS

DORA impact:

  • Deployment Frequency: This is a direct DF enabler. Currently every deploy requires manual kubectl set image or a manual PR to pal-e-deployments. Automating the tag update closes the last gap in the CI-to-ArgoCD pipeline. High-value change.
  • Lead Time for Changes: Will reduce lead time by eliminating the manual step between image push and ArgoCD sync.
  • Change Failure Risk: The retry logic and idempotency checks reduce failure risk for concurrent pipelines. The [skip ci] prefix prevents infinite loops. Good defensive design.

Dependency chain: This PR depends on #203 (ArgoCD source URLs) for end-to-end verification. The PR body correctly notes this. The 9 per-repo rollout issues are discovered scope that should be tracked.

The DRY violation is the primary concern. The script is good engineering. The YAML template duplicating it creates maintenance burden across 9+ repos that will each copy the inline version. A bug fix would require updating 10 files across 10 repos instead of 1 file in 1 repo.

VERDICT: NOT APPROVED

Two blockers must be addressed:

  1. DRY violation: Choose one canonical implementation (.sh script OR inline YAML) and have the other reference it. Two independent copies of token-authenticated push-with-retry logic is a divergence risk.
  2. Test coverage: Add at minimum a sed-pattern test against known kustomization.yaml variants (bare SHA, quoted tag, latest, dual newTag entries). A shell script in scripts/tests/ that creates mock files and asserts output would suffice.
## PR #205 Review ### DOMAIN REVIEW **Tech stack:** POSIX shell scripts + Woodpecker CI YAML (CI/CD automation domain). The PR adds two new files under `scripts/` -- a standalone shell script and a Woodpecker step template. Both target kustomize overlays in the `pal-e-deployments` repo. **File paths are clean.** Both files are at `scripts/` in the repo root, not nested under a worktree. Good. **Shell script quality (`scripts/update-kustomize-tag.sh`):** - POSIX `#!/bin/sh` with `set -eu` -- correct for CI containers (Alpine/busybox) - Required env var validation via `${VAR:?msg}` -- good pattern - Idempotency via `git diff --quiet` -- correct - `[skip ci]` commit prefix to prevent CI loops -- correct - Retry logic with rebase + re-apply sed on conflict -- well thought out for concurrent pipeline pushes - `--depth 5` instead of `--depth 1` to support rebase -- correct, with clear comment explaining why **Woodpecker step template (`scripts/woodpecker-update-tag-step.yaml`):** - Complete overlay directory mapping for all 9 services -- useful reference - Notes `westside-contracts` has no overlay yet -- good - `depends_on: build-and-push` and `when: push to main` guards -- correct **Sed pattern analysis against real kustomization.yaml files:** Pattern: `s/^\( newTag:\s*\).*$/\1${IMAGE_TAG}/` Validated against all 13 `newTag` lines across 9 overlay prod kustomization.yaml files: - Bare SHA tags (e.g., `newTag: 4102552649e4...`) -- matches correctly - Short SHAs (e.g., `newTag: a81d881`) -- matches correctly - `latest` (e.g., `newTag: latest`) -- matches correctly - **Quoted tags** (e.g., `newTag: "3ecec95"` in `platform-validation`) -- matches and replaces with unquoted tag. This is a minor format change but functionally equivalent for kustomize. Acceptable. **Multi-image overlay behavior:** Several overlays have 2 `newTag` entries (basketball-api, pal-e-docs, mcd-tracker, pal-e-mail). The sed replaces ALL `newTag` lines to the same value. This is documented as intentional in the PR ("update all newTag entries"). Currently all dual entries share the same tag, so this is correct. However, if a future overlay needs different tags per image block, this script would need modification. The PR body's review checklist adequately calls this out as known scope. ### BLOCKERS **1. DRY violation: full logic duplication between `.sh` script and `.yaml` template** `scripts/update-kustomize-tag.sh` (98 lines) contains the complete implementation. `scripts/woodpecker-update-tag-step.yaml` (79 lines) re-implements the same logic inline under `commands:` rather than calling the shell script. The YAML template comment says "Inline implementation (no external script dependency)" -- but this creates two copies of the same logic that will diverge over time. This is a DRY violation in a CI/security-adjacent path (pushing commits with a token to a deployment repo). If a bug is found in the retry logic, the sed pattern, or the git auth, it must be fixed in two places. The `.sh` script exists specifically to be reusable -- the YAML template should call it (e.g., `wget` or `curl` the script from pal-e-platform, or have each consuming repo copy the `.sh` and call it). **Recommendation:** Either (a) have the YAML template call the `.sh` script (fetch from raw Forgejo URL or expect it vendored), or (b) remove the `.sh` script entirely and document that the YAML inline block IS the canonical implementation. Two copies of push-with-token retry logic is a divergence risk. **2. No test coverage for the shell script** The script performs git clone, sed, commit, push with retry logic. There are no automated tests. The Test Plan shows manual verification only ("Verified sed pattern works on all kustomization.yaml variants") but no reproducible test. For a script that pushes commits to a deployment repo with a token, this is a blocker per the review criteria ("New functionality with zero test coverage"). At minimum, the sed pattern should have a test: create a mock `kustomization.yaml` with the known variants (bare SHA, quoted tag, `latest`, multiple `newTag` entries), run the sed, and assert the output. This could be a simple shell script in `scripts/tests/` or a CI step that runs on PR. ### NITS 1. **`alpine/git:latest` image tag in the YAML template (line 20):** Using `latest` for CI images is fragile. Consider pinning to a specific version (e.g., `alpine/git:2.47`) to prevent unexpected breakage when the image updates. This is noted in the k8s security checklist (image tags should be pinned). 2. **`OVERLAY_ENV` defaulting to `prod` in the `.sh` script but hardcoded to `prod` in the YAML template:** The `.sh` script supports `OVERLAY_ENV` as an optional override, but the YAML template hardcodes `overlays/${OVERLAY}/prod/kustomization.yaml`. If the `.sh` script is the intended reusable interface, the YAML template's hardcoding is fine as a prod-specific reference. But if someone copies the YAML and wants a different env, they'd need to edit the path manually. Minor inconsistency. 3. **Commit email `ci@pal-e.dev`:** Not a real domain MX record (presumably). Fine for git attribution but worth noting this is a noreply-style address. No action needed. 4. **The token is passed via HTTP URL (`http://ci-token:${FORGEJO_TOKEN}@...`):** This is the internal cluster URL (port 80), not external HTTPS. The token will appear in `git remote -v` output inside the container. Since this is an ephemeral CI container, the risk is low, but if Woodpecker logs `git` commands verbosely, the token could leak into CI logs. Consider using `.netrc` or `git credential.helper` instead of URL-embedded credentials (the existing `.woodpecker.yaml` clone step already uses the `.netrc` pattern -- see lines 9-10 of the existing file). This would be consistent with the existing pattern in this repo. 5. **Missing `westside-contracts` overlay:** The YAML template notes "(no overlay yet -- create one first)". This is documented discovered scope. Should have a corresponding Forgejo issue if it doesn't already. ### SOP COMPLIANCE - [x] Branch named after issue: `204-image-tag-automation` references issue #204 - [x] PR body has Summary, Changes, Test Plan, Related sections - [x] Related references parent issue: "Closes #204", also references #148 (supersedes) and #203 (depends on) - [ ] No plan slug referenced -- PR body says no plan, which is acceptable for bug/fix work per `feedback_todos_plan_pipeline.md` - [x] No secrets committed -- token is referenced via `from_secret`, not hardcoded - [x] No unnecessary file changes -- 2 new files, both directly related to the issue - [x] Commit scope is focused on the stated problem (image tag automation gap) - [x] Discovered scope documented: "9 child issues needed for per-repo rollout" ### PROCESS OBSERVATIONS **DORA impact:** - **Deployment Frequency:** This is a direct DF enabler. Currently every deploy requires manual `kubectl set image` or a manual PR to pal-e-deployments. Automating the tag update closes the last gap in the CI-to-ArgoCD pipeline. High-value change. - **Lead Time for Changes:** Will reduce lead time by eliminating the manual step between image push and ArgoCD sync. - **Change Failure Risk:** The retry logic and idempotency checks reduce failure risk for concurrent pipelines. The `[skip ci]` prefix prevents infinite loops. Good defensive design. **Dependency chain:** This PR depends on #203 (ArgoCD source URLs) for end-to-end verification. The PR body correctly notes this. The 9 per-repo rollout issues are discovered scope that should be tracked. **The DRY violation is the primary concern.** The script is good engineering. The YAML template duplicating it creates maintenance burden across 9+ repos that will each copy the inline version. A bug fix would require updating 10 files across 10 repos instead of 1 file in 1 repo. ### VERDICT: NOT APPROVED Two blockers must be addressed: 1. **DRY violation:** Choose one canonical implementation (`.sh` script OR inline YAML) and have the other reference it. Two independent copies of token-authenticated push-with-retry logic is a divergence risk. 2. **Test coverage:** Add at minimum a sed-pattern test against known kustomization.yaml variants (bare SHA, quoted tag, `latest`, dual `newTag` entries). A shell script in `scripts/tests/` that creates mock files and asserts output would suffice.
fix: eliminate DRY violation and add sed pattern tests
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
ec3b484fe1
YAML template now downloads and runs the canonical shell script instead
of re-implementing clone/sed/push logic inline. Added 7-case test suite
covering bare SHA, quoted tags, "latest", dual newTag entries,
idempotency, full SHA, and non-newTag field safety.

Fixes QA blockers on PR #205.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

QA Blocker Fixes (ec3b484)

1. DRY violation resolved

woodpecker-update-tag-step.yaml no longer re-implements the clone/sed/push logic inline. It now downloads and executes the canonical update-kustomize-tag.sh from pal-e-platform's main branch via wget. The shell script remains the single source of truth.

2. Test coverage added

New scripts/test-update-kustomize-tag.sh -- 7 test cases covering:

  • Bare SHA tag replacement
  • Quoted tag (e.g. "3ecec95") replacement
  • latest tag replacement
  • Dual newTag entries (basketball-api pattern) -- both updated
  • Idempotency (same tag = no change)
  • Full 40-char SHA replacement
  • Non-newTag fields (newName, namespace) left untouched

All 7 pass.

## QA Blocker Fixes (ec3b484) ### 1. DRY violation resolved `woodpecker-update-tag-step.yaml` no longer re-implements the clone/sed/push logic inline. It now downloads and executes the canonical `update-kustomize-tag.sh` from pal-e-platform's main branch via wget. The shell script remains the single source of truth. ### 2. Test coverage added New `scripts/test-update-kustomize-tag.sh` -- 7 test cases covering: - Bare SHA tag replacement - Quoted tag (e.g. `"3ecec95"`) replacement - `latest` tag replacement - Dual `newTag` entries (basketball-api pattern) -- both updated - Idempotency (same tag = no change) - Full 40-char SHA replacement - Non-`newTag` fields (`newName`, `namespace`) left untouched All 7 pass.
Author
Owner

PR #205 Review (Re-review)

Previous review identified two blockers: DRY violation (YAML template duplicated sed logic inline) and no test coverage. Commit ec3b484 was pushed to address both. This re-review evaluates the fix.

DOMAIN REVIEW

Tech stack: POSIX shell scripts + Woodpecker CI YAML. Domain: CI/CD automation, kustomize overlay management.

File paths: All three files are at repo root level (scripts/update-kustomize-tag.sh, scripts/test-update-kustomize-tag.sh, scripts/woodpecker-update-tag-step.yaml). No worktree path contamination. Clean.

DRY fix verified: The YAML template (woodpecker-update-tag-step.yaml lines 28-30) now downloads the canonical script via wget from Forgejo raw URL and runs it with sh. Zero inline sed logic. All business logic lives in update-kustomize-tag.sh. The previous blocker is resolved.

Shell script quality (update-kustomize-tag.sh):

  • set -eu at top -- good, fails on errors and unset vars
  • Required env vars validated with ${VAR:?msg} pattern -- clean POSIX
  • Sensible defaults for optional vars (OVERLAY_ENV, FORGEJO_HOST, DEPLOY_REPO)
  • git clone --depth 5 with comment explaining why not depth 1 -- good operational awareness
  • Idempotency check via git diff --quiet before committing -- prevents empty commits
  • Push retry loop with rebase and re-apply -- handles concurrent pipeline race conditions correctly
  • [skip ci] prefix on commit message -- prevents CI loops
  • Token passed via git URL (not cli arg) -- acceptable for CI containers, not leaked to process table

sed pattern: s/^\( newTag:\s*\).*$/\1${IMAGE_TAG}/ -- anchored to 2-space indent, matches newTag: field specifically. Won't accidentally match newName: or top-level fields. Test 7 explicitly validates this boundary.

Potential sed injection: IMAGE_TAG is interpolated directly into the sed expression. In this context, IMAGE_TAG comes from CI_COMMIT_SHA (a hex git SHA), so the attack surface is minimal. However, if someone passes a tag containing / or & characters, the sed would break or produce unexpected output. This is a nit given the CI_COMMIT_SHA source, but worth noting.

alpine/git:latest image tag: The template uses an unpinned :latest tag. This is a known tradeoff for reference templates -- consumers should pin to a specific version.

BLOCKERS

None. Both previous blockers are resolved:

  1. DRY violation -- FIXED. YAML template delegates to the shell script via wget + sh. Single source of truth for sed logic.

  2. Test coverage -- FIXED. test-update-kustomize-tag.sh provides 7 test cases:

    • Test 1: Bare SHA tag (most common case)
    • Test 2: Quoted tag
    • Test 3: latest tag
    • Test 4: Dual newTag entries (basketball-api pattern)
    • Test 5: Idempotency (same tag, no-op)
    • Test 6: Full 40-char SHA
    • Test 7: Non-newTag fields unchanged (newName, namespace)

    Covers happy path, edge cases, and boundary conditions. Test harness uses mktemp, trap cleanup, and proper exit codes.

NITS

  1. sed pattern duplication between test and script: The test file duplicates the sed pattern in run_sed() (line 26) rather than sourcing it from the script. If someone updates the pattern in one file but not the other, tests would pass but production would differ. Consider extracting the sed pattern as a shared function or having the test source the script. Low risk since the pattern is simple, but worth noting for maintainability.

  2. alpine/git:latest in template: The reference template uses an unpinned image tag. Consider adding a comment recommending consumers pin to a specific version (e.g., alpine/git:v2.43.0).

  3. IMAGE_TAG not validated as hex: The script validates that IMAGE_TAG is non-empty but does not validate it's a valid git SHA (hex chars only). A tag containing sed metacharacters (/, &, \) could produce unexpected sed behavior. Low risk since CI_COMMIT_SHA is always hex, but a regex guard like echo "${IMAGE_TAG}" | grep -qE '^[a-f0-9]+$' would add defense in depth.

  4. CLONE_DIR is hardcoded to /tmp/pal-e-deployments: If two pipelines for different overlays run concurrently in the same container, they would collide. The rm -rf at line 41 mitigates this since Woodpecker runs each step in its own container, but a unique suffix (e.g., mktemp -d) would be safer.

SOP COMPLIANCE

  • Branch named after issue: 204-image-tag-automation references issue #204
  • PR body follows template: Summary, Changes, Test Plan, Related sections all present
  • Related references parent issue: "Closes #204", also references #148 and #203
  • No secrets committed: FORGEJO_TOKEN sourced from Woodpecker secret, only xxx placeholder in comments
  • No unnecessary file changes: 3 files, all directly related to the feature
  • Discovered scope documented: PR body identifies 9 child issues needed for per-repo rollout (tracked as #206)
  • Test plan includes both automated and manual verification steps

PROCESS OBSERVATIONS

  • Deployment frequency: This directly enables automated tag updates, removing a manual step from every deploy. High DORA impact once rolled out to all 9 repos.
  • Change failure risk: Low. The script is defensive (idempotency check, push retry, [skip ci]). The sed pattern is well-tested.
  • Rollout dependency: Full end-to-end validation depends on #203 (ArgoCD source URLs) and per-repo rollout (#206). The script and tests stand alone.

VERDICT: APPROVED

## PR #205 Review (Re-review) Previous review identified two blockers: DRY violation (YAML template duplicated sed logic inline) and no test coverage. Commit ec3b484 was pushed to address both. This re-review evaluates the fix. ### DOMAIN REVIEW **Tech stack**: POSIX shell scripts + Woodpecker CI YAML. Domain: CI/CD automation, kustomize overlay management. **File paths**: All three files are at repo root level (`scripts/update-kustomize-tag.sh`, `scripts/test-update-kustomize-tag.sh`, `scripts/woodpecker-update-tag-step.yaml`). No worktree path contamination. Clean. **DRY fix verified**: The YAML template (`woodpecker-update-tag-step.yaml` lines 28-30) now downloads the canonical script via `wget` from Forgejo raw URL and runs it with `sh`. Zero inline sed logic. All business logic lives in `update-kustomize-tag.sh`. The previous blocker is resolved. **Shell script quality** (`update-kustomize-tag.sh`): - `set -eu` at top -- good, fails on errors and unset vars - Required env vars validated with `${VAR:?msg}` pattern -- clean POSIX - Sensible defaults for optional vars (`OVERLAY_ENV`, `FORGEJO_HOST`, `DEPLOY_REPO`) - `git clone --depth 5` with comment explaining why not depth 1 -- good operational awareness - Idempotency check via `git diff --quiet` before committing -- prevents empty commits - Push retry loop with rebase and re-apply -- handles concurrent pipeline race conditions correctly - `[skip ci]` prefix on commit message -- prevents CI loops - Token passed via git URL (not cli arg) -- acceptable for CI containers, not leaked to process table **sed pattern**: `s/^\( newTag:\s*\).*$/\1${IMAGE_TAG}/` -- anchored to 2-space indent, matches `newTag:` field specifically. Won't accidentally match `newName:` or top-level fields. Test 7 explicitly validates this boundary. **Potential sed injection**: `IMAGE_TAG` is interpolated directly into the sed expression. In this context, `IMAGE_TAG` comes from `CI_COMMIT_SHA` (a hex git SHA), so the attack surface is minimal. However, if someone passes a tag containing `/` or `&` characters, the sed would break or produce unexpected output. This is a nit given the CI_COMMIT_SHA source, but worth noting. **`alpine/git:latest` image tag**: The template uses an unpinned `:latest` tag. This is a known tradeoff for reference templates -- consumers should pin to a specific version. ### BLOCKERS **None.** Both previous blockers are resolved: 1. **DRY violation -- FIXED.** YAML template delegates to the shell script via `wget` + `sh`. Single source of truth for sed logic. 2. **Test coverage -- FIXED.** `test-update-kustomize-tag.sh` provides 7 test cases: - Test 1: Bare SHA tag (most common case) - Test 2: Quoted tag - Test 3: `latest` tag - Test 4: Dual `newTag` entries (basketball-api pattern) - Test 5: Idempotency (same tag, no-op) - Test 6: Full 40-char SHA - Test 7: Non-`newTag` fields unchanged (`newName`, `namespace`) Covers happy path, edge cases, and boundary conditions. Test harness uses `mktemp`, `trap cleanup`, and proper exit codes. ### NITS 1. **sed pattern duplication between test and script**: The test file duplicates the sed pattern in `run_sed()` (line 26) rather than sourcing it from the script. If someone updates the pattern in one file but not the other, tests would pass but production would differ. Consider extracting the sed pattern as a shared function or having the test source the script. Low risk since the pattern is simple, but worth noting for maintainability. 2. **`alpine/git:latest` in template**: The reference template uses an unpinned image tag. Consider adding a comment recommending consumers pin to a specific version (e.g., `alpine/git:v2.43.0`). 3. **`IMAGE_TAG` not validated as hex**: The script validates that `IMAGE_TAG` is non-empty but does not validate it's a valid git SHA (hex chars only). A tag containing sed metacharacters (`/`, `&`, `\`) could produce unexpected sed behavior. Low risk since `CI_COMMIT_SHA` is always hex, but a regex guard like `echo "${IMAGE_TAG}" | grep -qE '^[a-f0-9]+$'` would add defense in depth. 4. **`CLONE_DIR` is hardcoded to `/tmp/pal-e-deployments`**: If two pipelines for different overlays run concurrently in the same container, they would collide. The `rm -rf` at line 41 mitigates this since Woodpecker runs each step in its own container, but a unique suffix (e.g., `mktemp -d`) would be safer. ### SOP COMPLIANCE - [x] Branch named after issue: `204-image-tag-automation` references issue #204 - [x] PR body follows template: Summary, Changes, Test Plan, Related sections all present - [x] Related references parent issue: "Closes #204", also references #148 and #203 - [x] No secrets committed: `FORGEJO_TOKEN` sourced from Woodpecker secret, only `xxx` placeholder in comments - [x] No unnecessary file changes: 3 files, all directly related to the feature - [x] Discovered scope documented: PR body identifies 9 child issues needed for per-repo rollout (tracked as #206) - [x] Test plan includes both automated and manual verification steps ### PROCESS OBSERVATIONS - **Deployment frequency**: This directly enables automated tag updates, removing a manual step from every deploy. High DORA impact once rolled out to all 9 repos. - **Change failure risk**: Low. The script is defensive (idempotency check, push retry, `[skip ci]`). The sed pattern is well-tested. - **Rollout dependency**: Full end-to-end validation depends on #203 (ArgoCD source URLs) and per-repo rollout (#206). The script and tests stand alone. ### VERDICT: APPROVED
forgejo_admin deleted branch 204-image-tag-automation 2026-03-27 20:53:27 +00:00
Sign in to join this conversation.
No description provided.