Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0561063
Add account-level Terraform workflow and integrate with existing CI/C…
Thomas-Boyle Apr 7, 2026
283a3a1
Add bucket name validation to Makefile and improve bucket resolution …
Thomas-Boyle Apr 7, 2026
af6e116
Enhance bucket name resolution with validation in account-terraform w…
Thomas-Boyle Apr 7, 2026
2b6d8b2
Add state bucket environment input to workflows and update bucket res…
Thomas-Boyle Apr 7, 2026
5e2a874
Enhance Terraform workspace handling in workflows and bucket resoluti…
Thomas-Boyle Apr 7, 2026
c2d0559
Enhance bucket resolution script and workflow logging
Thomas-Boyle Apr 7, 2026
2ce0cdc
Refactor bucket resolution script to output bucket name and exit
Thomas-Boyle Apr 7, 2026
85b12bc
Refactor bucket resolution script to improve whitespace handling and …
Thomas-Boyle Apr 7, 2026
d93ba1b
Update account-terraform workflow to conditionally set bucket name ba…
Thomas-Boyle Apr 7, 2026
90f4aec
Refactor account-terraform workflow to streamline job dependencies an…
Thomas-Boyle Apr 8, 2026
7ed155f
Enhance account-terraform workflow to improve change detection logging
Thomas-Boyle Apr 8, 2026
a1fe5ea
Update pr-deploy-and-test workflow to enhance SHA handling during syn…
Thomas-Boyle Apr 8, 2026
60f978a
Enhance account-terraform workflow to improve environment variable ha…
Thomas-Boyle Apr 8, 2026
9860152
Enhance account-terraform workflow with improved SHA validation and e…
Thomas-Boyle Apr 8, 2026
a23387e
Refactor account-terraform workflow and bucket resolution script for …
Thomas-Boyle Apr 8, 2026
1abcf47
Refactor account-terraform workflow and bucket resolution script for …
Thomas-Boyle Apr 8, 2026
4caeddb
Refactor account-terraform workflow and bucket resolution script for …
Thomas-Boyle Apr 8, 2026
4db413a
Merge branch 'master' into VED-1223-update-terraform-account-apply-pi…
Thomas-Boyle Apr 8, 2026
c61a3c4
Update base_sha logic in pr-deploy-and-test workflow to simplify SHA …
Thomas-Boyle Apr 8, 2026
31c25f1
chore: empty commit
Thomas-Boyle Apr 8, 2026
aa15e8b
chore: empty commit
Thomas-Boyle Apr 9, 2026
34d9440
Enhance account-terraform workflow by adding manual approval step bef…
Thomas-Boyle Apr 9, 2026
99d20c0
Fix description in ECR lifecycle policy to include a period for consi…
Thomas-Boyle Apr 9, 2026
33b2bed
Refactor account-terraform workflow to enhance input handling and art…
Thomas-Boyle Apr 9, 2026
3dfb8a4
Merge branch 'master' into VED-1223-update-terraform-account-apply-pi…
Thomas-Boyle Apr 9, 2026
c37f9f9
Enhance account-terraform workflow and scripts for improved stability…
Thomas-Boyle Apr 10, 2026
264bee5
Enhance account-terraform workflow with attestation support
Thomas-Boyle Apr 10, 2026
6ccbb2d
chore: empty commit
Thomas-Boyle Apr 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 228 additions & 0 deletions .github/workflows/account-terraform.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
name: Apply Account Terraform

on:
workflow_call:
inputs:
base_sha:
required: true
type: string
head_sha:
required: true
type: string
environment:
required: true
type: string
state_bucket_environment:
required: false
type: string
default: ""
artifact_name:
required: true
type: string
workflow_dispatch:
inputs:
environment:
description: Select AWS account environment
required: true
type: choice
options:
- dev
- preprod
- prod
state_bucket_environment:
description: Override state bucket environment
required: false
type: string
default: ""
base_sha:
description: Base commit SHA for diff checks. Leave blank to use previous commit.
required: false
type: string
default: ""
head_sha:
description: Head commit SHA for diff checks. Leave blank to use current commit.
required: false
type: string
default: ""
artifact_name:
description: Optional Terraform plan artifact name
required: false
type: string
default: ""

