build-ci-image.yml had event:[push,manual] which caused it to run on every manual pipeline trigger. Its registry_user/registry_password secrets don't have the manual event, causing all manual pipelines to error. Removed manual from its event list since it only needs push. Reverted evaluate conditions (Woodpecker evaluates secrets before conditions, so evaluate can't prevent missing-secret errors). [ci skip] Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
160 lines
7 KiB
YAML
160 lines
7 KiB
YAML
when:
|
|
event: manual
|
|
|
|
clone:
|
|
git:
|
|
image: woodpeckerci/plugin-git
|
|
settings:
|
|
attempts: 5
|
|
backoff: 10s
|
|
|
|
steps:
|
|
- name: validate-inputs
|
|
image: alpine
|
|
commands:
|
|
- |
|
|
if [ -z "$USERNAME" ] || [ -z "$EMAIL" ]; then
|
|
echo "ERROR: USERNAME and EMAIL variables are required"
|
|
echo "Trigger with: POST /api/repos/1/pipelines {branch:master, variables:{USERNAME:x, EMAIL:y}}"
|
|
exit 1
|
|
fi
|
|
# Validate username: lowercase alphanumeric + dash/underscore, 2-63 chars
|
|
if ! echo "$USERNAME" | grep -qE '^[a-z0-9][a-z0-9_-]{0,61}[a-z0-9]$'; then
|
|
echo "ERROR: USERNAME must be 2-63 chars, lowercase alphanumeric/dash/underscore"
|
|
exit 1
|
|
fi
|
|
# Validate email: basic format check
|
|
if ! echo "$EMAIL" | grep -qE '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'; then
|
|
echo "ERROR: EMAIL must be a valid email address"
|
|
exit 1
|
|
fi
|
|
echo "Provisioning user: $USERNAME ($EMAIL)"
|
|
echo "export PROVISION_USERNAME='$USERNAME'" > .provision-env
|
|
echo "export PROVISION_EMAIL='$EMAIL'" >> .provision-env
|
|
|
|
- name: prepare
|
|
image: alpine
|
|
commands:
|
|
- "apk update && apk add jq curl git git-crypt"
|
|
# git-crypt for secrets/ directory
|
|
- |
|
|
curl -k https://10.0.20.100:6443/api/v1/namespaces/woodpecker/configmaps/git-crypt-key \
|
|
-H "Authorization:Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
|
|
| jq -r .data.key | base64 -d > /tmp/key
|
|
- "git-crypt unlock /tmp/key; rm -f /tmp/key"
|
|
# Vault: authenticate via K8s service account JWT
|
|
- |
|
|
SA_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
|
|
VAULT_TOKEN=$(curl -s -X POST http://vault-active.vault.svc.cluster.local:8200/v1/auth/kubernetes/login \
|
|
-d "{\"role\":\"ci\",\"jwt\":\"$SA_TOKEN\"}" | jq -r .auth.client_token)
|
|
echo "export VAULT_TOKEN=$VAULT_TOKEN" > .vault-env
|
|
echo "export VAULT_ADDR=http://vault-active.vault.svc.cluster.local:8200" >> .vault-env
|
|
|
|
- name: update-vault-kv
|
|
image: alpine
|
|
commands:
|
|
- "apk update && apk add jq curl"
|
|
# Read current platform secret
|
|
- |
|
|
. .provision-env && . .vault-env
|
|
CURRENT=$(curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
|
|
"$VAULT_ADDR/v1/secret/data/platform" | jq -r '.data.data')
|
|
|
|
# Parse current k8s_users (stored as JSON string)
|
|
CURRENT_USERS=$(echo "$CURRENT" | jq -r '.k8s_users')
|
|
|
|
# Check if user already exists
|
|
if echo "$CURRENT_USERS" | jq -e --arg u "$PROVISION_USERNAME" '.[$u]' >/dev/null 2>&1; then
|
|
echo "User $PROVISION_USERNAME already exists in k8s_users — skipping Vault KV update"
|
|
exit 0
|
|
fi
|
|
|
|
# Add new user with convention defaults
|
|
UPDATED_USERS=$(echo "$CURRENT_USERS" | jq --arg u "$PROVISION_USERNAME" --arg e "$PROVISION_EMAIL" \
|
|
'. + {($u): {"role":"namespace-owner","email":$e,"namespaces":[$u],"domains":[],"quota":{"cpu_requests":"2","memory_requests":"4Gi","memory_limits":"8Gi","pods":"20"}}}')
|
|
|
|
# Write back full platform secret with updated k8s_users (as JSON string)
|
|
PAYLOAD=$(echo "$CURRENT" | jq --arg users "$UPDATED_USERS" '.k8s_users = $users')
|
|
|
|
curl -s -X POST -H "X-Vault-Token: $VAULT_TOKEN" \
|
|
"$VAULT_ADDR/v1/secret/data/platform" \
|
|
-d "{\"data\": $PAYLOAD}" | jq .
|
|
|
|
echo "Added $PROVISION_USERNAME to k8s_users in Vault"
|
|
|
|
- name: create-authentik-groups
|
|
image: alpine
|
|
commands:
|
|
- "apk update && apk add jq curl"
|
|
- |
|
|
source .provision-env && source .vault-env
|
|
|
|
# Get Authentik API token from Vault
|
|
AUTHENTIK_TOKEN=$(curl -s -H "X-Vault-Token: $VAULT_TOKEN" \
|
|
"$VAULT_ADDR/v1/secret/data/viktor" | jq -r '.data.data.authentik_api_token')
|
|
AUTHENTIK_URL="https://authentik.viktorbarzin.me"
|
|
|
|
# Create sops-USERNAME group if it doesn't exist
|
|
SOPS_GROUP="sops-$PROVISION_USERNAME"
|
|
EXISTING=$(curl -s -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
|
|
"$AUTHENTIK_URL/api/v3/core/groups/?name=$SOPS_GROUP" | jq -r '.results | length')
|
|
|
|
if [ "$EXISTING" = "0" ]; then
|
|
GROUP_PAYLOAD=$(jq -n --arg name "$SOPS_GROUP" '{"name": $name, "is_superuser": false}')
|
|
GROUP_PK=$(curl -s -X POST -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
"$AUTHENTIK_URL/api/v3/core/groups/" \
|
|
-d "$GROUP_PAYLOAD" | jq -r '.pk')
|
|
echo "Created Authentik group $SOPS_GROUP (pk=$GROUP_PK)"
|
|
else
|
|
GROUP_PK=$(curl -s -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
|
|
"$AUTHENTIK_URL/api/v3/core/groups/?name=$SOPS_GROUP" | jq -r '.results[0].pk')
|
|
echo "Authentik group $SOPS_GROUP already exists (pk=$GROUP_PK)"
|
|
fi
|
|
|
|
# Find the user by username
|
|
USER_PK=$(curl -s -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
|
|
"$AUTHENTIK_URL/api/v3/core/users/?username=$PROVISION_USERNAME" | jq -r '.results[0].pk')
|
|
|
|
if [ "$USER_PK" = "null" ] || [ -z "$USER_PK" ]; then
|
|
echo "WARNING: User $PROVISION_USERNAME not found in Authentik — group assignment skipped"
|
|
echo "The user may not have signed up yet. Groups will need manual assignment."
|
|
exit 0
|
|
fi
|
|
|
|
# Add user to sops group
|
|
CURRENT_MEMBERS=$(curl -s -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
|
|
"$AUTHENTIK_URL/api/v3/core/groups/$GROUP_PK/" | jq -r '.users')
|
|
UPDATED_MEMBERS=$(echo "$CURRENT_MEMBERS" | jq --argjson uid "$USER_PK" '. + [$uid] | unique')
|
|
|
|
curl -s -X PATCH -H "Authorization: Bearer $AUTHENTIK_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
"$AUTHENTIK_URL/api/v3/core/groups/$GROUP_PK/" \
|
|
-d "{\"users\": $UPDATED_MEMBERS}" | jq .
|
|
|
|
echo "Added user $PROVISION_USERNAME (pk=$USER_PK) to group $SOPS_GROUP"
|
|
|
|
- name: notify-apply-needed
|
|
image: curlimages/curl
|
|
commands:
|
|
- |
|
|
. .provision-env
|
|
echo "User $PROVISION_USERNAME added to Vault KV and Authentik sops group."
|
|
echo "Manual step needed: apply vault + rbac + woodpecker stacks."
|
|
echo " cd stacks/vault && ../../scripts/tg apply --non-interactive"
|
|
echo " cd stacks/rbac && ../../scripts/tg apply --non-interactive"
|
|
echo " cd stacks/woodpecker && ../../scripts/tg apply --non-interactive"
|
|
|
|
- name: slack
|
|
image: curlimages/curl
|
|
commands:
|
|
- |
|
|
. .provision-env 2>/dev/null || true
|
|
curl -s -X POST -H 'Content-type: application/json' \
|
|
--data "{\"channel\":\"general\",\"text\":\"Woodpecker CI: User provisioned — $PROVISION_USERNAME added to Vault KV + Authentik. Run: cd stacks/vault && ../../scripts/tg apply --non-interactive && cd ../rbac && ../../scripts/tg apply --non-interactive\"}" \
|
|
"$SLACK_WEBHOOK" || true
|
|
environment:
|
|
SLACK_WEBHOOK:
|
|
from_secret: slack_webhook
|
|
when:
|
|
status: [success, failure]
|