Woodpecker wpc_* step usage snippets
ADR: docs/architecture/adr/0135-woodpecker-ci-full-migration-phases3-5.md §2
Wave: A (PORT-COMPOSITE)
Issue: #4016
These are the five scripts/ci/wpc_* scripts that replace the GHA composite
actions. Each section shows the canonical step block to copy-paste into a
.woodpecker/*.yaml pipeline.
1. wpc_load_vault_secrets.py — Infisical vault init
Fetches secrets from vault.raxx.app (Infisical Universal Auth) and writes
them to a shell-sourceable file for use within the same step (same-step
source pattern — confirmed correct for WP v3.16.0, ADR-0135 Wave A A1).
Why same-step, not a separate step: Woodpecker v3 runs each step in a
separate Docker container. There is no automatic inter-step env propagation
equivalent to GHA's $GITHUB_ENV. The WOODPECKER_ENV_FILE env var is NOT
set in step context on v3.16.0 (confirmed: WP build #6, 2026-07-04).
Pattern: Add the vault fetch as the FIRST commands of the step that needs
the secrets, source the generated file, then run the real work — all within one
commands: block (which executes in a single shell).
- name: your-step-that-needs-vault-secrets
image: python:3.12-slim
environment:
# WP org-level secrets — injected via from_secret; never hardcoded.
INFISICAL_CLIENT_ID:
from_secret: INFISICAL_CLIENT_ID
INFISICAL_CLIENT_SECRET:
from_secret: INFISICAL_CLIENT_SECRET
INFISICAL_PROJECT_ID:
from_secret: INFISICAL_PROJECT_ID
CF_ACCESS_CLIENT_ID:
from_secret: CF_ACCESS_CLIENT_ID
CF_ACCESS_CLIENT_SECRET:
from_secret: CF_ACCESS_CLIENT_SECRET
# Per-step vault configuration:
VAULT_SECRETS: "ALPACA_PAPER_API_KEY,ALPACA_PAPER_API_SECRET"
# Use EXPORT_NAME=VAULT_KEY mapping when env-var name differs from vault key:
# VAULT_SECRETS: "APP_KEY=ALPACA_PAPER_API_KEY"
VAULT_PATH: "/MooseQuest/"
VAULT_ENV: "prod" # or "staging"
# VAULT_ALLOW_MISSING: "true" # set to tolerate 404 for optional keys
# VAULT_SOURCE_FILE: "/tmp/vault-env.sh" # default; change for parallel steps
commands:
- pip install --quiet requests
- python3 scripts/ci/wpc_load_vault_secrets.py
- . /tmp/vault-env.sh && rm -f /tmp/vault-env.sh
- |
# ALPACA_PAPER_API_KEY and ALPACA_PAPER_API_SECRET are now in scope
echo "key length: ${#ALPACA_PAPER_API_KEY}"
# ... real work here ...
Secret encoding: values are base64-encoded in the generated file so that multi-line secrets (PEM keys, certificates) and any special characters survive safely. Raw values never appear in plaintext in the file, in logs, or on stdout.
Env var names reconciliation with #4037:
The five bootstrap secret names are exactly as set in issue #4037:
INFISICAL_CLIENT_ID, INFISICAL_CLIENT_SECRET, INFISICAL_PROJECT_ID,
CF_ACCESS_CLIENT_ID, CF_ACCESS_CLIENT_SECRET.
2. wpc_notify_deploy_status.py — Console deploy-modal callback
Posts a lifecycle status update to the Raxx console deploy tracking endpoint. Failure-safe: a network error or console downtime never aborts the calling pipeline.
- name: notify-deploy-status
image: python:3.12-slim
environment:
CONSOLE_DEPLOY_ID: ${CONSOLE_DEPLOY_ID} # from pipeline input variable
STATUS: building # or: deploying, succeeded, failed, cancelled
LOG_LINE: "Deploy job started" # optional single-line message
FAILURE_REASON: "" # optional; set when STATUS=failed
CONSOLE_URL: https://console.raxx.app
HMAC_SECRET:
from_secret: DEPLOY_CALLBACK_HMAC_SECRET
WPC_RUN_ID: ${CI_PIPELINE_NUMBER}
WPC_RUN_URL: ${CI_PIPELINE_URL}
commands:
- python3 scripts/ci/wpc_notify_deploy_status.py
When CONSOLE_DEPLOY_ID is empty (manual break-glass run), the step logs a
skip message and exits 0 — no network call is made.
3. wpc_emit_audit_event.py — Console audit-event emit
POSTs a structured audit event to the console's /api/internal/audit endpoint.
Respects FLAG_CONSOLE_DEPLOY_AUDIT_INGEST — if the flag is off (501 response),
the step skips silently. Never fatal.
- name: emit-audit-event
image: python:3.12-slim
environment:
AUDIT_ACTION: console.deploy.production
AUDIT_ACTOR: ${CI_COMMIT_AUTHOR}
AUDIT_SHA: ${CI_COMMIT_SHA}
AUDIT_APP_NAME: raxx-api-prod
AUDIT_ENVIRONMENT: production
AUDIT_TRIGGER: ci # or: manual, rollback
AUDIT_OUTCOME: success # or: deploy_failed, health_check_failed
AUDIT_RUN_ID: ${CI_PIPELINE_NUMBER}
AUDIT_RUN_URL: ${CI_PIPELINE_URL}
AUDIT_CONSOLE_URL: https://raxx-console-prod-ff30a22abccb.herokuapp.com
AUDIT_TOKEN:
from_secret: CONSOLE_AUDIT_INGEST_TOKEN
commands:
- python3 scripts/ci/wpc_emit_audit_event.py
Register new AUDIT_ACTION values in
docs/architecture/audit/action-allowlist.txt before use (ADR-0055).
4. wpc_cloudflare_retry.sh — CF API / wrangler deploy with retry
Runs a shell command with quadratic-backoff retry. Default: 5 attempts, starting at 15 s delay (doubles each retry: 15, 30, 60, 120 s).
- name: deploy-cf-pages
image: node:20-slim
environment:
CLOUDFLARE_API_TOKEN:
from_secret: CLOUDFLARE_PAGES_TOKEN
CF_RETRY_COMMAND: >-
npx wrangler pages deploy dist
--project-name raxx-app
--branch main
CF_RETRY_STEP_NAME: "CF Pages deploy (raxx-app)"
CF_RETRY_MAX_ATTEMPTS: "5"
CF_RETRY_INITIAL_DELAY: "15"
commands:
- npm ci --prefix frontend/raxx-next
- npm run --prefix frontend/raxx-next build
- bash scripts/ci/wpc_cloudflare_retry.sh
For quick local CI that doesn't reach CF (e.g. lint-only pipelines), set
CF_RETRY_MAX_ATTEMPTS: "1" to skip the retry loop.
5. wpc_check_deploy_freeze.sh — Console deploy-freeze gate
Checks the global deploy-freeze flag before a production deploy proceeds.
Two-tier design: console API first, DEPLOY_FREEZE_OVERRIDE env var as
break-glass fallback. Fail-open if both signals are absent.
- name: check-deploy-freeze
image: alpine/curl:latest
environment:
CF_DEPLOY_FREEZE_CLIENT_ID:
from_secret: CF_DEPLOY_FREEZE_CLIENT_ID
CF_DEPLOY_FREEZE_CLIENT_SECRET:
from_secret: CF_DEPLOY_FREEZE_CLIENT_SECRET
CONSOLE_URL: https://console.raxx.app
# DEPLOY_FREEZE_OVERRIDE: "1" # operator break-glass (set via WP secret)
commands:
- bash scripts/ci/wpc_check_deploy_freeze.sh
Place this step before any push-to-heroku or CF Pages deploy step in Wave C deploy pipelines.