run-name: Apply Account Terraform - ${{ inputs.environment }}

concurrency:
group: account-terraform-${{ github.repository }}-${{ inputs.environment }}
cancel-in-progress: false

env:
CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET: ${{ vars.ACCOUNT_TERRAFORM_STATE_BUCKET || (inputs.environment == 'dev' && 'immunisation-terraform-state-files' || '') }}
ACCOUNT_TERRAFORM_STATE_ENVIRONMENT: ${{ inputs.state_bucket_environment }}
ACCOUNT_TERRAFORM_ARTIFACT_NAME: ${{ inputs.artifact_name || format('{0}-account-tfplan-{1}', inputs.environment, github.run_attempt) }}
ACCOUNT_TERRAFORM_VERSION: "1.12.2"

jobs:
account-terraform-plan:
permissions:
id-token: write
contents: read
attestations: write
artifact-metadata: write
runs-on: ubuntu-latest
timeout-minutes: 30
environment:
name: ${{ inputs.environment }}
env:
ACCOUNT_TERRAFORM_BASE_SHA: ${{ inputs.base_sha }}
ACCOUNT_TERRAFORM_HEAD_SHA: ${{ inputs.head_sha || github.sha }}
ACCOUNT_TERRAFORM_ENVIRONMENT: ${{ inputs.environment }}
outputs:
account_infra_changed: ${{ steps.diff.outputs.account_infra_changed }}
plan_sha: ${{ env.ACCOUNT_TERRAFORM_HEAD_SHA }}
steps:
- name: Checkout
uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98
with:
fetch-depth: 0

- name: Detect account terraform changes
id: diff
run: |
base_sha="$ACCOUNT_TERRAFORM_BASE_SHA"
head_sha="$ACCOUNT_TERRAFORM_HEAD_SHA"

if [[ -z "$base_sha" || "$base_sha" == "0000000000000000000000000000000000000000" ]]; then
base_sha=$(git rev-parse HEAD~1)
fi

for sha_name in base_sha head_sha; do
if [[ ! "${!sha_name}" =~ ^[0-9a-f]{40}$ ]]; then
echo "Invalid $sha_name: ${!sha_name}" >&2
exit 1
fi
done

account_changed_files=$(git diff --name-only "$base_sha" "$head_sha" -- infrastructure/account)
if [ -n "$account_changed_files" ]; then
echo "changes detected in files:"
printf '%s\n' "$account_changed_files"
fi
echo "account_infra_changed=$( [ -n "$account_changed_files" ] && echo true || echo false )" >> "$GITHUB_OUTPUT"

- name: Connect to AWS
if: ${{ steps.diff.outputs.account_infra_changed == 'true' }}
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7
with:
aws-region: eu-west-2
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/auto-ops
role-session-name: ${{ format('github-actions-{0}-{1}-{2}', github.run_id, github.run_attempt, github.job) }}

- uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85
if: ${{ steps.diff.outputs.account_infra_changed == 'true' }}
with:
terraform_version: ${{ env.ACCOUNT_TERRAFORM_VERSION }}

- name: Resolve account terraform state bucket
id: account-state-bucket
if: ${{ steps.diff.outputs.account_infra_changed == 'true' }}
run: echo "bucket_name=$(bash ./utilities/scripts/resolve_account_terraform_state_bucket.sh)" >> "$GITHUB_OUTPUT"

