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-<stack>-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<<EOF"
            printf 'admin:%s\n' "$password"
            echo "EOF"
          } >> "$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
