From f117d37a987ba5cd1fed05d621dc192b0edca61a Mon Sep 17 00:00:00 2001 From: Tom Whitwell Date: Thu, 22 Jan 2026 11:11:20 +0000 Subject: [PATCH 1/3] Only run one review-apps workflow at a time This prevents us attempting to run multiple instance of terraform at the same time. Instead, the current running workflow will complete before the next one starts. Only 1 running and 1 pending workflow is allowed - any further workflows will supercede the pending one. --- .github/workflows/review_apps_on_pr_change.yml | 4 ++++ .github/workflows/review_apps_on_pr_close.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/review_apps_on_pr_change.yml b/.github/workflows/review_apps_on_pr_change.yml index bf5bba9f7..5f2b4de3f 100644 --- a/.github/workflows/review_apps_on_pr_change.yml +++ b/.github/workflows/review_apps_on_pr_change.yml @@ -5,6 +5,10 @@ on: # matches the docs for the default types # https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request types: [opened, reopened, synchronize] + +concurrency: + group: "review-apps-forms-runner-pr-${{ github.event.pull_request.number }}" + cancel-in-progress: false jobs: update-review-app: # this references a codebuild project configured in forms-deploy diff --git a/.github/workflows/review_apps_on_pr_close.yml b/.github/workflows/review_apps_on_pr_close.yml index 9262f4fd0..3f3ffa661 100644 --- a/.github/workflows/review_apps_on_pr_close.yml +++ b/.github/workflows/review_apps_on_pr_close.yml @@ -3,6 +3,10 @@ on: pull_request: # only run when a PR is closed or merged types: [closed] + +concurrency: + group: "review-apps-forms-runner-pr-${{ github.event.pull_request.number }}" + cancel-in-progress: false env: IMAGE_TAG: "842676007477.dkr.ecr.eu-west-2.amazonaws.com/forms-runner:pr-${{github.event.pull_request.number}}-${{github.event.pull_request.head.ref}}" jobs: From ad51097b53dea90d418d29b5a9b824e4fc5bfed2 Mon Sep 17 00:00:00 2001 From: Tom Whitwell Date: Thu, 19 Mar 2026 13:15:18 +0000 Subject: [PATCH 2/3] Deploy / destroy review apps with CodeBuild Instead of running Terraform directly in the GitHub Actions runners, we now trigger AWS CodeBuild projects to handle the deployment and destruction of review apps. This means that the repository no longer needs extensive AWS permissions in GitHub Actions, and the actual available AWS operations are limited. --- .../workflows/review_apps_on_pr_change.yml | 119 ++---------------- .github/workflows/review_apps_on_pr_close.yml | 42 ++----- 2 files changed, 22 insertions(+), 139 deletions(-) diff --git a/.github/workflows/review_apps_on_pr_change.yml b/.github/workflows/review_apps_on_pr_change.yml index 5f2b4de3f..cf8fe0ae4 100644 --- a/.github/workflows/review_apps_on_pr_change.yml +++ b/.github/workflows/review_apps_on_pr_change.yml @@ -1,117 +1,20 @@ name: "Review apps: on PR change" on: pull_request: - # being explicit about what to trigger on. - # matches the docs for the default types - # https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request types: [opened, reopened, synchronize] concurrency: - group: "review-apps-forms-runner-pr-${{ github.event.pull_request.number }}" + group: "review-apps-pr-${{ github.event.pull_request.number }}" cancel-in-progress: false -jobs: - update-review-app: - # this references a codebuild project configured in forms-deploy - # see: https://docs.aws.amazon.com/codebuild/latest/userguide/action-runner.html - runs-on: codebuild-review-forms-runner-gha-runner-${{github.run_id}}-${{github.run_attempt}} - - permissions: - pull-requests: write - - steps: - - name: Generate container image URI - run: | - echo "CONTAINER_IMAGE_URI=842676007477.dkr.ecr.eu-west-2.amazonaws.com/forms-runner:pr-${{github.event.pull_request.number}}-${{github.event.pull_request.head.sha}}-$(date +%s)" >> "$GITHUB_ENV" - - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Build container - run: | - # Docker credentials are configured in CodeBuild - # CodeBuild retrieves the credentials from ParameterStore - echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin - docker build \ - --tag "${{env.CONTAINER_IMAGE_URI}}" \ - . - - - name: Push container - id: build-container - run: | - aws ecr get-login-password --region eu-west-2 \ - | docker login --username AWS --password-stdin 842676007477.dkr.ecr.eu-west-2.amazonaws.com - - echo "Pushing container image" - echo "${{env.CONTAINER_IMAGE_URI}}" - - docker push "${CONTAINER_IMAGE_URI}" - - - name: Determine Terraform version - id: terraform-version - run: | - echo "TF_VERSION=$(< .review_apps/.terraform-version)" >> "$GITHUB_OUTPUT" - - - uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85 # v4.0.0 - with: - terraform_version: ${{steps.terraform-version.outputs.TF_VERSION}} - - - name: Deploy review app - id: deploy - run: | - cd .review_apps/ - terraform init -backend-config="key=review-apps/forms-runner/pr-${{github.event.pull_request.number}}.tfstate" +permissions: + id-token: write + contents: read + pull-requests: write - terraform apply \ - -var "pull_request_number=${{github.event.pull_request.number}}" \ - -var "forms_runner_container_image=${{env.CONTAINER_IMAGE_URI}}" \ - -no-color \ - -auto-approve - - # shellcheck disable=SC2129 # SC2129 is "mainly a stylistic issue" and it breaks our flow - echo "REVIEW_APP_URL=$(terraform output -raw review_app_url)" >> "$GITHUB_OUTPUT" - echo "ADMIN_APP_URL=$(terraform output -raw admin_app_url)" >> "$GITHUB_OUTPUT" - echo "ECS_CLUSTER_ID=$(terraform output -raw review_app_ecs_cluster_id)" >> "$GITHUB_OUTPUT" - echo "ECS_SERVICE_NAME=$(terraform output -raw review_app_ecs_service_name)" >> "$GITHUB_OUTPUT" - - - name: Wait for AWS ECS deployments to finish - run: | - aws ecs wait services-stable \ - --cluster "${{steps.deploy.outputs.ECS_CLUSTER_ID}}" \ - --services "${{steps.deploy.outputs.ECS_SERVICE_NAME}}" - - - name: Comment on PR - env: - COMMENT_MARKER: - GH_TOKEN: ${{ github.token }} - run: | - cat < "${{runner.temp}}/pr-comment.md" - :tada: A review copy of this PR has been deployed! It is made of up two components - - 1. [A review copy of forms-runner](${{steps.deploy.outputs.REVIEW_APP_URL}}) - 2. [A production copy of forms-admin](${{steps.deploy.outputs.ADMIN_APP_URL}}) - - > [!IMPORTANT] - > Not all of the functionality of forms-runner is present in review apps. - > Functionality such as sending emails, file upload, and S3 submission types are - > deliberately disabled for the sake of simplifying review apps. - > - > You should use the full dev environment to test the functionality which is disabled here. - - It may take 5 minutes or so for the application to be fully deployed and working. If it still isn't ready - after 5 minutes, there may be something wrong with the ECS task. You will need to go to the integration AWS account - to debug, or otherwise ask an infrastructure person. - - For the sign in details and more information, [see the review apps wiki page](https://github.com/alphagov/forms-team/wiki/Review-apps). - - $COMMENT_MARKER - EOF - - # shellcheck disable=SC2016 - # `jq` uses single-quote characters on Unix shells - old_comment_ids=$(gh api "repos/{owner}/{repo}/issues/${{github.event.pull_request.number}}/comments" --jq 'map(select((.user.login == "github-actions[bot]") and (.body | endswith($ENV.COMMENT_MARKER + "\n")))) | .[].id') - for comment_id in $old_comment_ids; do - gh api -X DELETE "repos/{owner}/{repo}/issues/comments/${comment_id}" - done - - gh pr comment "${{github.event.pull_request.html_url}}" --body-file "${{runner.temp}}/pr-comment.md" +jobs: + update-review-app: + name: Update review app + uses: alphagov/forms-deploy/.github/workflows/reusable-review_apps_on_pr_change.yml@main + with: + app-name: forms-runner diff --git a/.github/workflows/review_apps_on_pr_close.yml b/.github/workflows/review_apps_on_pr_close.yml index 3f3ffa661..a3712ca38 100644 --- a/.github/workflows/review_apps_on_pr_close.yml +++ b/.github/workflows/review_apps_on_pr_close.yml @@ -1,40 +1,20 @@ name: "Review apps: on PR close" on: pull_request: - # only run when a PR is closed or merged types: [closed] concurrency: - group: "review-apps-forms-runner-pr-${{ github.event.pull_request.number }}" + group: "review-apps-pr-${{ github.event.pull_request.number }}" cancel-in-progress: false -env: - IMAGE_TAG: "842676007477.dkr.ecr.eu-west-2.amazonaws.com/forms-runner:pr-${{github.event.pull_request.number}}-${{github.event.pull_request.head.ref}}" -jobs: - delete-review-app: - # this references a codebuild project configured in forms-deploy - # see: https://docs.aws.amazon.com/codebuild/latest/userguide/action-runner.html - runs-on: codebuild-review-forms-runner-gha-runner-${{github.run_id}}-${{github.run_attempt}} - - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Determine Terraform version - id: terraform-version - run: | - echo "TF_VERSION=$(< .review_apps/.terraform-version)" >> "$GITHUB_OUTPUT" - - uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85 # v4.0.0 - with: - terraform_version: ${{steps.terraform-version.outputs.TF_VERSION}} +permissions: + id-token: write + contents: read + pull-requests: write - - name: Delete review app - run: | - cd .review_apps/ - - terraform init -backend-config="key=review-apps/forms-runner/pr-${{github.event.pull_request.number}}.tfstate" - terraform destroy \ - -var "pull_request_number=${{github.event.pull_request.number}}" \ - -var "forms_runner_container_image=${{env.IMAGE_TAG}}" \ - -no-color \ - -auto-approve +jobs: + delete-review-app: + name: Delete review app + uses: alphagov/forms-deploy/.github/workflows/reusable-review_apps_on_pr_close.yml@main + with: + app-name: forms-runner From 5db180e7fe01118f8a57a486f659966d0a16600b Mon Sep 17 00:00:00 2001 From: Tom Whitwell Date: Thu, 19 Mar 2026 13:26:00 +0000 Subject: [PATCH 3/3] BAU: add missing fields to the review app task definition These fields are automatically added by AWS when creating a task definition. If we don't include them in our task definition, Terraform tries to remove them on every apply, which causes unnecessary changes to the task definition (and thus noise in our Terraform plan output). --- .review_apps/ecs_task_definition.tf | 54 ++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/.review_apps/ecs_task_definition.tf b/.review_apps/ecs_task_definition.tf index db03b59e4..6c9f36b5d 100644 --- a/.review_apps/ecs_task_definition.tf +++ b/.review_apps/ecs_task_definition.tf @@ -93,11 +93,16 @@ resource "aws_ecs_task_definition" "task" { portMappings = [ { containerPort = 3001 + hostPort = 3001 protocol = "tcp" appProtocol = "http" } ] + mountPoints = [] + systemControls = [] + volumesFrom = [] + logConfiguration = { logDriver = "awslogs" options = { @@ -112,6 +117,7 @@ resource "aws_ecs_task_definition" "task" { interval = 30 retries = 5 startPeriod = 180 + timeout = 5 } dependsOn = [ @@ -155,11 +161,16 @@ resource "aws_ecs_task_definition" "task" { portMappings = [ { containerPort = 3000 + hostPort = 3000 protocol = "tcp" appProtocol = "http" } ] + mountPoints = [] + systemControls = [] + volumesFrom = [] + logConfiguration = { logDriver = "awslogs" options = { @@ -174,6 +185,7 @@ resource "aws_ecs_task_definition" "task" { interval = 30 retries = 5 startPeriod = 180 + timeout = 5 } dependsOn = [ @@ -195,7 +207,17 @@ resource "aws_ecs_task_definition" "task" { command = [] essential = true - portMappings = [{ containerPort = 5432 }] + portMappings = [ + { + containerPort = 5432 + hostPort = 5432 + protocol = "tcp" + } + ] + + mountPoints = [] + systemControls = [] + volumesFrom = [] environment = [ { name = "POSTGRES_PASSWORD", value = "postgres" } @@ -211,7 +233,10 @@ resource "aws_ecs_task_definition" "task" { } healthCheck = { - command = ["CMD-SHELL", "psql -h localhost -p 5432 -U postgres -c \"SELECT current_timestamp - pg_postmaster_start_time();\""] + command = ["CMD-SHELL", "psql -h localhost -p 5432 -U postgres -c \"SELECT current_timestamp - pg_postmaster_start_time();\""] + interval = 30 + retries = 3 + timeout = 5 } }, @@ -226,7 +251,17 @@ resource "aws_ecs_task_definition" "task" { ], essential = true - portMappings = [{ containerPort = 6379 }] + portMappings = [ + { + containerPort = 6379 + hostPort = 6379 + protocol = "tcp" + } + ] + + mountPoints = [] + systemControls = [] + volumesFrom = [] logConfiguration = { logDriver = "awslogs" @@ -238,7 +273,10 @@ resource "aws_ecs_task_definition" "task" { } healthCheck = { - command = ["CMD-SHELL", "redis-cli", "ping"] + command = ["CMD-SHELL", "redis-cli", "ping"] + interval = 30 + retries = 3 + timeout = 5 } }, @@ -251,6 +289,10 @@ resource "aws_ecs_task_definition" "task" { environment = local.forms_runner_env_vars readonlyRootFilesystem = true + mountPoints = [] + systemControls = [] + volumesFrom = [] + logConfiguration = { logDriver = "awslogs" options = { @@ -277,6 +319,10 @@ resource "aws_ecs_task_definition" "task" { environment = local.forms_admin_env_vars readonlyRootFilesystem = true + mountPoints = [] + systemControls = [] + volumesFrom = [] + logConfiguration = { logDriver = "awslogs" options = {