name: "Infra: OpenTofu" run-name: "Infra: OpenTofu (${{ github.event.inputs.stack || 'grafana' }} ${{ github.event.inputs.mode || 'validate' }})" on: push: branches: - main paths: - ".forgejo/workflows/infra-opentofu.yml" - "Scripts/grafana-tofu.sh" - "Scripts/identity-tofu.sh" - "Scripts/openbao-tofu.sh" - "Scripts/releases-tofu.sh" - "infra/grafana/**" - "infra/identity/**" - "infra/openbao/**" - "infra/releases/**" - "services/grafana/**" workflow_dispatch: inputs: stack: description: OpenTofu stack to run required: true default: grafana type: choice options: - grafana - identity - openbao - releases mode: description: OpenTofu mode required: true default: plan type: choice options: - fmt - validate - plan - apply - import apply_confirmation: description: Type apply-burrow--tofu to run apply required: false default: "" import_address: description: OpenTofu resource address for import mode required: false default: "" import_id: description: Provider import id for import mode required: false default: "" permissions: contents: read concurrency: group: infra-opentofu-${{ github.ref }}-${{ github.event.inputs.stack || 'grafana' }} cancel-in-progress: false jobs: opentofu: name: OpenTofu (${{ github.event.inputs.stack || 'grafana' }}) runs-on: [self-hosted, linux, x86_64, burrow-forge] timeout-minutes: 30 env: MODE: ${{ github.event.inputs.mode || 'validate' }} STACK: ${{ github.event.inputs.stack || 'grafana' }} APPLY_CONFIRMATION: ${{ github.event.inputs.apply_confirmation || '' }} IMPORT_ADDRESS: ${{ github.event.inputs.import_address || '' }} IMPORT_ID: ${{ github.event.inputs.import_id || '' }} TF_INPUT: "false" TF_IN_AUTOMATION: "true" TF_VAR_grafana_url: ${{ vars.GRAFANA_URL || 'https://graphs.burrow.net' }} TF_VAR_google_project_id: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }} TF_VAR_google_project_number: ${{ vars.GOOGLE_PROJECT_NUMBER || '416198671487' }} TF_VAR_google_runner_service_account_email: ${{ vars.BURROW_GOOGLE_WIF_SERVICE_ACCOUNT || 'burrow-forgejo-runner@project-88c23ce9-918a-470a-b33.iam.gserviceaccount.com' }} TF_VAR_google_wif_runner_client_id: ${{ vars.BURROW_AUTHENTIK_WIF_CLIENT_ID || 'google-wif.burrow.net' }} TF_VAR_google_release_bucket_name: ${{ vars.BURROW_RELEASE_GCS_BUCKET || 'burrow-net-releases' }} TF_VAR_google_package_bucket_name: ${{ vars.BURROW_PACKAGE_GCS_BUCKET || 'burrow-net-packages' }} TF_VAR_google_nix_cache_bucket_name: ${{ vars.BURROW_NIX_CACHE_GCS_BUCKET || 'burrow-net-nix-cache' }} TF_VAR_google_nix_cache_public_read: ${{ vars.BURROW_NIX_CACHE_GCS_PUBLIC_READ || 'false' }} TF_VAR_google_storage_location: ${{ vars.BURROW_GCS_LOCATION || 'US' }} BURROW_GOOGLE_PROJECT_ID: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }} GOOGLE_CLOUD_PROJECT: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }} BURROW_GOOGLE_PROJECT_NUMBER: ${{ vars.GOOGLE_PROJECT_NUMBER || '416198671487' }} BURROW_GOOGLE_WIF_POOL_ID: ${{ vars.BURROW_GOOGLE_WIF_POOL_ID || 'burrow-authentik' }} BURROW_GOOGLE_WIF_PROVIDER_ID: ${{ vars.BURROW_GOOGLE_WIF_PROVIDER_ID || 'forgejo-runners' }} BURROW_GOOGLE_WIF_SERVICE_ACCOUNT: ${{ vars.BURROW_GOOGLE_WIF_SERVICE_ACCOUNT || 'burrow-forgejo-runner@project-88c23ce9-918a-470a-b33.iam.gserviceaccount.com' }} BURROW_AUTHENTIK_WIF_ISSUER: ${{ vars.BURROW_AUTHENTIK_WIF_ISSUER || 'https://auth.burrow.net/application/o/google-cloud/' }} BURROW_AUTHENTIK_WIF_AUDIENCE: ${{ vars.BURROW_AUTHENTIK_WIF_AUDIENCE || 'google-wif.burrow.net' }} BURROW_AUTHENTIK_WIF_CLIENT_ID: ${{ vars.BURROW_AUTHENTIK_WIF_CLIENT_ID || 'google-wif.burrow.net' }} BURROW_AUTHENTIK_WIF_CLIENT_SECRET: ${{ secrets.BURROW_AUTHENTIK_WIF_CLIENT_SECRET }} BURROW_NIX_CACHE_URL: ${{ vars.BURROW_NIX_CACHE_URL || 'https://nix.burrow.net/burrow' }} BURROW_NIX_CACHE_PUBLIC_KEY: ${{ vars.BURROW_NIX_CACHE_PUBLIC_KEY }} OPENTOFU_STATE_BUCKET: ${{ vars.OPENTOFU_STATE_BUCKET || '' }} OPENTOFU_STATE_REGION: ${{ vars.OPENTOFU_STATE_REGION || vars.AWS_REGION || 'us-west-2' }} OPENTOFU_STATE_DYNAMODB_TABLE: ${{ vars.OPENTOFU_STATE_DYNAMODB_TABLE || '' }} steps: - name: Checkout shell: bash run: | set -euo pipefail repo_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" if [ ! -d .git ]; then git init . fi if git remote get-url origin >/dev/null 2>&1; then git remote set-url origin "${repo_url}" else git remote add origin "${repo_url}" fi git fetch --force --tags origin "${GITHUB_SHA}" git checkout --force --detach FETCH_HEAD git clean -ffdqx - name: Validate Request shell: bash run: | set -euo pipefail case "$STACK" in grafana|identity|openbao|releases) ;; *) echo "::error ::Unsupported OpenTofu stack: $STACK" >&2 exit 2 ;; esac case "$MODE" in fmt|validate|plan|apply|import) ;; *) echo "::error ::Unsupported OpenTofu mode: $MODE" >&2 exit 2 ;; esac if [[ "${GITHUB_EVENT_NAME}" != "workflow_dispatch" && "$MODE" != "validate" ]]; then echo "::error ::Non-manual runs may only validate." >&2 exit 2 fi if [[ "$MODE" == "apply" && "$APPLY_CONFIRMATION" != "apply-burrow-${STACK}-tofu" ]]; then echo "::error ::apply_confirmation must be exactly apply-burrow-${STACK}-tofu." >&2 exit 1 fi if [[ "$MODE" == "import" && ( -z "$IMPORT_ADDRESS" || -z "$IMPORT_ID" ) ]]; then echo "::error ::import mode requires import_address and import_id." >&2 exit 2 fi - name: Configure Age if: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.mode == 'plan' || github.event.inputs.mode == 'apply' || github.event.inputs.mode == 'import') }} shell: bash env: AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }} run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV" - name: Configure Grafana Provider Auth if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.stack == 'grafana' && (github.event.inputs.mode == 'plan' || github.event.inputs.mode == 'apply' || github.event.inputs.mode == 'import') }} shell: bash run: | set -euo pipefail : "${AGE_IDENTITY_PATH:?AGE_IDENTITY_PATH is required}" agenix="$(nix build --no-link .#agenix --print-out-paths | tail -n1)/bin/agenix" password="$("$agenix" --identity "$AGE_IDENTITY_PATH" --decrypt secrets/infra/grafana-admin-password.age | tr -d '\r')" echo "::add-mask::${password}" { echo "TF_VAR_grafana_auth<> "$GITHUB_ENV" - name: Configure OpenTofu Backend shell: bash run: | set -euo pipefail stack_dir="infra/${STACK}" rm -f "${stack_dir}/backend.hcl" if [[ -n "$OPENTOFU_STATE_BUCKET" ]]; then { printf 'bucket = "%s"\n' "$OPENTOFU_STATE_BUCKET" printf 'key = "%s"\n' "${STACK}/${STACK}.tfstate" printf 'region = "%s"\n' "$OPENTOFU_STATE_REGION" printf 'encrypt = true\n' if [[ -n "$OPENTOFU_STATE_DYNAMODB_TABLE" ]]; then printf 'dynamodb_table = "%s"\n' "$OPENTOFU_STATE_DYNAMODB_TABLE" fi } > "${stack_dir}/backend.hcl" elif [[ "$MODE" == "apply" || "$MODE" == "import" ]]; then echo "::error ::OPENTOFU_STATE_BUCKET is required for apply/import." >&2 exit 1 fi - name: Authenticate Google WIF if: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.stack == 'identity' || github.event.inputs.stack == 'releases') && (github.event.inputs.mode == 'plan' || github.event.inputs.mode == 'apply' || github.event.inputs.mode == 'import') }} shell: bash run: | set -euo pipefail nix develop .#ci -c Scripts/ci/google-wif-auth.sh - name: Run OpenTofu shell: bash run: | set -euo pipefail stack_script="Scripts/${STACK}-tofu.sh" case "$MODE" in fmt) nix develop .#ci -c tofu -chdir="infra/${STACK}" fmt -check ;; validate) "$stack_script" init -backend=false "$stack_script" validate ;; plan) if [[ -f "infra/${STACK}/backend.hcl" ]]; then "$stack_script" init -backend-config=backend.hcl else "$stack_script" init -backend=false fi "$stack_script" plan ;; apply) "$stack_script" init -backend-config=backend.hcl "$stack_script" apply -auto-approve ;; import) "$stack_script" init -backend-config=backend.hcl "$stack_script" import "$IMPORT_ADDRESS" "$IMPORT_ID" ;; esac