From 056106334968deb8db69af334e0b73d599ebc6ad Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Tue, 7 Apr 2026 12:42:43 +0100 Subject: [PATCH 01/27] Add account-level Terraform workflow and integrate with existing CI/CD pipelines - Introduced a new GitHub Actions workflow for managing account-level Terraform operations, including planning, manual approval, and applying changes. - Updated `continuous-deployment.yml` and `pr-deploy-and-test.yml` to utilize the new account-terraform workflow, ensuring infrastructure changes are detected and processed. - Enhanced Makefile with new targets for CI-specific Terraform commands. - Added a script to resolve the Terraform state bucket dynamically based on the configured environment. - Updated IAM policy to include permissions for AWS Shield operations. --- .github/workflows/account-terraform.yml | 173 ++++++++++++++++++ .github/workflows/continuous-deployment.yml | 10 +- .github/workflows/pr-deploy-and-test.yml | 12 +- infrastructure/account/Makefile | 6 + infrastructure/account/auto_ops_policy.json | 1 + .../resolve_account_terraform_state_bucket.sh | 23 +++ 6 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/account-terraform.yml create mode 100644 utilities/scripts/resolve_account_terraform_state_bucket.sh diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml new file mode 100644 index 0000000000..da98751037 --- /dev/null +++ b/.github/workflows/account-terraform.yml @@ -0,0 +1,173 @@ +name: Account Terraform + +on: + workflow_call: + inputs: + base_sha: + required: true + type: string + head_sha: + required: true + type: string + environment: + required: true + type: string + artifact_name: + required: true + type: string + +concurrency: + group: account-terraform-${{ github.repository }}-${{ inputs.environment }} + cancel-in-progress: false + +jobs: + detect-account-infra-changes: + runs-on: ubuntu-latest + outputs: + account_infra_changed: ${{ steps.diff.outputs.account_infra_changed }} + steps: + - name: Checkout + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + with: + fetch-depth: 0 + + - name: Detect account terraform changes + id: diff + run: | + base_sha="${{ inputs.base_sha }}" + if [ -z "$base_sha" ] || [ "$base_sha" = "0000000000000000000000000000000000000000" ]; then + base_sha=$(git rev-parse HEAD~1) + fi + + changed_files=$(git diff --name-only "$base_sha" "${{ inputs.head_sha }}") + if echo "$changed_files" | grep -q '^infrastructure/account/'; then + echo "account_infra_changed=true" >> "$GITHUB_OUTPUT" + else + echo "account_infra_changed=false" >> "$GITHUB_OUTPUT" + fi + + account-terraform-plan: + permissions: + id-token: write + contents: read + needs: [detect-account-infra-changes] + if: ${{ needs.detect-account-infra-changes.outputs.account_infra_changed == 'true' }} + runs-on: ubuntu-latest + environment: + name: ${{ inputs.environment }} + steps: + - name: Checkout + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + + - 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: github-actions + + - uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85 + with: + terraform_version: "1.12.2" + + - name: Resolve account terraform state bucket + id: account-state-bucket + env: + CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET: ${{ vars.ACCOUNT_TERRAFORM_STATE_BUCKET }} + run: | + echo "bucket_name=$(bash ./utilities/scripts/resolve_account_terraform_state_bucket.sh)" >> "$GITHUB_OUTPUT" + + - name: Terraform Init (account) + working-directory: infrastructure/account + run: make init ENVIRONMENT=${{ inputs.environment }} BUCKET_NAME=${{ steps.account-state-bucket.outputs.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: ${{ !failure() }} + working-directory: infrastructure/account + run: make plan-ci ENVIRONMENT=${{ inputs.environment }} + + - name: Save Account Terraform Plan + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f + with: + name: ${{ inputs.artifact_name }} + path: infrastructure/account/tfplan + + account-terraform-manual-approval: + needs: [account-terraform-plan] + if: ${{ !cancelled() && needs.account-terraform-plan.result == 'success' }} + runs-on: ubuntu-latest + environment: + name: account-level-infra-approval + steps: + - name: Await manual approval + run: echo "Waiting for account-level infrastructure approval." + + account-terraform-apply: + permissions: + id-token: write + contents: read + needs: [account-terraform-manual-approval] + if: ${{ !cancelled() && needs.account-terraform-manual-approval.result == 'success' }} + runs-on: ubuntu-latest + environment: + name: ${{ inputs.environment }} + steps: + - name: Checkout + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + + - 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: github-actions + + - uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85 + with: + terraform_version: "1.12.2" + + - name: Resolve account terraform state bucket + id: account-state-bucket + env: + CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET: ${{ vars.ACCOUNT_TERRAFORM_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: ${{ inputs.artifact_name }} + path: infrastructure/account + + - name: Terraform Init (account) + working-directory: infrastructure/account + run: make init ENVIRONMENT=${{ inputs.environment }} BUCKET_NAME=${{ steps.account-state-bucket.outputs.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=${{ inputs.environment }} + + account-terraform-not-required: + needs: [detect-account-infra-changes] + if: ${{ needs.detect-account-infra-changes.outputs.account_infra_changed != 'true' }} + runs-on: ubuntu-latest + steps: + - name: Skip account terraform + run: echo "No account-level infrastructure changes detected." + + account-terraform-ready: + needs: + - account-terraform-plan + - account-terraform-manual-approval + - account-terraform-apply + - account-terraform-not-required + if: ${{ always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }} + runs-on: ubuntu-latest + steps: + - name: Account terraform stage complete + run: echo "Account terraform stage complete." diff --git a/.github/workflows/continuous-deployment.yml b/.github/workflows/continuous-deployment.yml index db8914740c..ec738d43d4 100644 --- a/.github/workflows/continuous-deployment.yml +++ b/.github/workflows/continuous-deployment.yml @@ -15,8 +15,16 @@ jobs: secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + account-terraform: + uses: ./.github/workflows/account-terraform.yml + with: + base_sha: ${{ github.event.before }} + head_sha: ${{ github.sha }} + environment: dev + artifact_name: dev-account-tfplan + deploy-internal-dev-backend: - needs: [run-quality-checks] + needs: [run-quality-checks, account-terraform] uses: ./.github/workflows/deploy-backend.yml with: apigee_environment: internal-dev diff --git a/.github/workflows/pr-deploy-and-test.yml b/.github/workflows/pr-deploy-and-test.yml index 573b15a0c8..4142d2bd4a 100644 --- a/.github/workflows/pr-deploy-and-test.yml +++ b/.github/workflows/pr-deploy-and-test.yml @@ -14,14 +14,22 @@ jobs: secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + account-terraform: + uses: ./.github/workflows/account-terraform.yml + with: + base_sha: ${{ github.event.pull_request.base.sha }} + head_sha: ${{ github.event.pull_request.head.sha }} + environment: dev + artifact_name: pr-dev-account-tfplan + deploy-pr-backend: - needs: [run-quality-checks] + needs: [run-quality-checks, account-terraform] if: ${{ always() && !failure() && !cancelled() }} uses: ./.github/workflows/deploy-backend.yml 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.pull_request.base.sha }} diff_head_sha: ${{ github.event.pull_request.head.sha }} run_diff_check: ${{ github.event.action == 'synchronize' }} create_mns_subscription: true diff --git a/infrastructure/account/Makefile b/infrastructure/account/Makefile index fd539f57e2..3dacbd8781 100644 --- a/infrastructure/account/Makefile +++ b/infrastructure/account/Makefile @@ -25,9 +25,15 @@ init-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 $(tf_vars) -input=false tfplan + clean: rm -rf build .terraform upload-key diff --git a/infrastructure/account/auto_ops_policy.json b/infrastructure/account/auto_ops_policy.json index a3ae6e12ef..1a8b6bd1e6 100644 --- a/infrastructure/account/auto_ops_policy.json +++ b/infrastructure/account/auto_ops_policy.json @@ -214,6 +214,7 @@ "iam:DeleteGroupPolicy", "iam:ListMFADeviceTags", "elasticache:*", + "shield:*", "iam:DeletePolicyVersion", "chatbot:*" ], diff --git a/utilities/scripts/resolve_account_terraform_state_bucket.sh b/utilities/scripts/resolve_account_terraform_state_bucket.sh new file mode 100644 index 0000000000..ef6fba763a --- /dev/null +++ b/utilities/scripts/resolve_account_terraform_state_bucket.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -o nounset errexit pipefail + +if [ -n "${CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET:-}" ]; then + printf '%s\n' "$CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET" + exit 0 +fi + +mapfile -t buckets < <( + aws s3api list-buckets --query 'Buckets[].Name' --output text | + tr '\t' '\n' | + grep -E '^immunisation-dev-terraform-state(-files)?$' +) + +if [ "${#buckets[@]}" -ne 1 ]; then + echo "Expected exactly 1 dev account terraform state bucket, found ${#buckets[@]}." >&2 + echo "Set repo/environment variable ACCOUNT_TERRAFORM_STATE_BUCKET to remove ambiguity." >&2 + printf '%s\n' "${buckets[@]}" >&2 + exit 1 +fi + +printf '%s\n' "${buckets[0]}" From 283a3a1cac4796e596868e2526dc724273cee31f Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Tue, 7 Apr 2026 12:46:36 +0100 Subject: [PATCH 02/27] Add bucket name validation to Makefile and improve bucket resolution script - Introduced a new function in the Makefile to ensure the BUCKET_NAME variable is set before executing Terraform commands. - Updated the bucket resolution script to trim whitespace from the CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET variable, enhancing reliability in detecting the configured bucket. --- infrastructure/account/Makefile | 9 +++++++++ .../scripts/resolve_account_terraform_state_bucket.sh | 6 ++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/infrastructure/account/Makefile b/infrastructure/account/Makefile index 3dacbd8781..f31e8ebe33 100644 --- a/infrastructure/account/Makefile +++ b/infrastructure/account/Makefile @@ -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: @@ -17,9 +24,11 @@ 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 diff --git a/utilities/scripts/resolve_account_terraform_state_bucket.sh b/utilities/scripts/resolve_account_terraform_state_bucket.sh index ef6fba763a..17e4210629 100644 --- a/utilities/scripts/resolve_account_terraform_state_bucket.sh +++ b/utilities/scripts/resolve_account_terraform_state_bucket.sh @@ -2,8 +2,10 @@ set -o nounset errexit pipefail -if [ -n "${CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET:-}" ]; then - printf '%s\n' "$CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET" +configured_bucket="$(printf '%s' "${CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET:-}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + +if [ -n "$configured_bucket" ]; then + printf '%s\n' "$configured_bucket" exit 0 fi From af6e116701754887449ad433fbbbaef571d24dc7 Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Tue, 7 Apr 2026 12:48:44 +0100 Subject: [PATCH 03/27] Enhance bucket name resolution with validation in account-terraform workflow - Added validation to ensure the resolved bucket name is not empty, improving error handling in the GitHub Actions workflow. - Updated the script execution to store the bucket name in a variable before outputting, enhancing clarity and maintainability. --- .github/workflows/account-terraform.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml index da98751037..5b7a72bc75 100644 --- a/.github/workflows/account-terraform.yml +++ b/.github/workflows/account-terraform.yml @@ -75,7 +75,12 @@ jobs: env: CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET: ${{ vars.ACCOUNT_TERRAFORM_STATE_BUCKET }} run: | - echo "bucket_name=$(bash ./utilities/scripts/resolve_account_terraform_state_bucket.sh)" >> "$GITHUB_OUTPUT" + bucket_name="$(bash ./utilities/scripts/resolve_account_terraform_state_bucket.sh)" + if [ -z "$bucket_name" ]; then + echo "Resolved empty account terraform state bucket." >&2 + exit 1 + fi + echo "bucket_name=$bucket_name" >> "$GITHUB_OUTPUT" - name: Terraform Init (account) working-directory: infrastructure/account @@ -133,7 +138,12 @@ jobs: env: CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET: ${{ vars.ACCOUNT_TERRAFORM_STATE_BUCKET }} run: | - echo "bucket_name=$(bash ./utilities/scripts/resolve_account_terraform_state_bucket.sh)" >> "$GITHUB_OUTPUT" + bucket_name="$(bash ./utilities/scripts/resolve_account_terraform_state_bucket.sh)" + if [ -z "$bucket_name" ]; then + echo "Resolved empty account terraform state bucket." >&2 + exit 1 + fi + echo "bucket_name=$bucket_name" >> "$GITHUB_OUTPUT" - name: Retrieve Account Terraform Plan uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c From 2b6d8b20ca389093cc603044486090b28173bb0d Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Tue, 7 Apr 2026 13:07:03 +0100 Subject: [PATCH 04/27] Add state bucket environment input to workflows and update bucket resolution script - Introduced a new optional input `state_bucket_environment` in the `account-terraform.yml` workflow to allow dynamic configuration. - Updated `continuous-deployment.yml` and `pr-deploy-and-test.yml` workflows to set the `state_bucket_environment` for internal development. - Enhanced the bucket resolution script to utilize the `ACCOUNT_TERRAFORM_STATE_ENVIRONMENT` variable for improved bucket naming based on the environment. --- .github/workflows/account-terraform.yml | 6 ++++++ .github/workflows/continuous-deployment.yml | 1 + .github/workflows/pr-deploy-and-test.yml | 1 + utilities/scripts/resolve_account_terraform_state_bucket.sh | 6 ++++++ 4 files changed, 14 insertions(+) diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml index 5b7a72bc75..905445277e 100644 --- a/.github/workflows/account-terraform.yml +++ b/.github/workflows/account-terraform.yml @@ -12,6 +12,10 @@ on: environment: required: true type: string + state_bucket_environment: + required: false + type: string + default: "" artifact_name: required: true type: string @@ -74,6 +78,7 @@ jobs: id: account-state-bucket env: CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET: ${{ vars.ACCOUNT_TERRAFORM_STATE_BUCKET }} + ACCOUNT_TERRAFORM_STATE_ENVIRONMENT: ${{ inputs.state_bucket_environment }} run: | bucket_name="$(bash ./utilities/scripts/resolve_account_terraform_state_bucket.sh)" if [ -z "$bucket_name" ]; then @@ -137,6 +142,7 @@ jobs: id: account-state-bucket env: CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET: ${{ vars.ACCOUNT_TERRAFORM_STATE_BUCKET }} + ACCOUNT_TERRAFORM_STATE_ENVIRONMENT: ${{ inputs.state_bucket_environment }} run: | bucket_name="$(bash ./utilities/scripts/resolve_account_terraform_state_bucket.sh)" if [ -z "$bucket_name" ]; then diff --git a/.github/workflows/continuous-deployment.yml b/.github/workflows/continuous-deployment.yml index ec738d43d4..771fd5756b 100644 --- a/.github/workflows/continuous-deployment.yml +++ b/.github/workflows/continuous-deployment.yml @@ -21,6 +21,7 @@ jobs: base_sha: ${{ github.event.before }} head_sha: ${{ github.sha }} environment: dev + state_bucket_environment: internal-dev artifact_name: dev-account-tfplan deploy-internal-dev-backend: diff --git a/.github/workflows/pr-deploy-and-test.yml b/.github/workflows/pr-deploy-and-test.yml index 4142d2bd4a..a5e7ddc7cc 100644 --- a/.github/workflows/pr-deploy-and-test.yml +++ b/.github/workflows/pr-deploy-and-test.yml @@ -20,6 +20,7 @@ jobs: base_sha: ${{ github.event.pull_request.base.sha }} head_sha: ${{ github.event.pull_request.head.sha }} environment: dev + state_bucket_environment: internal-dev artifact_name: pr-dev-account-tfplan deploy-pr-backend: diff --git a/utilities/scripts/resolve_account_terraform_state_bucket.sh b/utilities/scripts/resolve_account_terraform_state_bucket.sh index 17e4210629..3fecd28f9d 100644 --- a/utilities/scripts/resolve_account_terraform_state_bucket.sh +++ b/utilities/scripts/resolve_account_terraform_state_bucket.sh @@ -3,12 +3,18 @@ set -o nounset errexit pipefail configured_bucket="$(printf '%s' "${CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET:-}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" +state_bucket_environment="$(printf '%s' "${ACCOUNT_TERRAFORM_STATE_ENVIRONMENT:-}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" if [ -n "$configured_bucket" ]; then printf '%s\n' "$configured_bucket" exit 0 fi +if [ -n "$state_bucket_environment" ]; then + printf 'immunisation-%s-terraform-state-files\n' "$state_bucket_environment" + exit 0 +fi + mapfile -t buckets < <( aws s3api list-buckets --query 'Buckets[].Name' --output text | tr '\t' '\n' | From 5e2a874aee57d41342f9ff5c86a2c4fb6d3ca495 Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Tue, 7 Apr 2026 13:17:54 +0100 Subject: [PATCH 05/27] Enhance Terraform workspace handling in workflows and bucket resolution script - Added `ACCOUNT_TERRAFORM_WORKSPACE` input to the `account-terraform.yml` workflow for improved workspace management. - Updated the bucket resolution script to validate the `ACCOUNT_TERRAFORM_WORKSPACE` variable and incorporate it into the state key for better bucket identification. - Enhanced error handling to ensure the workspace is set before proceeding with bucket resolution. --- .github/workflows/account-terraform.yml | 2 + .../resolve_account_terraform_state_bucket.sh | 50 ++++++++++++++++--- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml index 905445277e..8b1ff76102 100644 --- a/.github/workflows/account-terraform.yml +++ b/.github/workflows/account-terraform.yml @@ -79,6 +79,7 @@ jobs: env: CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET: ${{ vars.ACCOUNT_TERRAFORM_STATE_BUCKET }} ACCOUNT_TERRAFORM_STATE_ENVIRONMENT: ${{ inputs.state_bucket_environment }} + ACCOUNT_TERRAFORM_WORKSPACE: ${{ inputs.environment }} run: | bucket_name="$(bash ./utilities/scripts/resolve_account_terraform_state_bucket.sh)" if [ -z "$bucket_name" ]; then @@ -143,6 +144,7 @@ jobs: env: CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET: ${{ vars.ACCOUNT_TERRAFORM_STATE_BUCKET }} ACCOUNT_TERRAFORM_STATE_ENVIRONMENT: ${{ inputs.state_bucket_environment }} + ACCOUNT_TERRAFORM_WORKSPACE: ${{ inputs.environment }} run: | bucket_name="$(bash ./utilities/scripts/resolve_account_terraform_state_bucket.sh)" if [ -z "$bucket_name" ]; then diff --git a/utilities/scripts/resolve_account_terraform_state_bucket.sh b/utilities/scripts/resolve_account_terraform_state_bucket.sh index 3fecd28f9d..5cbcddd348 100644 --- a/utilities/scripts/resolve_account_terraform_state_bucket.sh +++ b/utilities/scripts/resolve_account_terraform_state_bucket.sh @@ -4,28 +4,64 @@ set -o nounset errexit pipefail configured_bucket="$(printf '%s' "${CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET:-}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" state_bucket_environment="$(printf '%s' "${ACCOUNT_TERRAFORM_STATE_ENVIRONMENT:-}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" +terraform_workspace="$(printf '%s' "${ACCOUNT_TERRAFORM_WORKSPACE:-}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" if [ -n "$configured_bucket" ]; then printf '%s\n' "$configured_bucket" exit 0 fi +if [ -z "$terraform_workspace" ]; then + echo "ACCOUNT_TERRAFORM_WORKSPACE must be set." >&2 + exit 1 +fi + +state_key="env:/${terraform_workspace}/state" + +bucket_contains_state() { + local bucket_name="$1" + aws s3api head-object --bucket "$bucket_name" --key "$state_key" >/dev/null 2>&1 +} + +candidate_buckets=() + if [ -n "$state_bucket_environment" ]; then - printf 'immunisation-%s-terraform-state-files\n' "$state_bucket_environment" - exit 0 + candidate_buckets+=("immunisation-${state_bucket_environment}-terraform-state-files") fi +candidate_buckets+=("immunisation-terraform-state-files") + +for candidate_bucket in "${candidate_buckets[@]}"; do + if bucket_contains_state "$candidate_bucket"; then + printf '%s\n' "$candidate_bucket" + exit 0 + fi +done + mapfile -t buckets < <( aws s3api list-buckets --query 'Buckets[].Name' --output text | tr '\t' '\n' | - grep -E '^immunisation-dev-terraform-state(-files)?$' + grep -E '^immunisation(-[a-z0-9-]+)?-terraform-state(-files)?$' ) -if [ "${#buckets[@]}" -ne 1 ]; then - echo "Expected exactly 1 dev account terraform state bucket, found ${#buckets[@]}." >&2 +matching_buckets=() + +for bucket in "${buckets[@]}"; do + if bucket_contains_state "$bucket"; then + matching_buckets+=("$bucket") + fi +done + +if [ "${#matching_buckets[@]}" -ne 1 ]; then + echo "Expected exactly 1 terraform state bucket containing ${state_key}, found ${#matching_buckets[@]}." >&2 echo "Set repo/environment variable ACCOUNT_TERRAFORM_STATE_BUCKET to remove ambiguity." >&2 - printf '%s\n' "${buckets[@]}" >&2 + if [ "${#matching_buckets[@]}" -gt 0 ]; then + printf '%s\n' "${matching_buckets[@]}" >&2 + else + echo "Checked buckets:" >&2 + printf '%s\n' "${buckets[@]}" >&2 + fi exit 1 fi -printf '%s\n' "${buckets[0]}" +printf '%s\n' "${matching_buckets[0]}" From c2d0559ff16c98f32eb6f423ebaae0a76084544f Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Tue, 7 Apr 2026 13:27:38 +0100 Subject: [PATCH 06/27] Enhance bucket resolution script and workflow logging - Added a new function to the bucket resolution script to check if the bucket matches the account state, improving accuracy in identifying the correct bucket. - Updated the workflow to log the resolved bucket name, enhancing visibility during execution and debugging. --- .github/workflows/account-terraform.yml | 2 ++ .../resolve_account_terraform_state_bucket.sh | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml index 8b1ff76102..88c37fa65d 100644 --- a/.github/workflows/account-terraform.yml +++ b/.github/workflows/account-terraform.yml @@ -86,6 +86,7 @@ jobs: echo "Resolved empty account terraform state bucket." >&2 exit 1 fi + echo "Resolved account terraform state bucket: $bucket_name" echo "bucket_name=$bucket_name" >> "$GITHUB_OUTPUT" - name: Terraform Init (account) @@ -151,6 +152,7 @@ jobs: echo "Resolved empty account terraform state bucket." >&2 exit 1 fi + echo "Resolved account terraform state bucket: $bucket_name" echo "bucket_name=$bucket_name" >> "$GITHUB_OUTPUT" - name: Retrieve Account Terraform Plan diff --git a/utilities/scripts/resolve_account_terraform_state_bucket.sh b/utilities/scripts/resolve_account_terraform_state_bucket.sh index 5cbcddd348..af2e47edce 100644 --- a/utilities/scripts/resolve_account_terraform_state_bucket.sh +++ b/utilities/scripts/resolve_account_terraform_state_bucket.sh @@ -23,6 +23,17 @@ bucket_contains_state() { aws s3api head-object --bucket "$bucket_name" --key "$state_key" >/dev/null 2>&1 } +bucket_matches_account_state() { + local bucket_name="$1" + + if ! bucket_contains_state "$bucket_name"; then + return 1 + fi + + aws s3 cp "s3://${bucket_name}/${state_key}" - 2>/dev/null | + grep -Eq '"address":"aws_iam_role\.auto_ops"|"address":"aws_iam_openid_connect_provider\.github"|"address":"aws_ecr_repository\.recordprocessor_repository"' +} + candidate_buckets=() if [ -n "$state_bucket_environment" ]; then @@ -32,7 +43,7 @@ fi candidate_buckets+=("immunisation-terraform-state-files") for candidate_bucket in "${candidate_buckets[@]}"; do - if bucket_contains_state "$candidate_bucket"; then + if bucket_matches_account_state "$candidate_bucket"; then printf '%s\n' "$candidate_bucket" exit 0 fi @@ -47,13 +58,13 @@ mapfile -t buckets < <( matching_buckets=() for bucket in "${buckets[@]}"; do - if bucket_contains_state "$bucket"; then + if bucket_matches_account_state "$bucket"; then matching_buckets+=("$bucket") fi done if [ "${#matching_buckets[@]}" -ne 1 ]; then - echo "Expected exactly 1 terraform state bucket containing ${state_key}, found ${#matching_buckets[@]}." >&2 + echo "Expected exactly 1 terraform state bucket containing account state for ${state_key}, found ${#matching_buckets[@]}." >&2 echo "Set repo/environment variable ACCOUNT_TERRAFORM_STATE_BUCKET to remove ambiguity." >&2 if [ "${#matching_buckets[@]}" -gt 0 ]; then printf '%s\n' "${matching_buckets[@]}" >&2 From 2ce0cdcd1728351effc04b53afc3924367be5644 Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Tue, 7 Apr 2026 14:03:29 +0100 Subject: [PATCH 07/27] Refactor bucket resolution script to output bucket name and exit - Updated the `resolve_account_terraform_state_bucket.sh` script to print the formatted bucket name based on the `state_bucket_environment` variable. - Removed the previous logic for adding candidate buckets, simplifying the script's flow when the environment variable is set. --- utilities/scripts/resolve_account_terraform_state_bucket.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utilities/scripts/resolve_account_terraform_state_bucket.sh b/utilities/scripts/resolve_account_terraform_state_bucket.sh index af2e47edce..61e6317090 100644 --- a/utilities/scripts/resolve_account_terraform_state_bucket.sh +++ b/utilities/scripts/resolve_account_terraform_state_bucket.sh @@ -37,7 +37,8 @@ bucket_matches_account_state() { candidate_buckets=() if [ -n "$state_bucket_environment" ]; then - candidate_buckets+=("immunisation-${state_bucket_environment}-terraform-state-files") + printf 'immunisation-%s-terraform-state-files\n' "$state_bucket_environment" + exit 0 fi candidate_buckets+=("immunisation-terraform-state-files") From 85b12bc966931b28058e21395ce6477162023dd8 Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Tue, 7 Apr 2026 14:25:50 +0100 Subject: [PATCH 08/27] Refactor bucket resolution script to improve whitespace handling and error messaging - Simplified the script by introducing a `trim` function to handle whitespace for the `CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET` and `ACCOUNT_TERRAFORM_STATE_ENVIRONMENT` variables. - Enhanced error handling to ensure the `ACCOUNT_TERRAFORM_STATE_ENVIRONMENT` variable is set when the configured bucket is not provided, improving clarity in user feedback. --- .../resolve_account_terraform_state_bucket.sh | 75 +++---------------- 1 file changed, 9 insertions(+), 66 deletions(-) diff --git a/utilities/scripts/resolve_account_terraform_state_bucket.sh b/utilities/scripts/resolve_account_terraform_state_bucket.sh index 61e6317090..46ed5f102c 100644 --- a/utilities/scripts/resolve_account_terraform_state_bucket.sh +++ b/utilities/scripts/resolve_account_terraform_state_bucket.sh @@ -2,78 +2,21 @@ set -o nounset errexit pipefail -configured_bucket="$(printf '%s' "${CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET:-}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" -state_bucket_environment="$(printf '%s' "${ACCOUNT_TERRAFORM_STATE_ENVIRONMENT:-}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" -terraform_workspace="$(printf '%s' "${ACCOUNT_TERRAFORM_WORKSPACE:-}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" - -if [ -n "$configured_bucket" ]; then - printf '%s\n' "$configured_bucket" - exit 0 -fi - -if [ -z "$terraform_workspace" ]; then - echo "ACCOUNT_TERRAFORM_WORKSPACE must be set." >&2 - exit 1 -fi - -state_key="env:/${terraform_workspace}/state" - -bucket_contains_state() { - local bucket_name="$1" - aws s3api head-object --bucket "$bucket_name" --key "$state_key" >/dev/null 2>&1 +trim() { + printf '%s' "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' } -bucket_matches_account_state() { - local bucket_name="$1" - - if ! bucket_contains_state "$bucket_name"; then - return 1 - fi +configured_bucket="$(trim "${CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET:-}")" +state_bucket_environment="$(trim "${ACCOUNT_TERRAFORM_STATE_ENVIRONMENT:-}")" - aws s3 cp "s3://${bucket_name}/${state_key}" - 2>/dev/null | - grep -Eq '"address":"aws_iam_role\.auto_ops"|"address":"aws_iam_openid_connect_provider\.github"|"address":"aws_ecr_repository\.recordprocessor_repository"' -} - -candidate_buckets=() - -if [ -n "$state_bucket_environment" ]; then - printf 'immunisation-%s-terraform-state-files\n' "$state_bucket_environment" +if [ -n "$configured_bucket" ]; then + printf '%s\n' "$configured_bucket" exit 0 fi -candidate_buckets+=("immunisation-terraform-state-files") - -for candidate_bucket in "${candidate_buckets[@]}"; do - if bucket_matches_account_state "$candidate_bucket"; then - printf '%s\n' "$candidate_bucket" - exit 0 - fi -done - -mapfile -t buckets < <( - aws s3api list-buckets --query 'Buckets[].Name' --output text | - tr '\t' '\n' | - grep -E '^immunisation(-[a-z0-9-]+)?-terraform-state(-files)?$' -) - -matching_buckets=() - -for bucket in "${buckets[@]}"; do - if bucket_matches_account_state "$bucket"; then - matching_buckets+=("$bucket") - fi -done - -if [ "${#matching_buckets[@]}" -ne 1 ]; then - echo "Expected exactly 1 terraform state bucket containing account state for ${state_key}, found ${#matching_buckets[@]}." >&2 - echo "Set repo/environment variable ACCOUNT_TERRAFORM_STATE_BUCKET to remove ambiguity." >&2 - if [ "${#matching_buckets[@]}" -gt 0 ]; then - printf '%s\n' "${matching_buckets[@]}" >&2 - else - echo "Checked buckets:" >&2 - printf '%s\n' "${buckets[@]}" >&2 - fi +if [ -z "$state_bucket_environment" ]; then + echo "ACCOUNT_TERRAFORM_STATE_ENVIRONMENT must be set when ACCOUNT_TERRAFORM_STATE_BUCKET is not configured." >&2 exit 1 fi -printf '%s\n' "${matching_buckets[0]}" +printf 'immunisation-%s-terraform-state-files\n' "$state_bucket_environment" From d93ba1bcb6c88b720b2d94c84a24ed15b9303c6f Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Tue, 7 Apr 2026 14:46:59 +0100 Subject: [PATCH 09/27] Update account-terraform workflow to conditionally set bucket name based on environment - Modified the `CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET` variable to use a conditional expression, allowing for a default bucket name when the environment is 'dev'. - Removed the `ACCOUNT_TERRAFORM_WORKSPACE` variable from the environment settings to streamline the workflow. --- .github/workflows/account-terraform.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml index 88c37fa65d..17becb65fb 100644 --- a/.github/workflows/account-terraform.yml +++ b/.github/workflows/account-terraform.yml @@ -77,9 +77,8 @@ jobs: - name: Resolve account terraform state bucket id: account-state-bucket env: - CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET: ${{ vars.ACCOUNT_TERRAFORM_STATE_BUCKET }} + 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_WORKSPACE: ${{ inputs.environment }} run: | bucket_name="$(bash ./utilities/scripts/resolve_account_terraform_state_bucket.sh)" if [ -z "$bucket_name" ]; then @@ -143,9 +142,8 @@ jobs: - name: Resolve account terraform state bucket id: account-state-bucket env: - CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET: ${{ vars.ACCOUNT_TERRAFORM_STATE_BUCKET }} + 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_WORKSPACE: ${{ inputs.environment }} run: | bucket_name="$(bash ./utilities/scripts/resolve_account_terraform_state_bucket.sh)" if [ -z "$bucket_name" ]; then From 90f4aec04357c9b0e920564de435fa7f1b981603 Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Wed, 8 Apr 2026 10:35:11 +0100 Subject: [PATCH 10/27] Refactor account-terraform workflow to streamline job dependencies and conditions - Consolidated the account-terraform jobs by removing unnecessary manual approval and not-required stages, simplifying the workflow. - Updated conditions for the account-terraform-apply job to ensure it only runs when infrastructure changes are detected. - Enhanced the account-terraform-plan job to skip execution when no changes are present, improving efficiency. --- .github/workflows/account-terraform.yml | 63 +++++++------------------ 1 file changed, 17 insertions(+), 46 deletions(-) diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml index 17becb65fb..d668f8db91 100644 --- a/.github/workflows/account-terraform.yml +++ b/.github/workflows/account-terraform.yml @@ -25,8 +25,13 @@ concurrency: cancel-in-progress: false jobs: - detect-account-infra-changes: + account-terraform-plan: + permissions: + id-token: write + contents: read runs-on: ubuntu-latest + environment: + name: ${{ inputs.environment }} outputs: account_infra_changed: ${{ steps.diff.outputs.account_infra_changed }} steps: @@ -50,20 +55,12 @@ jobs: echo "account_infra_changed=false" >> "$GITHUB_OUTPUT" fi - account-terraform-plan: - permissions: - id-token: write - contents: read - needs: [detect-account-infra-changes] - if: ${{ needs.detect-account-infra-changes.outputs.account_infra_changed == 'true' }} - runs-on: ubuntu-latest - environment: - name: ${{ inputs.environment }} - steps: - - name: Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + - name: Skip account terraform + if: ${{ steps.diff.outputs.account_infra_changed != 'true' }} + run: echo "No account-level infrastructure changes detected." - 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 @@ -71,11 +68,13 @@ jobs: role-session-name: github-actions - uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85 + if: ${{ steps.diff.outputs.account_infra_changed == 'true' }} with: terraform_version: "1.12.2" - name: Resolve account terraform state bucket id: account-state-bucket + if: ${{ steps.diff.outputs.account_infra_changed == 'true' }} 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 }} @@ -89,38 +88,30 @@ jobs: echo "bucket_name=$bucket_name" >> "$GITHUB_OUTPUT" - name: Terraform Init (account) + if: ${{ steps.diff.outputs.account_infra_changed == 'true' }} working-directory: infrastructure/account run: make init ENVIRONMENT=${{ inputs.environment }} BUCKET_NAME=${{ steps.account-state-bucket.outputs.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: ${{ !failure() }} + if: ${{ steps.diff.outputs.account_infra_changed == 'true' && !failure() }} working-directory: infrastructure/account run: make plan-ci ENVIRONMENT=${{ inputs.environment }} - name: Save Account Terraform Plan + if: ${{ steps.diff.outputs.account_infra_changed == 'true' }} uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f with: name: ${{ inputs.artifact_name }} path: infrastructure/account/tfplan - account-terraform-manual-approval: - needs: [account-terraform-plan] - if: ${{ !cancelled() && needs.account-terraform-plan.result == 'success' }} - runs-on: ubuntu-latest - environment: - name: account-level-infra-approval - steps: - - name: Await manual approval - run: echo "Waiting for account-level infrastructure approval." - account-terraform-apply: permissions: id-token: write contents: read - needs: [account-terraform-manual-approval] - if: ${{ !cancelled() && needs.account-terraform-manual-approval.result == 'success' }} + 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: ${{ inputs.environment }} @@ -169,23 +160,3 @@ jobs: if: ${{ !failure() }} working-directory: infrastructure/account run: make apply-ci ENVIRONMENT=${{ inputs.environment }} - - account-terraform-not-required: - needs: [detect-account-infra-changes] - if: ${{ needs.detect-account-infra-changes.outputs.account_infra_changed != 'true' }} - runs-on: ubuntu-latest - steps: - - name: Skip account terraform - run: echo "No account-level infrastructure changes detected." - - account-terraform-ready: - needs: - - account-terraform-plan - - account-terraform-manual-approval - - account-terraform-apply - - account-terraform-not-required - if: ${{ always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }} - runs-on: ubuntu-latest - steps: - - name: Account terraform stage complete - run: echo "Account terraform stage complete." From 7ed155ffe4c0e1d09e5ec22ae66e751cd642f091 Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Wed, 8 Apr 2026 10:38:16 +0100 Subject: [PATCH 11/27] Enhance account-terraform workflow to improve change detection logging - Updated the change detection logic to store and log specific files that have changed within the `infrastructure/account/` directory, enhancing visibility during workflow execution. - Ensured that the workflow outputs a clear indication of whether account infrastructure changes are present, improving debugging and tracking of modifications. --- .github/workflows/account-terraform.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml index d668f8db91..e791b05427 100644 --- a/.github/workflows/account-terraform.yml +++ b/.github/workflows/account-terraform.yml @@ -49,7 +49,9 @@ jobs: fi changed_files=$(git diff --name-only "$base_sha" "${{ inputs.head_sha }}") - if echo "$changed_files" | grep -q '^infrastructure/account/'; then + account_changed_files=$(echo "$changed_files" | grep '^infrastructure/account/' || true) + if [ -n "$account_changed_files" ]; then + echo "changes detected in files: $account_changed_files" echo "account_infra_changed=true" >> "$GITHUB_OUTPUT" else echo "account_infra_changed=false" >> "$GITHUB_OUTPUT" From a1fe5ea4e0a90dd2c404da56705b7b534842276f Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Wed, 8 Apr 2026 10:49:25 +0100 Subject: [PATCH 12/27] Update pr-deploy-and-test workflow to enhance SHA handling during synchronization - Modified the logic for setting `base_sha` and `diff_base_sha` to account for the 'synchronize' action, ensuring accurate detection of changes when pull requests are updated. - Improved the workflow's responsiveness to changes, enhancing overall efficiency in deployment and testing processes. --- .github/workflows/pr-deploy-and-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-deploy-and-test.yml b/.github/workflows/pr-deploy-and-test.yml index a5e7ddc7cc..db44fd2a01 100644 --- a/.github/workflows/pr-deploy-and-test.yml +++ b/.github/workflows/pr-deploy-and-test.yml @@ -17,7 +17,7 @@ jobs: account-terraform: uses: ./.github/workflows/account-terraform.yml with: - base_sha: ${{ github.event.pull_request.base.sha }} + base_sha: ${{ github.event.action == 'synchronize' && github.event.before || github.event.pull_request.base.sha }} head_sha: ${{ github.event.pull_request.head.sha }} environment: dev state_bucket_environment: internal-dev @@ -30,7 +30,7 @@ jobs: with: apigee_environment: internal-dev build_recordprocessor_image: ${{ github.event.action == 'opened' || github.event.action == 'reopened' }} - diff_base_sha: ${{ github.event.pull_request.base.sha }} + 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 From 60f978a87639f00e81f814fdb8006ee4c29d5a30 Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Wed, 8 Apr 2026 10:54:02 +0100 Subject: [PATCH 13/27] Enhance account-terraform workflow to improve environment variable handling - Added environment variables for base SHA, head SHA, and environment to streamline the Terraform job configurations. - Updated the workflow steps to utilize these environment variables, improving clarity and maintainability in the execution of Terraform commands. --- .github/workflows/account-terraform.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml index e791b05427..4fb69c7664 100644 --- a/.github/workflows/account-terraform.yml +++ b/.github/workflows/account-terraform.yml @@ -32,6 +32,10 @@ jobs: runs-on: ubuntu-latest environment: name: ${{ inputs.environment }} + env: + ACCOUNT_TERRAFORM_BASE_SHA: ${{ inputs.base_sha }} + ACCOUNT_TERRAFORM_HEAD_SHA: ${{ inputs.head_sha }} + ACCOUNT_TERRAFORM_ENVIRONMENT: ${{ inputs.environment }} outputs: account_infra_changed: ${{ steps.diff.outputs.account_infra_changed }} steps: @@ -43,12 +47,12 @@ jobs: - name: Detect account terraform changes id: diff run: | - base_sha="${{ inputs.base_sha }}" + base_sha="$ACCOUNT_TERRAFORM_BASE_SHA" if [ -z "$base_sha" ] || [ "$base_sha" = "0000000000000000000000000000000000000000" ]; then base_sha=$(git rev-parse HEAD~1) fi - changed_files=$(git diff --name-only "$base_sha" "${{ inputs.head_sha }}") + changed_files=$(git diff --name-only "$base_sha" "$ACCOUNT_TERRAFORM_HEAD_SHA") account_changed_files=$(echo "$changed_files" | grep '^infrastructure/account/' || true) if [ -n "$account_changed_files" ]; then echo "changes detected in files: $account_changed_files" @@ -92,14 +96,14 @@ jobs: - name: Terraform Init (account) if: ${{ steps.diff.outputs.account_infra_changed == 'true' }} working-directory: infrastructure/account - run: make init ENVIRONMENT=${{ inputs.environment }} BUCKET_NAME=${{ steps.account-state-bucket.outputs.bucket_name }} + run: make init ENVIRONMENT="$ACCOUNT_TERRAFORM_ENVIRONMENT" BUCKET_NAME="${{ steps.account-state-bucket.outputs.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=${{ inputs.environment }} + run: make plan-ci ENVIRONMENT="$ACCOUNT_TERRAFORM_ENVIRONMENT" - name: Save Account Terraform Plan if: ${{ steps.diff.outputs.account_infra_changed == 'true' }} @@ -117,6 +121,8 @@ jobs: runs-on: ubuntu-latest environment: name: ${{ inputs.environment }} + env: + ACCOUNT_TERRAFORM_ENVIRONMENT: ${{ inputs.environment }} steps: - name: Checkout uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 @@ -154,11 +160,11 @@ jobs: - name: Terraform Init (account) working-directory: infrastructure/account - run: make init ENVIRONMENT=${{ inputs.environment }} BUCKET_NAME=${{ steps.account-state-bucket.outputs.bucket_name }} + run: make init ENVIRONMENT="$ACCOUNT_TERRAFORM_ENVIRONMENT" BUCKET_NAME="${{ steps.account-state-bucket.outputs.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=${{ inputs.environment }} + run: make apply-ci ENVIRONMENT="$ACCOUNT_TERRAFORM_ENVIRONMENT" From 9860152a313f56d4412552bcce72db310c80f79d Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Wed, 8 Apr 2026 10:58:09 +0100 Subject: [PATCH 14/27] Enhance account-terraform workflow with improved SHA validation and environment variable usage - Added validation for base and head SHA variables to ensure they are correctly formatted before proceeding with change detection. - Updated the workflow to utilize an environment variable for the bucket name in Terraform initialization steps, enhancing clarity and maintainability. --- .github/workflows/account-terraform.yml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml index 4fb69c7664..4f25867974 100644 --- a/.github/workflows/account-terraform.yml +++ b/.github/workflows/account-terraform.yml @@ -48,11 +48,23 @@ jobs: 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 - changed_files=$(git diff --name-only "$base_sha" "$ACCOUNT_TERRAFORM_HEAD_SHA") + if ! printf '%s' "$base_sha" | grep -Eq '^[0-9a-f]{40}$'; then + echo "Invalid base SHA: $base_sha" >&2 + exit 1 + fi + + if ! printf '%s' "$head_sha" | grep -Eq '^[0-9a-f]{40}$'; then + echo "Invalid head SHA: $head_sha" >&2 + exit 1 + fi + + changed_files=$(git diff --name-only "$base_sha" "$head_sha") account_changed_files=$(echo "$changed_files" | grep '^infrastructure/account/' || true) if [ -n "$account_changed_files" ]; then echo "changes detected in files: $account_changed_files" @@ -96,7 +108,9 @@ jobs: - name: Terraform Init (account) if: ${{ steps.diff.outputs.account_infra_changed == 'true' }} working-directory: infrastructure/account - run: make init ENVIRONMENT="$ACCOUNT_TERRAFORM_ENVIRONMENT" BUCKET_NAME="${{ steps.account-state-bucket.outputs.bucket_name }}" + 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 @@ -160,7 +174,9 @@ jobs: - name: Terraform Init (account) working-directory: infrastructure/account - run: make init ENVIRONMENT="$ACCOUNT_TERRAFORM_ENVIRONMENT" BUCKET_NAME="${{ steps.account-state-bucket.outputs.bucket_name }}" + 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 From a23387ea03afcb9cec018d0b4d27dda328c88e4e Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Wed, 8 Apr 2026 11:09:14 +0100 Subject: [PATCH 15/27] Refactor account-terraform workflow and bucket resolution script for improved change detection and whitespace handling - Updated the SHA validation logic in the account-terraform workflow to use more efficient conditional expressions. - Enhanced change detection by directly capturing modified files in the `infrastructure/account/` directory, improving logging clarity. - Refined the `trim` function in the bucket resolution script to handle whitespace more effectively, ensuring accurate bucket name processing. --- .github/workflows/account-terraform.yml | 12 ++++++------ .../resolve_account_terraform_state_bucket.sh | 5 ++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml index 4f25867974..0fb8c81afb 100644 --- a/.github/workflows/account-terraform.yml +++ b/.github/workflows/account-terraform.yml @@ -50,24 +50,24 @@ jobs: base_sha="$ACCOUNT_TERRAFORM_BASE_SHA" head_sha="$ACCOUNT_TERRAFORM_HEAD_SHA" - if [ -z "$base_sha" ] || [ "$base_sha" = "0000000000000000000000000000000000000000" ]; then + if [[ -z "$base_sha" || "$base_sha" == "0000000000000000000000000000000000000000" ]]; then base_sha=$(git rev-parse HEAD~1) fi - if ! printf '%s' "$base_sha" | grep -Eq '^[0-9a-f]{40}$'; then + if [[ ! "$base_sha" =~ ^[0-9a-f]{40}$ ]]; then echo "Invalid base SHA: $base_sha" >&2 exit 1 fi - if ! printf '%s' "$head_sha" | grep -Eq '^[0-9a-f]{40}$'; then + if [[ ! "$head_sha" =~ ^[0-9a-f]{40}$ ]]; then echo "Invalid head SHA: $head_sha" >&2 exit 1 fi - changed_files=$(git diff --name-only "$base_sha" "$head_sha") - account_changed_files=$(echo "$changed_files" | grep '^infrastructure/account/' || true) + account_changed_files=$(git diff --name-only "$base_sha" "$head_sha" -- infrastructure/account) if [ -n "$account_changed_files" ]; then - echo "changes detected in files: $account_changed_files" + echo "changes detected in files:" + printf '%s\n' "$account_changed_files" echo "account_infra_changed=true" >> "$GITHUB_OUTPUT" else echo "account_infra_changed=false" >> "$GITHUB_OUTPUT" diff --git a/utilities/scripts/resolve_account_terraform_state_bucket.sh b/utilities/scripts/resolve_account_terraform_state_bucket.sh index 46ed5f102c..e78db0c57c 100644 --- a/utilities/scripts/resolve_account_terraform_state_bucket.sh +++ b/utilities/scripts/resolve_account_terraform_state_bucket.sh @@ -3,7 +3,10 @@ set -o nounset errexit pipefail trim() { - printf '%s' "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' + local value="${1-}" + value="${value#"${value%%[![:space:]]*}"}" + value="${value%"${value##*[![:space:]]}"}" + printf '%s' "$value" } configured_bucket="$(trim "${CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET:-}")" From 1abcf47ed68535facfe2c11d40fe38d671254865 Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Wed, 8 Apr 2026 11:22:55 +0100 Subject: [PATCH 16/27] Refactor account-terraform workflow and bucket resolution script for improved clarity and efficiency - Introduced environment variables for the configured bucket and state environment directly in the workflow, enhancing maintainability. - Simplified the bucket resolution logic in the script by condensing conditional checks, improving readability and error handling. - Streamlined the workflow steps to directly capture the bucket name output, reducing unnecessary complexity. --- .github/workflows/account-terraform.yml | 28 ++++--------------- .../resolve_account_terraform_state_bucket.sh | 9 ++---- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml index 0fb8c81afb..1f4249845b 100644 --- a/.github/workflows/account-terraform.yml +++ b/.github/workflows/account-terraform.yml @@ -24,6 +24,10 @@ 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 }} + jobs: account-terraform-plan: permissions: @@ -93,17 +97,7 @@ jobs: - name: Resolve account terraform state bucket id: account-state-bucket if: ${{ steps.diff.outputs.account_infra_changed == 'true' }} - 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 }} - run: | - bucket_name="$(bash ./utilities/scripts/resolve_account_terraform_state_bucket.sh)" - if [ -z "$bucket_name" ]; then - echo "Resolved empty account terraform state bucket." >&2 - exit 1 - fi - echo "Resolved account terraform state bucket: $bucket_name" - echo "bucket_name=$bucket_name" >> "$GITHUB_OUTPUT" + 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' }} @@ -154,17 +148,7 @@ jobs: - name: Resolve account terraform state bucket id: account-state-bucket - 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 }} - run: | - bucket_name="$(bash ./utilities/scripts/resolve_account_terraform_state_bucket.sh)" - if [ -z "$bucket_name" ]; then - echo "Resolved empty account terraform state bucket." >&2 - exit 1 - fi - echo "Resolved account terraform state bucket: $bucket_name" - echo "bucket_name=$bucket_name" >> "$GITHUB_OUTPUT" + 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 diff --git a/utilities/scripts/resolve_account_terraform_state_bucket.sh b/utilities/scripts/resolve_account_terraform_state_bucket.sh index e78db0c57c..92435c1609 100644 --- a/utilities/scripts/resolve_account_terraform_state_bucket.sh +++ b/utilities/scripts/resolve_account_terraform_state_bucket.sh @@ -12,14 +12,11 @@ trim() { configured_bucket="$(trim "${CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET:-}")" state_bucket_environment="$(trim "${ACCOUNT_TERRAFORM_STATE_ENVIRONMENT:-}")" -if [ -n "$configured_bucket" ]; then - printf '%s\n' "$configured_bucket" - exit 0 -fi +[ -n "$configured_bucket" ] && printf '%s\n' "$configured_bucket" && exit 0 -if [ -z "$state_bucket_environment" ]; then +[ -n "$state_bucket_environment" ] || { echo "ACCOUNT_TERRAFORM_STATE_ENVIRONMENT must be set when ACCOUNT_TERRAFORM_STATE_BUCKET is not configured." >&2 exit 1 -fi +} printf 'immunisation-%s-terraform-state-files\n' "$state_bucket_environment" From 4caeddb81b70974eda07742d01c7aeb112967b10 Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Wed, 8 Apr 2026 13:03:36 +0100 Subject: [PATCH 17/27] Refactor account-terraform workflow and bucket resolution script for enhanced validation and clarity - Consolidated SHA validation logic in the account-terraform workflow to improve efficiency and readability. - Streamlined change detection output by directly capturing the status of account infrastructure changes. - Simplified the bucket resolution script by removing unnecessary functions and utilizing direct variable assignment for improved clarity. --- .github/workflows/account-terraform.yml | 23 ++++++------------- .../resolve_account_terraform_state_bucket.sh | 13 +++-------- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml index 1f4249845b..11d1986be0 100644 --- a/.github/workflows/account-terraform.yml +++ b/.github/workflows/account-terraform.yml @@ -58,28 +58,19 @@ jobs: base_sha=$(git rev-parse HEAD~1) fi - if [[ ! "$base_sha" =~ ^[0-9a-f]{40}$ ]]; then - echo "Invalid base SHA: $base_sha" >&2 - exit 1 - fi - - if [[ ! "$head_sha" =~ ^[0-9a-f]{40}$ ]]; then - echo "Invalid head SHA: $head_sha" >&2 - exit 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" - echo "account_infra_changed=true" >> "$GITHUB_OUTPUT" - else - echo "account_infra_changed=false" >> "$GITHUB_OUTPUT" fi - - - name: Skip account terraform - if: ${{ steps.diff.outputs.account_infra_changed != 'true' }} - run: echo "No account-level infrastructure changes detected." + 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' }} diff --git a/utilities/scripts/resolve_account_terraform_state_bucket.sh b/utilities/scripts/resolve_account_terraform_state_bucket.sh index 92435c1609..628b4a886c 100644 --- a/utilities/scripts/resolve_account_terraform_state_bucket.sh +++ b/utilities/scripts/resolve_account_terraform_state_bucket.sh @@ -1,16 +1,9 @@ #!/bin/bash -set -o nounset errexit pipefail +set -euo pipefail -trim() { - local value="${1-}" - value="${value#"${value%%[![:space:]]*}"}" - value="${value%"${value##*[![:space:]]}"}" - printf '%s' "$value" -} - -configured_bucket="$(trim "${CONFIGURED_ACCOUNT_TERRAFORM_STATE_BUCKET:-}")" -state_bucket_environment="$(trim "${ACCOUNT_TERRAFORM_STATE_ENVIRONMENT:-}")" +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 From c61a3c455368542416feee43241d0b56cb2a9992 Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Wed, 8 Apr 2026 16:24:17 +0100 Subject: [PATCH 18/27] Update base_sha logic in pr-deploy-and-test workflow to simplify SHA handling for pull requests. Removed conditional check for 'synchronize' action to ensure consistent base SHA retrieval. --- .github/workflows/pr-deploy-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-deploy-and-test.yml b/.github/workflows/pr-deploy-and-test.yml index db44fd2a01..1b92eca234 100644 --- a/.github/workflows/pr-deploy-and-test.yml +++ b/.github/workflows/pr-deploy-and-test.yml @@ -17,7 +17,7 @@ jobs: account-terraform: uses: ./.github/workflows/account-terraform.yml with: - base_sha: ${{ github.event.action == 'synchronize' && github.event.before || github.event.pull_request.base.sha }} + base_sha: ${{ github.event.pull_request.base.sha }} head_sha: ${{ github.event.pull_request.head.sha }} environment: dev state_bucket_environment: internal-dev From 31c25f18dc482ef37179dcf81c578e08bf0b084c Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Wed, 8 Apr 2026 18:47:11 +0100 Subject: [PATCH 19/27] chore: empty commit From aa15e8be3cfea7a7e6e5d276135b0cf96818d508 Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Thu, 9 Apr 2026 12:04:37 +0100 Subject: [PATCH 20/27] chore: empty commit From 34d944079b91e8e64bcaba3679e51e8f095fe1ce Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Thu, 9 Apr 2026 13:03:11 +0100 Subject: [PATCH 21/27] Enhance account-terraform workflow by adding manual approval step before apply phase - Introduced a new job for manual approval after the plan phase, ensuring that changes are reviewed before application. - Updated the apply job to depend on the approval step, enhancing control over the deployment process. --- .github/workflows/account-terraform.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml index 11d1986be0..3edc90e290 100644 --- a/.github/workflows/account-terraform.yml +++ b/.github/workflows/account-terraform.yml @@ -111,12 +111,23 @@ jobs: name: ${{ inputs.artifact_name }} 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 - needs: [account-terraform-plan] - if: ${{ !cancelled() && needs.account-terraform-plan.result == 'success' && needs.account-terraform-plan.outputs.account_infra_changed == 'true' }} + 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 environment: name: ${{ inputs.environment }} From 99d20c065030b7f44cae0df02d0832b6dac6f0a1 Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Thu, 9 Apr 2026 13:05:35 +0100 Subject: [PATCH 22/27] Fix description in ECR lifecycle policy to include a period for consistency. --- infrastructure/account/recordprocessor_ecr_repo.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/account/recordprocessor_ecr_repo.tf b/infrastructure/account/recordprocessor_ecr_repo.tf index 9aaef8bfc1..4f2d635ea1 100644 --- a/infrastructure/account/recordprocessor_ecr_repo.tf +++ b/infrastructure/account/recordprocessor_ecr_repo.tf @@ -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", From 33b2bed5b2a80cd2377804dd66ddb07076082481 Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Thu, 9 Apr 2026 14:27:40 +0100 Subject: [PATCH 23/27] Refactor account-terraform workflow to enhance input handling and artifact naming - Updated workflow name for clarity. - Added support for manual input parameters including environment selection and optional artifact naming. - Improved handling of SHA values for better consistency in deployment processes. - Streamlined artifact name generation to ensure clarity and avoid potential conflicts. --- .github/workflows/account-terraform.yml | 41 +++++++++++++++++++-- .github/workflows/continuous-deployment.yml | 11 +----- .github/workflows/pr-deploy-and-test.yml | 11 +----- 3 files changed, 39 insertions(+), 24 deletions(-) diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml index 3edc90e290..e24bdc5fd4 100644 --- a/.github/workflows/account-terraform.yml +++ b/.github/workflows/account-terraform.yml @@ -1,4 +1,4 @@ -name: Account Terraform +name: Apply Account Terraform on: workflow_call: @@ -19,6 +19,38 @@ on: 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 }} @@ -27,6 +59,7 @@ concurrency: 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) }} jobs: account-terraform-plan: @@ -38,7 +71,7 @@ jobs: name: ${{ inputs.environment }} env: ACCOUNT_TERRAFORM_BASE_SHA: ${{ inputs.base_sha }} - ACCOUNT_TERRAFORM_HEAD_SHA: ${{ inputs.head_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 }} @@ -108,7 +141,7 @@ jobs: if: ${{ steps.diff.outputs.account_infra_changed == 'true' }} uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f with: - name: ${{ inputs.artifact_name }} + name: ${{ env.ACCOUNT_TERRAFORM_ARTIFACT_NAME }} path: infrastructure/account/tfplan account-terraform-approval: @@ -155,7 +188,7 @@ jobs: - name: Retrieve Account Terraform Plan uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with: - name: ${{ inputs.artifact_name }} + name: ${{ env.ACCOUNT_TERRAFORM_ARTIFACT_NAME }} path: infrastructure/account - name: Terraform Init (account) diff --git a/.github/workflows/continuous-deployment.yml b/.github/workflows/continuous-deployment.yml index 771fd5756b..db8914740c 100644 --- a/.github/workflows/continuous-deployment.yml +++ b/.github/workflows/continuous-deployment.yml @@ -15,17 +15,8 @@ jobs: secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - account-terraform: - uses: ./.github/workflows/account-terraform.yml - with: - base_sha: ${{ github.event.before }} - head_sha: ${{ github.sha }} - environment: dev - state_bucket_environment: internal-dev - artifact_name: dev-account-tfplan - deploy-internal-dev-backend: - needs: [run-quality-checks, account-terraform] + needs: [run-quality-checks] uses: ./.github/workflows/deploy-backend.yml with: apigee_environment: internal-dev diff --git a/.github/workflows/pr-deploy-and-test.yml b/.github/workflows/pr-deploy-and-test.yml index 1b92eca234..83c82a2772 100644 --- a/.github/workflows/pr-deploy-and-test.yml +++ b/.github/workflows/pr-deploy-and-test.yml @@ -14,17 +14,8 @@ jobs: secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - account-terraform: - uses: ./.github/workflows/account-terraform.yml - with: - base_sha: ${{ github.event.pull_request.base.sha }} - head_sha: ${{ github.event.pull_request.head.sha }} - environment: dev - state_bucket_environment: internal-dev - artifact_name: pr-dev-account-tfplan - deploy-pr-backend: - needs: [run-quality-checks, account-terraform] + needs: [run-quality-checks] if: ${{ always() && !failure() && !cancelled() }} uses: ./.github/workflows/deploy-backend.yml with: From c37f9f91898ca7f96c0cb7751416db6fbd5cea28 Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Fri, 10 Apr 2026 09:48:27 +0100 Subject: [PATCH 24/27] Enhance account-terraform workflow and scripts for improved stability and clarity - Added ACCOUNT_TERRAFORM_VERSION environment variable for consistent Terraform versioning. - Increased job timeout to 30 minutes for both planning and applying stages. - Updated role-session-name format for better traceability in AWS actions. - Modified Makefile to streamline Terraform apply command. - Added validation for ACCOUNT_TERRAFORM_STATE_ENVIRONMENT in the state bucket resolution script to enforce correct environment values. --- .github/workflows/account-terraform.yml | 14 ++++++++++---- infrastructure/account/Makefile | 2 +- .../resolve_account_terraform_state_bucket.sh | 9 +++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml index e24bdc5fd4..f1c136d3e4 100644 --- a/.github/workflows/account-terraform.yml +++ b/.github/workflows/account-terraform.yml @@ -60,6 +60,7 @@ 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: @@ -67,6 +68,7 @@ jobs: id-token: write contents: read runs-on: ubuntu-latest + timeout-minutes: 30 environment: name: ${{ inputs.environment }} env: @@ -75,6 +77,7 @@ jobs: 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 @@ -111,12 +114,12 @@ jobs: with: aws-region: eu-west-2 role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/auto-ops - role-session-name: github-actions + 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: "1.12.2" + terraform_version: ${{ env.ACCOUNT_TERRAFORM_VERSION }} - name: Resolve account terraform state bucket id: account-state-bucket @@ -162,6 +165,7 @@ jobs: 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: @@ -169,17 +173,19 @@ jobs: 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: github-actions + 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: "1.12.2" + terraform_version: ${{ env.ACCOUNT_TERRAFORM_VERSION }} - name: Resolve account terraform state bucket id: account-state-bucket diff --git a/infrastructure/account/Makefile b/infrastructure/account/Makefile index f31e8ebe33..a2d46dd82b 100644 --- a/infrastructure/account/Makefile +++ b/infrastructure/account/Makefile @@ -41,7 +41,7 @@ apply: workspace $(tf_cmd) apply $(tf_vars) -auto-approve apply-ci: workspace - $(tf_cmd) apply $(tf_vars) -input=false tfplan + $(tf_cmd) apply -input=false tfplan clean: rm -rf build .terraform upload-key diff --git a/utilities/scripts/resolve_account_terraform_state_bucket.sh b/utilities/scripts/resolve_account_terraform_state_bucket.sh index 628b4a886c..89b83ce0a7 100644 --- a/utilities/scripts/resolve_account_terraform_state_bucket.sh +++ b/utilities/scripts/resolve_account_terraform_state_bucket.sh @@ -12,4 +12,13 @@ read -r state_bucket_environment <<< "${ACCOUNT_TERRAFORM_STATE_ENVIRONMENT:-}" 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" From 264bee55a6b5e680b54ddc3f417a539b65904876 Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Fri, 10 Apr 2026 11:49:55 +0100 Subject: [PATCH 25/27] Enhance account-terraform workflow with attestation support - Added permissions for attestations and artifact metadata in the workflow. - Introduced steps for attesting the Terraform plan and verifying the attestation. - Improved overall security and traceability of the Terraform deployment process. --- .github/workflows/account-terraform.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/account-terraform.yml b/.github/workflows/account-terraform.yml index f1c136d3e4..871abcb895 100644 --- a/.github/workflows/account-terraform.yml +++ b/.github/workflows/account-terraform.yml @@ -67,6 +67,8 @@ jobs: permissions: id-token: write contents: read + attestations: write + artifact-metadata: write runs-on: ubuntu-latest timeout-minutes: 30 environment: @@ -147,6 +149,12 @@ jobs: 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] @@ -162,6 +170,7 @@ jobs: 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 @@ -197,6 +206,14 @@ jobs: 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: From 6ccbb2dd3176292f7990fa73213bf1677f85a24e Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Sat, 11 Apr 2026 13:48:52 +0100 Subject: [PATCH 26/27] chore: empty commit From 48f3cad54f2aba9486528c23ee3185eb33bba7e5 Mon Sep 17 00:00:00 2001 From: Thomas-Boyle Date: Mon, 13 Apr 2026 08:59:47 +0100 Subject: [PATCH 27/27] chore: empty commit