Add CI plan-on-PR and apply-on-merge pipeline (Phase 6.3+6.4) #50
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/pal-e-platform!50
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "48-ci-plan-and-apply-pipeline"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds
planandapplysteps to the Woodpecker CI pipeline. Plan runs on every PR (after validate passes) and posts tofu plan output as a Forgejo PR comment. Apply runs on push to main and executestofu apply -auto-approvefor automated infrastructure deployment on merge.Changes
.woodpecker.yaml— Restructured from single top-levelwhento step-levelwhenblocks. Addedplanstep (triggers onpull_request, depends onvalidate, posts plan output as PR comment via Forgejo API) andapplystep (triggers onpushtomainonly). Both steps inject all 17 Woodpecker secrets:kubeconfig_content,forgejo_token, and 15tf_var_*secrets. Kubeconfig is written to/tmp/kubeconfigwithchmod 600and used via-backend-config="config_path=/tmp/kubeconfig"andTF_VAR_kubeconfig_pathto override the hardcoded~/.kube/configinversions.tf.Test Plan
validatestep still passesplanstep runs after validate and posts plan output as a PR commentapplystep triggers on the push-to-main eventapplystep completes successfully (no infrastructure drift expected)Review Checklist
tofu fmt/tofu validatenot applicabletf_var_*secrets match the variables defined invariables.tf10.0.0.217:6443(node IP for CI pod network namespace)Related
plan-pal-e-platform(Phase 6.3 + 6.4)Self-Review
Verdict: LGTM -- ready for human review.
Verification
tf_var_*secrets cross-referenced againstvariables.tf-- complete match, no missing or extra entries.kubeconfig_pathcorrectly set viaexportin commands (not a Woodpecker secret) since it points to/tmp/kubeconfigwritten at runtime.forgejo_tokenincluded inplanstep (needed for PR comment API), excluded fromapplystep (not needed). Correct.kubeconfig_contentincluded in both steps. Correct.whenblocks correctly replace the former top-levelwhen.validateandplantrigger onpull_request;applytriggers onpushtomain.depends_on: [validate]ensures plan only runs after validate passes.jq --rawfile+curl -d @filepattern avoids shell escaping issues with plan text./tmp/plan-failed) to allow comment posting before step failure.$$escaping used correctly in multiline blocks for shell variables ($$PLAN_OUTPUT,$${CI_REPO}, etc.) to prevent Woodpecker envsubst from consuming them.yaml.safe_load.Notes
validatestep is unchanged in behavior (same commands, same image). Only structural change is movingwhenfrom top-level to step-level.ashshell supports${#VAR}(string length) and${VAR:offset:length}(substring) used in the truncation logic.PR #50 Review
BLOCKERS
1.
$KUBECONFIG_CONTENT(single$) on lines 39 and 87 -- potential multiline breakageBoth the
planandapplysteps use:KUBECONFIG_CONTENTis declared insecrets:, so Woodpecker performs YAML-level substitution of$KUBECONFIG_CONTENTbefore the shell ever sees it. For a multiline kubeconfig (which it almost certainly is -- YAML with clusters, contexts, users), this substitution injects raw newlines into the YAML command string, which will break parsing or produce a malformedechocommand.Every other variable reference in the multiline block correctly uses
$$(e.g.,$$PLAN_OUTPUT,$$FORGEJO_TOKEN,$${CI_REPO}), which tells Woodpecker to emit a literal$so the shell expands the env var instead.KUBECONFIG_CONTENTshould follow the same pattern:This applies to both line 39 (plan step) and line 87 (apply step).
2. Bashism in Alpine
ashshell --${PLAN_OUTPUT:0:60000}substring syntax (lines 50-52)The truncation logic uses bash substring expansion:
The
${var:offset:length}syntax is a bashism. Theghcr.io/opentofu/opentofu:1.9image is Alpine-based and usesashas/bin/sh. Woodpecker v3 runs commands via/bin/shby default. This will produce a syntax error at runtime.POSIX-compatible alternatives:
PLAN_OUTPUT=$(echo "$PLAN_OUTPUT" | head -c 60000)(useshead)PLAN_OUTPUT=$(printf '%.60000s' "$PLAN_OUTPUT")(usesprintftruncation)The
${#PLAN_OUTPUT}length check on line 50 IS POSIX-compliant and will work inash.3. TF_VAR secret case mismatch -- high risk of silent variable injection failure
Woodpecker v3 exposes secrets as uppercased environment variables. The secret
tf_var_tailscale_oauth_client_idbecomes the env varTF_VAR_TAILSCALE_OAUTH_CLIENT_ID. OpenTofu matchesTF_VAR_*environment variables case-sensitively against variable names. The variable is declared as:OpenTofu looks for
TF_VAR_tailscale_oauth_client_id(lowercase after the prefix). It will NOT matchTF_VAR_TAILSCALE_OAUTH_CLIENT_ID.This affects all 15
tf_var_*secrets. The result would be silent failure: variables with defaults would use defaults, variables without defaults would error ontofu plan.Recommended fix: Explicitly re-export each secret in lowercase in the
commands:block:Or use a loop:
Important caveat: I am flagging this based on documented Woodpecker v3 behavior (secrets uppercased to env vars) and OpenTofu behavior (case-sensitive TF_VAR_ matching). If Woodpecker v3 has changed this behavior to preserve case, or if OpenTofu does case-insensitive matching, this is a non-issue. Recommend testing with a single variable first (e.g., add
printenv | grep -i TF_VARas a debug command to verify actual env var names before thetofu plancall). Do NOT leave the debug command in the final version as it would leak secret values to logs.NITS
4. No
tofu validate/tofu fmt -checkon push-to-main (apply step)The
validatestep only runs onevent: pull_request. On push-to-main, onlyapplyruns. This means the apply step has no pre-validation gate. The code was validated during the PR phase, so this is low risk, but addingvalidateto the push event (or makingapplydepend on a validate step that also triggers on push) would provide defense-in-depth.5. No kubeconfig cleanup in plan/apply steps
Both steps write the kubeconfig to
/tmp/kubeconfigbut never remove it. Since Woodpecker K8s backend runs each step container independently and/tmpis container-local (not a shared volume), the file is destroyed when the container exits. This is fine in practice. Mentioning only for documentation clarity -- atrap 'rm -f /tmp/kubeconfig' EXITwould make the security posture explicit.6. Plan output posted even on plan success -- consider adding a summary line
The PR comment will always contain the full plan output. For large plans, adding a summary line (e.g., "Plan: X to add, Y to change, Z to destroy" or "No changes") at the top of the comment would make PR reviews faster without needing to read the full plan.
7.
apk addon every PR run (line 37)The
planstep installscurlandjqviaapk addon every run. This adds network latency and a failure point (Alpine mirror availability). Consider using a custom image with these tools pre-installed, or caching the apk layer. Low priority since CI runs are infrequent.8. Duplicate secrets blocks
The 15
tf_var_*secrets are listed identically in bothplanandapplysteps (lines 21-35 and 70-84). This is correct but creates maintenance burden -- adding a new TF variable requires updating both blocks. Woodpecker v3 does not support YAML anchors for deduplication, so this is unavoidable. Just noting it for awareness.SOP COMPLIANCE
48-ci-plan-and-apply-pipelinereferences issue #48plan-pal-e-platform(Phase 6.3 + 6.4)Closes #48andCloses #49.woodpecker.yamlchanged, no scope creeptofu fmt/tofu validatenot applicable (correctly noted in PR body)$vs$$, ash bashism, TF_VAR case mismatch)VERDICT: NOT APPROVED
Three blockers must be addressed before merge:
$KUBECONFIG_CONTENTto$$KUBECONFIG_CONTENTon lines 39 and 87${PLAN_OUTPUT:0:60000}with POSIX-compatible alternativeprintenv | grep -i TF_VARas a temporary debug line to confirm Woodpecker's actual behavior, then add re-export commands if needed)Forgejo's API routes PR comments through the issues endpoint. POST pulls/{N}/comments returns 405; POST issues/{N}/comments is the correct path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>Tofu Plan Output