fix: Vault-gated deployment promotion via Woodpecker environments (#755)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
04696b35a9
commit
3372da594b
3 changed files with 175 additions and 0 deletions
97
bin/disinto
97
bin/disinto
|
|
@ -260,6 +260,20 @@ services:
|
|||
networks:
|
||||
- disinto-net
|
||||
|
||||
# Staging deployment slot — activated by Woodpecker staging pipeline (#755).
|
||||
# Profile-gated: only starts when explicitly targeted by deploy commands.
|
||||
# Customize image/ports/volumes for your project after init.
|
||||
staging:
|
||||
image: alpine:3
|
||||
profiles: ["staging"]
|
||||
security_opt:
|
||||
- apparmor=unconfined
|
||||
environment:
|
||||
DEPLOY_ENV: staging
|
||||
networks:
|
||||
- disinto-net
|
||||
command: ["echo", "staging slot — replace with project image"]
|
||||
|
||||
volumes:
|
||||
forgejo-data:
|
||||
woodpecker-data:
|
||||
|
|
@ -307,6 +321,86 @@ generate_agent_docker() {
|
|||
fi
|
||||
}
|
||||
|
||||
# Generate template .woodpecker/ deployment pipeline configs in a project repo.
|
||||
# Creates staging.yml and production.yml alongside the project's existing CI config.
|
||||
# These pipelines trigger on Woodpecker's deployment event with environment filters.
|
||||
generate_deploy_pipelines() {
|
||||
local repo_root="$1" project_name="$2"
|
||||
local wp_dir="${repo_root}/.woodpecker"
|
||||
|
||||
mkdir -p "$wp_dir"
|
||||
|
||||
# Skip if deploy pipelines already exist
|
||||
if [ -f "${wp_dir}/staging.yml" ] && [ -f "${wp_dir}/production.yml" ]; then
|
||||
echo "Deploy: .woodpecker/{staging,production}.yml (already exist)"
|
||||
return
|
||||
fi
|
||||
|
||||
if [ ! -f "${wp_dir}/staging.yml" ]; then
|
||||
cat > "${wp_dir}/staging.yml" <<'STAGINGEOF'
|
||||
# .woodpecker/staging.yml — Staging deployment pipeline
|
||||
# Triggered by vault-runner via Woodpecker promote API.
|
||||
# Human approves promotion in vault → vault-runner calls promote → this runs.
|
||||
|
||||
when:
|
||||
event: deployment
|
||||
environment: staging
|
||||
|
||||
steps:
|
||||
- name: deploy-staging
|
||||
image: docker:27
|
||||
commands:
|
||||
- echo "Deploying to staging environment..."
|
||||
- echo "Pipeline ${CI_PIPELINE_NUMBER} promoted from CI #${CI_PIPELINE_PARENT}"
|
||||
# Pull the image built by CI and deploy to staging
|
||||
# Customize these commands for your project:
|
||||
# - docker compose -f docker-compose.yml --profile staging up -d
|
||||
- echo "Staging deployment complete"
|
||||
|
||||
- name: verify-staging
|
||||
image: alpine:3
|
||||
commands:
|
||||
- echo "Verifying staging deployment..."
|
||||
# Add health checks, smoke tests, or integration tests here:
|
||||
# - curl -sf http://staging:8080/health || exit 1
|
||||
- echo "Staging verification complete"
|
||||
STAGINGEOF
|
||||
echo "Created: ${wp_dir}/staging.yml"
|
||||
fi
|
||||
|
||||
if [ ! -f "${wp_dir}/production.yml" ]; then
|
||||
cat > "${wp_dir}/production.yml" <<'PRODUCTIONEOF'
|
||||
# .woodpecker/production.yml — Production deployment pipeline
|
||||
# Triggered by vault-runner via Woodpecker promote API.
|
||||
# Human approves promotion in vault → vault-runner calls promote → this runs.
|
||||
|
||||
when:
|
||||
event: deployment
|
||||
environment: production
|
||||
|
||||
steps:
|
||||
- name: deploy-production
|
||||
image: docker:27
|
||||
commands:
|
||||
- echo "Deploying to production environment..."
|
||||
- echo "Pipeline ${CI_PIPELINE_NUMBER} promoted from staging"
|
||||
# Pull the verified image and deploy to production
|
||||
# Customize these commands for your project:
|
||||
# - docker compose -f docker-compose.yml up -d
|
||||
- echo "Production deployment complete"
|
||||
|
||||
- name: verify-production
|
||||
image: alpine:3
|
||||
commands:
|
||||
- echo "Verifying production deployment..."
|
||||
# Add production health checks here:
|
||||
# - curl -sf http://production:8080/health || exit 1
|
||||
- echo "Production verification complete"
|
||||
PRODUCTIONEOF
|
||||
echo "Created: ${wp_dir}/production.yml"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check whether compose mode is active (docker-compose.yml exists).
|
||||
is_compose_mode() {
|
||||
[ -f "${FACTORY_ROOT}/docker-compose.yml" ]
|
||||
|
|
@ -1233,6 +1327,9 @@ p.write_text(text)
|
|||
# Generate VISION.md template
|
||||
generate_vision "$repo_root" "$project_name"
|
||||
|
||||
# Generate template deployment pipeline configs in project repo
|
||||
generate_deploy_pipelines "$repo_root" "$project_name"
|
||||
|
||||
# Install cron jobs
|
||||
install_cron "$project_name" "$toml_path" "$auto_yes" "$bare"
|
||||
|
||||
|
|
|
|||
|
|
@ -235,3 +235,35 @@ classify_pipeline_failure() {
|
|||
echo "code"
|
||||
return 1
|
||||
}
|
||||
|
||||
# ci_promote <repo_id> <pipeline_number> <environment>
|
||||
# Calls the Woodpecker promote API to trigger a deployment pipeline.
|
||||
# The promote endpoint creates a new pipeline with event=deployment and
|
||||
# deploy_to=<environment>, which fires pipelines filtered on that environment.
|
||||
# Requires: WOODPECKER_TOKEN, WOODPECKER_SERVER (from env.sh)
|
||||
# Returns 0 on success, 1 on failure. Prints the new pipeline number on success.
|
||||
ci_promote() {
|
||||
local repo_id="$1" pipeline_num="$2" environment="$3"
|
||||
|
||||
if [ -z "$repo_id" ] || [ -z "$pipeline_num" ] || [ -z "$environment" ]; then
|
||||
echo "Usage: ci_promote <repo_id> <pipeline_number> <environment>" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local resp new_num
|
||||
resp=$(woodpecker_api "/repos/${repo_id}/pipelines/${pipeline_num}" \
|
||||
-X POST \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "event=deployment&deploy_to=${environment}" 2>/dev/null) || {
|
||||
echo "ERROR: promote API call failed" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
new_num=$(printf '%s' "$resp" | jq -r '.number // empty' 2>/dev/null)
|
||||
if [ -z "$new_num" ]; then
|
||||
echo "ERROR: promote returned no pipeline number" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$new_num"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,52 @@ case "$ACTION_TYPE" in
|
|||
fi
|
||||
;;
|
||||
|
||||
promote)
|
||||
# Promote a Woodpecker pipeline to a deployment environment (staging/production).
|
||||
# Payload: {"repo_id": N, "pipeline": N, "environment": "staging"|"production"}
|
||||
PROMOTE_REPO_ID=$(echo "$PAYLOAD" | jq -r '.repo_id // ""')
|
||||
PROMOTE_PIPELINE=$(echo "$PAYLOAD" | jq -r '.pipeline // ""')
|
||||
PROMOTE_ENV=$(echo "$PAYLOAD" | jq -r '.environment // ""')
|
||||
|
||||
if [ -z "$PROMOTE_REPO_ID" ] || [ -z "$PROMOTE_PIPELINE" ] || [ -z "$PROMOTE_ENV" ]; then
|
||||
log "ERROR: ${ACTION_ID} promote missing repo_id, pipeline, or environment"
|
||||
FIRE_EXIT=1
|
||||
else
|
||||
# Validate environment is staging or production
|
||||
case "$PROMOTE_ENV" in
|
||||
staging|production) ;;
|
||||
*)
|
||||
log "ERROR: ${ACTION_ID} promote invalid environment '${PROMOTE_ENV}' (must be staging or production)"
|
||||
FIRE_EXIT=1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$FIRE_EXIT" -eq 0 ]; then
|
||||
WP_SERVER="${WOODPECKER_SERVER:-http://woodpecker:8000}"
|
||||
WP_TOKEN="${WOODPECKER_TOKEN:-}"
|
||||
|
||||
if [ -z "$WP_TOKEN" ]; then
|
||||
log "ERROR: ${ACTION_ID} promote requires WOODPECKER_TOKEN"
|
||||
FIRE_EXIT=1
|
||||
else
|
||||
PROMOTE_RESP=$(curl -sf -X POST \
|
||||
-H "Authorization: Bearer ${WP_TOKEN}" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "event=deployment&deploy_to=${PROMOTE_ENV}" \
|
||||
"${WP_SERVER}/api/repos/${PROMOTE_REPO_ID}/pipelines/${PROMOTE_PIPELINE}" 2>/dev/null) || PROMOTE_RESP=""
|
||||
|
||||
NEW_PIPELINE=$(printf '%s' "$PROMOTE_RESP" | jq -r '.number // empty' 2>/dev/null)
|
||||
if [ -n "$NEW_PIPELINE" ]; then
|
||||
log "${ACTION_ID}: promoted pipeline ${PROMOTE_PIPELINE} to ${PROMOTE_ENV} -> new pipeline #${NEW_PIPELINE}"
|
||||
else
|
||||
log "ERROR: ${ACTION_ID} promote API failed (repo_id=${PROMOTE_REPO_ID} pipeline=${PROMOTE_PIPELINE} env=${PROMOTE_ENV})"
|
||||
FIRE_EXIT=1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
|
||||
blog-post|social-post|email-blast|pricing-change|dns-change|stripe-charge)
|
||||
HANDLER="${VAULT_DIR}/handlers/${ACTION_TYPE}.sh"
|
||||
if [ -x "$HANDLER" ]; then
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue