Raxx · internal docs

internal · gated

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.