- name: Terraform Init (account)
if: ${{ steps.diff.outputs.account_infra_changed == 'true' }}
working-directory: infrastructure/account
env:
ACCOUNT_TERRAFORM_BUCKET_NAME: ${{ steps.account-state-bucket.outputs.bucket_name }}
run: make init ENVIRONMENT="$ACCOUNT_TERRAFORM_ENVIRONMENT" BUCKET_NAME="$ACCOUNT_TERRAFORM_BUCKET_NAME"

- name: Terraform Plan (account)
# Ignore cancellations to prevent Terraform from being killed while it holds a state lock
# A stuck process can still be killed with the force-cancel API operation
if: ${{ steps.diff.outputs.account_infra_changed == 'true' && !failure() }}
working-directory: infrastructure/account
run: make plan-ci ENVIRONMENT="$ACCOUNT_TERRAFORM_ENVIRONMENT"

- name: Save Account Terraform Plan
if: ${{ steps.diff.outputs.account_infra_changed == 'true' }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
with:
name: ${{ env.ACCOUNT_TERRAFORM_ARTIFACT_NAME }}
path: infrastructure/account/tfplan

- name: Attest Account Terraform Plan
if: ${{ steps.diff.outputs.account_infra_changed == 'true' }}
uses: actions/attest@v4
with:
subject-path: infrastructure/account/tfplan

account-terraform-approval:
permissions: {}
needs: [account-terraform-plan]
if: ${{ !cancelled() && needs.account-terraform-plan.result == 'success' && needs.account-terraform-plan.outputs.account_infra_changed == 'true' }}
runs-on: ubuntu-latest
environment:
name: account-apply-${{ inputs.environment }}
steps:
- name: Await manual approval
run: echo "Manual approval granted"

account-terraform-apply:
permissions:
id-token: write
contents: read
attestations: read
needs: [account-terraform-plan, account-terraform-approval]
if: ${{ !cancelled() && needs.account-terraform-plan.result == 'success' && needs.account-terraform-plan.outputs.account_infra_changed == 'true' && needs.account-terraform-approval.result == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 30
environment:
name: ${{ inputs.environment }}
env:
ACCOUNT_TERRAFORM_ENVIRONMENT: ${{ inputs.environment }}
steps:
- name: Checkout
uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98
with:
ref: ${{ needs.account-terraform-plan.outputs.plan_sha }}

- name: Connect to AWS
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7
with:
aws-region: eu-west-2
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/auto-ops
role-session-name: ${{ format('github-actions-{0}-{1}-{2}', github.run_id, github.run_attempt, github.job) }}

- uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85
with:
terraform_version: ${{ env.ACCOUNT_TERRAFORM_VERSION }}

- name: Resolve account terraform state bucket
id: account-state-bucket
run: echo "bucket_name=$(bash ./utilities/scripts/resolve_account_terraform_state_bucket.sh)" >> "$GITHUB_OUTPUT"

- name: Retrieve Account Terraform Plan
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
name: ${{ env.ACCOUNT_TERRAFORM_ARTIFACT_NAME }}
path: infrastructure/account

- name: Verify Account Terraform Plan Attestation
env:
GH_TOKEN: ${{ github.token }}
run: |
gh attestation verify infrastructure/account/tfplan \
--repo "$GITHUB_REPOSITORY" \
--signer-workflow "$GITHUB_REPOSITORY/.github/workflows/account-terraform.yml"

- name: Terraform Init (account)
working-directory: infrastructure/account
env:
ACCOUNT_TERRAFORM_BUCKET_NAME: ${{ steps.account-state-bucket.outputs.bucket_name }}
run: make init ENVIRONMENT="$ACCOUNT_TERRAFORM_ENVIRONMENT" BUCKET_NAME="$ACCOUNT_TERRAFORM_BUCKET_NAME"

- name: Terraform Apply (account)
# Ignore cancellations to prevent Terraform from being killed while it holds a state lock
# A stuck process can still be killed with the force-cancel API operation
if: ${{ !failure() }}
working-directory: infrastructure/account
run: make apply-ci ENVIRONMENT="$ACCOUNT_TERRAFORM_ENVIRONMENT"
2 changes: 1 addition & 1 deletion .github/workflows/pr-deploy-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
with:
apigee_environment: internal-dev
build_recordprocessor_image: ${{ github.event.action == 'opened' || github.event.action == 'reopened' }}
diff_base_sha: ${{ github.event.before }}
diff_base_sha: ${{ github.event.action == 'synchronize' && github.event.before || github.event.pull_request.base.sha }}
diff_head_sha: ${{ github.event.pull_request.head.sha }}
run_diff_check: ${{ github.event.action == 'synchronize' }}
create_mns_subscription: true
Expand Down
15 changes: 15 additions & 0 deletions infrastructure/account/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ tf_cmd = AWS_PROFILE=$(AWS_PROFILE) terraform
tf_state= -backend-config="bucket=$(BUCKET_NAME)"
tf_vars= -var-file=environments/$(ENVIRONMENT)/variables.tfvars

define require_bucket_name
@if [ -z "$(strip $(BUCKET_NAME))" ]; then \
echo "BUCKET_NAME variable not set. Use 'make init ENVIRONMENT=... BUCKET_NAME=...'"; \
exit 1; \
fi
endef

.PHONY: lock-provider workspace init plan apply clean destroy output tf-%

lock-provider:
Expand All @@ -17,17 +24,25 @@ workspace:
$(tf_cmd) workspace select -or-create $(ENVIRONMENT) && echo "Switched to workspace/environment: $(ENVIRONMENT)"

init:
$(require_bucket_name)
$(tf_cmd) init $(tf_state) -upgrade $(tf_vars)

init-reconfigure:
$(require_bucket_name)
$(tf_cmd) init $(tf_state) -upgrade $(tf_vars) -reconfigure

plan: workspace
$(tf_cmd) plan $(tf_vars)

plan-ci: workspace
$(tf_cmd) plan $(tf_vars) -out=tfplan -input=false

apply: workspace
$(tf_cmd) apply $(tf_vars) -auto-approve

apply-ci: workspace
$(tf_cmd) apply -input=false tfplan

clean:
rm -rf build .terraform upload-key

Expand Down
1 change: 1 addition & 0 deletions infrastructure/account/auto_ops_policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@
"iam:DeleteGroupPolicy",
"iam:ListMFADeviceTags",
"elasticache:*",
"shield:*",
"iam:DeletePolicyVersion",
"chatbot:*"
],
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/account/recordprocessor_ecr_repo.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ resource "aws_ecr_lifecycle_policy" "recordprocessor_repository_lifecycle_policy
"rules": [
{
"rulePriority": 1,
"description": "Keep last 10 images",
"description": "Keep last 10 images.",
"selection": {
"tagStatus": "any",
"countType": "imageCountMoreThan",
Expand Down
24 changes: 24 additions & 0 deletions utilities/scripts/resolve_account_terraform_state_bucket.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash

set -euo pipefail

read -r configured_bucket <<< "${CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET:-}"
read -r state_bucket_environment <<< "${ACCOUNT_TERRAFORM_STATE_ENVIRONMENT:-}"

[ -n "$configured_bucket" ] && printf '%s\n' "$configured_bucket" && exit 0

[ -n "$state_bucket_environment" ] || {
echo "ACCOUNT_TERRAFORM_STATE_ENVIRONMENT must be set when ACCOUNT_TERRAFORM_STATE_BUCKET is not configured." >&2
exit 1
}

case "$state_bucket_environment" in
internal-dev|internal-qa|preprod|prod)
;;
*)
echo "ACCOUNT_TERRAFORM_STATE_ENVIRONMENT must be one of: internal-dev, internal-qa, preprod, prod." >&2
exit 1
;;
esac

printf 'immunisation-%s-terraform-state-files\n' "$state_bucket_environment"
Loading