diff --git a/.gitea/workflows/terraform.yml b/.gitea/workflows/terraform.yml new file mode 100644 index 0000000..19e2027 --- /dev/null +++ b/.gitea/workflows/terraform.yml @@ -0,0 +1,132 @@ +name: Terraform Terragrunt (contour) + +# One workflow for ALL closed contours. Per-contour differentiation is done via +# repository variables/secrets in each contour's Gitea — this is the downstream +# runner contract: +# vars: INFRA_ENV, RUNNER_LABEL, RUNNER_IMAGE +# secrets: TF_STATE_S3_ENDPOINT, TF_STATE_S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_KEY, +# VAULT_ADDR, VAULT_TOKEN, SOPS_AGE_KEY, KUBECONFIG_B64 (external runner +# only), KUBE_CONTEXT (external runner only) +# Infra-service admin creds are NOT passed as secrets — they are read from Vault +# at run time (see the "Run ... stacks" step) so the single source of truth stays +# in the contour Vault. + +on: + pull_request: + branches: [contour] + paths: + - "live/**" + - "modules/**" + - "infrastructure.yaml" + - "infrastructure-secrets.yaml" + - "scripts/**" + - ".gitea/workflows/**" + push: + branches: [contour] + paths: + - "live/**" + - "modules/**" + - "infrastructure.yaml" + - "infrastructure-secrets.yaml" + - "scripts/**" + - ".gitea/workflows/**" + +env: + TF_STATE_S3_REGION: ru-central1 + INFRA_ENV: ${{ vars.INFRA_ENV }} + +jobs: + validate: + runs-on: ${{ vars.RUNNER_LABEL || 'ubuntu-latest' }} + container: + image: ${{ vars.RUNNER_IMAGE || 'cr.yandex/crp3ccidau046kdj8g9q/terraform/terragrunt:v9.11' }} + env: + TF_STATE_S3_ENDPOINT: ${{ secrets.TF_STATE_S3_ENDPOINT }} + TF_STATE_S3_BUCKET: ${{ secrets.TF_STATE_S3_BUCKET }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} + VAULT_ADDR: ${{ secrets.VAULT_ADDR }} + VAULT_TOKEN: ${{ secrets.VAULT_TOKEN }} + SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} + KUBE_CONTEXT: ${{ secrets.KUBE_CONTEXT }} + KUBECONFIG_B64: ${{ secrets.KUBECONFIG_B64 }} + KUBECONFIG: /workspace/.kube/config + GIT_AUTH_TOKEN: ${{ github.token }} + steps: + - uses: actions/checkout@v4 + - name: Prepare kubeconfig + run: | + if [ -n "${KUBECONFIG_B64:-}" ]; then + mkdir -p /workspace/.kube + echo "$KUBECONFIG_B64" | base64 -d > /workspace/.kube/config + chmod 600 /workspace/.kube/config + else + echo "No KUBECONFIG_B64 — in-cluster runner: kubernetes/vault providers use the pod ServiceAccount kubeconfig or in-cluster config." + fi + - name: Run validate for all stacks + run: | + . ./scripts/load_admin_creds.sh + ./scripts/run_all_stacks.sh validate + + plan: + needs: [validate] + runs-on: ${{ vars.RUNNER_LABEL || 'ubuntu-latest' }} + container: + image: ${{ vars.RUNNER_IMAGE || 'cr.yandex/crp3ccidau046kdj8g9q/terraform/terragrunt:v9.11' }} + env: + TF_STATE_S3_ENDPOINT: ${{ secrets.TF_STATE_S3_ENDPOINT }} + TF_STATE_S3_BUCKET: ${{ secrets.TF_STATE_S3_BUCKET }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} + VAULT_ADDR: ${{ secrets.VAULT_ADDR }} + VAULT_TOKEN: ${{ secrets.VAULT_TOKEN }} + SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} + KUBE_CONTEXT: ${{ secrets.KUBE_CONTEXT }} + KUBECONFIG_B64: ${{ secrets.KUBECONFIG_B64 }} + KUBECONFIG: /workspace/.kube/config + GIT_AUTH_TOKEN: ${{ github.token }} + steps: + - uses: actions/checkout@v4 + - name: Prepare kubeconfig + run: | + if [ -n "${KUBECONFIG_B64:-}" ]; then + mkdir -p /workspace/.kube + echo "$KUBECONFIG_B64" | base64 -d > /workspace/.kube/config + chmod 600 /workspace/.kube/config + fi + - name: Run plan for all stacks + run: | + . ./scripts/load_admin_creds.sh + ./scripts/run_all_stacks.sh plan + + apply: + needs: [plan] + if: github.event_name == 'push' + runs-on: ${{ vars.RUNNER_LABEL || 'ubuntu-latest' }} + container: + image: ${{ vars.RUNNER_IMAGE || 'cr.yandex/crp3ccidau046kdj8g9q/terraform/terragrunt:v9.11' }} + env: + TF_STATE_S3_ENDPOINT: ${{ secrets.TF_STATE_S3_ENDPOINT }} + TF_STATE_S3_BUCKET: ${{ secrets.TF_STATE_S3_BUCKET }} + S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} + S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} + VAULT_ADDR: ${{ secrets.VAULT_ADDR }} + VAULT_TOKEN: ${{ secrets.VAULT_TOKEN }} + SOPS_AGE_KEY: ${{ secrets.SOPS_AGE_KEY }} + KUBE_CONTEXT: ${{ secrets.KUBE_CONTEXT }} + KUBECONFIG_B64: ${{ secrets.KUBECONFIG_B64 }} + KUBECONFIG: /workspace/.kube/config + GIT_AUTH_TOKEN: ${{ github.token }} + steps: + - uses: actions/checkout@v4 + - name: Prepare kubeconfig + run: | + if [ -n "${KUBECONFIG_B64:-}" ]; then + mkdir -p /workspace/.kube + echo "$KUBECONFIG_B64" | base64 -d > /workspace/.kube/config + chmod 600 /workspace/.kube/config + fi + - name: Run apply for all stacks + run: | + . ./scripts/load_admin_creds.sh + ./scripts/run_all_stacks.sh apply diff --git a/scripts/load_admin_creds.sh b/scripts/load_admin_creds.sh new file mode 100755 index 0000000..2976eef --- /dev/null +++ b/scripts/load_admin_creds.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env sh +# Read in-cluster infra-service ADMIN creds from the contour Vault and export them +# for the providers generated in live/terragrunt.hcl. Sourced (not executed) by the +# CI run steps so the exported vars live in the same shell as terragrunt. +# +# Paths are the platform bootstrap layout (same as the bootstrap cronjobs read): +# secrets/postgresql/admin postgres-password +# secrets/kafka/bootstrap interBrokerPassword +# secrets/minio/admin rootUser / rootPassword +# secrets/rabbitmq/auth username / password +set -eu +: "${VAULT_ADDR:?VAULT_ADDR is required}" +: "${VAULT_TOKEN:?VAULT_TOKEN is required}" + +_kv() { + curl -sf -H "X-Vault-Token: ${VAULT_TOKEN}" "${VAULT_ADDR}/v1/secrets/data/$1" \ + | jq -r --arg k "$2" '.data.data[$k] // ""' +} + +# Service endpoints default to the in-cluster svc DNS (consistent across contours); +# override via CI vars if a contour differs. +export PG_ADMIN_HOST="${PG_ADMIN_HOST:-postgresql.postgresql.svc.cluster.local}" +export PG_ADMIN_PORT="${PG_ADMIN_PORT:-5432}" +export PG_ADMIN_USER="${PG_ADMIN_USER:-postgres}" +export PG_ADMIN_PASSWORD="$(_kv postgresql/admin postgres-password)" + +export KAFKA_BOOTSTRAP="${KAFKA_BOOTSTRAP:-kafka-kafka-contour.kafka.svc.cluster.local:9092}" +export KAFKA_ADMIN_USER="${KAFKA_ADMIN_USER:-inter_broker_user}" +export KAFKA_ADMIN_PASSWORD="$(_kv kafka/bootstrap interBrokerPassword)" +export KAFKA_SASL_MECHANISM="${KAFKA_SASL_MECHANISM:-plain}" + +export MINIO_ENDPOINT="${MINIO_ENDPOINT:-minio.minio.svc.cluster.local:9000}" +export MINIO_ADMIN_USER="$(_kv minio/admin rootUser)" +export MINIO_ADMIN_PASSWORD="$(_kv minio/admin rootPassword)" + +export RABBITMQ_ENDPOINT="${RABBITMQ_ENDPOINT:-http://rabbitmq.rabbitmq.svc.cluster.local:15672}" +export RABBITMQ_ADMIN_USER="$(_kv rabbitmq/auth username)" +export RABBITMQ_ADMIN_PASSWORD="$(_kv rabbitmq/auth password)" + +echo "infra admin creds loaded from Vault (${VAULT_ADDR})" diff --git a/scripts/run_all_stacks.sh b/scripts/run_all_stacks.sh new file mode 100755 index 0000000..345d110 --- /dev/null +++ b/scripts/run_all_stacks.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +set -euo pipefail + +ACTION="${1:-}" +if [[ -z "$ACTION" ]]; then + echo "Usage: $0 " + exit 1 +fi + +STACK_FILES=() +while IFS= read -r tg; do + STACK_FILES+=("$tg") +done < <(find live -name 'terragrunt.hcl' -not -path '*/.terragrunt-cache/*' | sort) + +if [[ ${#STACK_FILES[@]} -eq 0 ]]; then + echo "No terragrunt stacks found under live/" + exit 0 +fi + +for tg in "${STACK_FILES[@]}"; do + stack_dir="$(dirname "$tg")" + # Skip only root-level Terragrunt configuration file (live/terragrunt.hcl). + # Execute concrete stacks like live/ and live//. + if [[ "$stack_dir" == "live" ]]; then + continue + fi + + echo "=== STACK: ${stack_dir} ===" + + ( + cd "$stack_dir" + terragrunt init -reconfigure + + if [[ "$ACTION" == "validate" ]]; then + terragrunt validate + elif [[ "$ACTION" == "plan" ]]; then + terragrunt plan -out=tfplan + elif [[ "$ACTION" == "apply" ]]; then + terragrunt apply -auto-approve + else + echo "Unknown action: $ACTION" + exit 1 + fi + ) +done