Fix App Insights API version, retention parity, bootstrap auth, budge… #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # PREREQUISITE: Configure a remote backend in infra/terraform/main.tf before | |
| # using this workflow. Without a remote backend, Terraform state is lost between | |
| # runs and apply will attempt to recreate all resources. | |
| # | |
| # OIDC tokens from azure/login@v2 are valid for ~1 hour. Each job performs its | |
| # own fresh login, so token expiry is not a concern for typical deployments. | |
| name: Deploy Landing Zone (Terraform) | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - 'infra/terraform/**' | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - 'infra/terraform/**' | |
| workflow_dispatch: | |
| permissions: | |
| id-token: write | |
| contents: read | |
| pull-requests: write | |
| env: | |
| TF_DIR: infra/terraform | |
| ARM_USE_OIDC: true | |
| jobs: | |
| plan: | |
| name: Terraform Plan (${{ matrix.environment }}) | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| environment: [nonprod, prod] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Terraform | |
| uses: hashicorp/setup-terraform@v3 | |
| with: | |
| terraform_version: "~> 1.9" | |
| - name: Azure Login | |
| uses: azure/login@v2 | |
| with: | |
| client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
| subscription-id: ${{ matrix.environment == 'prod' && secrets.AZURE_SUBSCRIPTION_ID_PROD || secrets.AZURE_SUBSCRIPTION_ID_NONPROD }} | |
| - name: Terraform Init | |
| working-directory: ${{ env.TF_DIR }} | |
| env: | |
| ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| ARM_SUBSCRIPTION_ID: ${{ matrix.environment == 'prod' && secrets.AZURE_SUBSCRIPTION_ID_PROD || secrets.AZURE_SUBSCRIPTION_ID_NONPROD }} | |
| run: terraform init | |
| - name: Set budget start date | |
| run: echo "TF_VAR_budget_start_date=$(date -u +%Y-%m-01T00:00:00Z)" >> "$GITHUB_ENV" | |
| - name: Terraform Plan | |
| id: plan | |
| working-directory: ${{ env.TF_DIR }} | |
| env: | |
| ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| ARM_SUBSCRIPTION_ID: ${{ matrix.environment == 'prod' && secrets.AZURE_SUBSCRIPTION_ID_PROD || secrets.AZURE_SUBSCRIPTION_ID_NONPROD }} | |
| TF_VAR_budget_alert_emails: ${{ vars.BUDGET_ALERT_EMAILS || '["platform@example.com"]' }} | |
| TF_VAR_security_contact_email: ${{ vars.SECURITY_CONTACT_EMAIL || 'security@example.com' }} | |
| run: | | |
| terraform plan \ | |
| -var="subscription_id=${ARM_SUBSCRIPTION_ID}" \ | |
| -var="environment=${{ matrix.environment }}" \ | |
| -var="company_name=${{ vars.COMPANY_NAME || 'mycompany' }}" \ | |
| -no-color \ | |
| -out=tfplan-${{ matrix.environment }} 2>&1 | tee plan-output.txt | |
| - name: Post Plan to PR | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const plan = fs.readFileSync('${{ env.TF_DIR }}/plan-output.txt', 'utf8'); | |
| const truncated = plan.length > 60000 ? plan.substring(0, 60000) + '\n... (truncated)' : plan; | |
| // Extract resource summary from plan output | |
| const summaryMatch = plan.match(/Plan: (\d+ to add, \d+ to change, \d+ to destroy)/); | |
| const noChanges = plan.includes('No changes.'); | |
| const badge = noChanges ? '`No changes`' : (summaryMatch ? `\`${summaryMatch[1]}\`` : '`See details`'); | |
| const body = [ | |
| `### Terraform Plan — \`${{ matrix.environment }}\` ${badge}`, | |
| '', | |
| '<details>', | |
| '<summary>Show Plan Output</summary>', | |
| '', | |
| '```', | |
| truncated, | |
| '```', | |
| '', | |
| '</details>' | |
| ].join('\n'); | |
| github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body | |
| }); | |
| - name: Upload Plan | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: tfplan-${{ matrix.environment }} | |
| path: ${{ env.TF_DIR }}/tfplan-${{ matrix.environment }} | |
| apply-nonprod: | |
| name: Apply Non-Prod | |
| runs-on: ubuntu-latest | |
| needs: plan | |
| if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' | |
| environment: nonprod | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Terraform | |
| uses: hashicorp/setup-terraform@v3 | |
| with: | |
| terraform_version: "~> 1.9" | |
| - name: Download Plan | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: tfplan-nonprod | |
| path: ${{ env.TF_DIR }} | |
| - name: Azure Login | |
| uses: azure/login@v2 | |
| with: | |
| client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
| subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID_NONPROD }} | |
| - name: Terraform Init | |
| working-directory: ${{ env.TF_DIR }} | |
| env: | |
| ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID_NONPROD }} | |
| run: terraform init | |
| - name: Terraform Apply | |
| working-directory: ${{ env.TF_DIR }} | |
| env: | |
| ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID_NONPROD }} | |
| run: terraform apply tfplan-nonprod | |
| # Prod re-plans before applying to avoid stale plan artifacts. The nonprod | |
| # apply may change subscription-level state (Defender, Policy) that the | |
| # parallel plan job could not have predicted. | |
| apply-prod: | |
| name: Apply Prod | |
| runs-on: ubuntu-latest | |
| needs: apply-nonprod | |
| if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' | |
| environment: prod | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Terraform | |
| uses: hashicorp/setup-terraform@v3 | |
| with: | |
| terraform_version: "~> 1.9" | |
| - name: Azure Login | |
| uses: azure/login@v2 | |
| with: | |
| client-id: ${{ secrets.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ secrets.AZURE_TENANT_ID }} | |
| subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID_PROD }} | |
| - name: Terraform Init | |
| working-directory: ${{ env.TF_DIR }} | |
| env: | |
| ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID_PROD }} | |
| run: terraform init | |
| - name: Set budget start date | |
| run: echo "TF_VAR_budget_start_date=$(date -u +%Y-%m-01T00:00:00Z)" >> "$GITHUB_ENV" | |
| - name: Terraform Plan (fresh) | |
| working-directory: ${{ env.TF_DIR }} | |
| env: | |
| ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID_PROD }} | |
| TF_VAR_budget_alert_emails: ${{ vars.BUDGET_ALERT_EMAILS || '["platform@example.com"]' }} | |
| TF_VAR_security_contact_email: ${{ vars.SECURITY_CONTACT_EMAIL || 'security@example.com' }} | |
| run: | | |
| terraform plan \ | |
| -var="subscription_id=${ARM_SUBSCRIPTION_ID}" \ | |
| -var="environment=prod" \ | |
| -var="company_name=${{ vars.COMPANY_NAME || 'mycompany' }}" \ | |
| -out=tfplan-prod | |
| - name: Terraform Apply | |
| working-directory: ${{ env.TF_DIR }} | |
| env: | |
| ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} | |
| ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} | |
| ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID_PROD }} | |
| run: terraform apply tfplan-prod |