Skip to content

Fix App Insights API version, retention parity, bootstrap auth, budge… #2

Fix App Insights API version, retention parity, bootstrap auth, budge…

Fix App Insights API version, retention parity, bootstrap auth, budge… #2

# 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