From 75ec4d1e753d23ff6dd480c721b00b22b21663c2 Mon Sep 17 00:00:00 2001 From: Nat Dean-Lewis Date: Wed, 11 Mar 2026 10:26:30 +0000 Subject: [PATCH 1/5] CLDC-4236: use list-images in review app deployments --- .github/workflows/aws_deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/aws_deploy.yml b/.github/workflows/aws_deploy.yml index 5af3c2d08..0e82e576b 100644 --- a/.github/workflows/aws_deploy.yml +++ b/.github/workflows/aws_deploy.yml @@ -55,7 +55,7 @@ jobs: - name: Check if image with tag already exists run: | - echo "image-exists=$(if aws ecr list-images --repository-name=$repository --query "imageIds[*].imageTag" | grep -q ${{ github.sha }}; then echo true; else echo false; fi)" >> $GITHUB_ENV + echo "image-exists=$(if aws ecr describe-images --repository-name=$repository --image-ids imageTag=${{ github.sha }} > /dev/null 2>&1; then echo true; else echo false; fi)" >> $GITHUB_ENV - name: Build, tag, and push docker image to ECR if there is no image, failing for releases id: build-image From 36918740f40f7901d5ec194baf49be63fee41f97 Mon Sep 17 00:00:00 2001 From: Nat Dean-Lewis Date: Wed, 11 Mar 2026 12:20:22 +0000 Subject: [PATCH 2/5] CLDC-4236: use correct sha ref for review apps --- .github/workflows/aws_deploy.yml | 15 ++++++++++++--- .github/workflows/review_pipeline.yml | 16 +++++++++++++--- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/.github/workflows/aws_deploy.yml b/.github/workflows/aws_deploy.yml index 0e82e576b..9bd78ea3e 100644 --- a/.github/workflows/aws_deploy.yml +++ b/.github/workflows/aws_deploy.yml @@ -22,6 +22,10 @@ on: release_tag: required: false type: string + ref: + required: false + type: string + default: "" concurrency: group: deploy-${{ inputs.environment }}${{ inputs.concurrency_tag }} @@ -42,6 +46,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref || github.sha }} - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 @@ -53,16 +59,19 @@ jobs: id: ecr-login uses: aws-actions/amazon-ecr-login@v2 + - name: Resolve commit SHA + run: echo "commit_sha=${{ inputs.ref || github.sha }}" >> $GITHUB_ENV + - name: Check if image with tag already exists run: | - echo "image-exists=$(if aws ecr describe-images --repository-name=$repository --image-ids imageTag=${{ github.sha }} > /dev/null 2>&1; then echo true; else echo false; fi)" >> $GITHUB_ENV + echo "image-exists=$(if aws ecr describe-images --repository-name=$repository --image-ids imageTag=${{ env.commit_sha }} > /dev/null 2>&1; then echo true; else echo false; fi)" >> $GITHUB_ENV - name: Build, tag, and push docker image to ECR if there is no image, failing for releases id: build-image if: ${{ env.image-exists == 'false' }} env: registry: ${{ steps.ecr-login.outputs.registry }} - commit_tag: ${{ github.sha }} + commit_tag: ${{ env.commit_sha }} run: | if [[ ${{ inputs.environment }} == 'production' ]]; then echo "Error: Deployment to production environment is not allowed as there is no docker image (i.e. the AWS deploy on staging was unsuccessful for this commit)." @@ -100,7 +109,7 @@ jobs: id: update-image-tags env: registry: ${{ steps.ecr-login.outputs.registry }} - commit_tag: ${{ github.sha }} + commit_tag: ${{ inputs.ref || github.sha }} readable_tag: ${{ inputs.environment }}-${{ env.additional-tag }} run: | manifest=$(aws ecr batch-get-image --repository-name $repository --image-ids imageTag=$commit_tag --output text --query images[].imageManifest) diff --git a/.github/workflows/review_pipeline.yml b/.github/workflows/review_pipeline.yml index 307aa0381..5c89d91e3 100644 --- a/.github/workflows/review_pipeline.yml +++ b/.github/workflows/review_pipeline.yml @@ -19,17 +19,26 @@ jobs: runs-on: ubuntu-latest outputs: pr_number: ${{ steps.get_pr_details.outputs.pr_number }} + pr_head_sha: ${{ steps.get_pr_details.outputs.pr_head_sha }} steps: - - name: Get PR number + - name: Get PR number and HEAD SHA id: get_pr_details uses: actions/github-script@v7 with: script: | + let prNumber; if (context.eventName === 'workflow_dispatch') { - core.setOutput('pr_number', '${{ inputs.pr_number }}'); + prNumber = '${{ inputs.pr_number }}'; } else { - core.setOutput('pr_number', context.issue.number.toString()); + prNumber = context.issue.number.toString(); } + core.setOutput('pr_number', prNumber); + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: parseInt(prNumber), + }); + core.setOutput('pr_head_sha', pr.head.sha); infra: name: Deploy review app infrastructure @@ -51,6 +60,7 @@ jobs: aws_task_prefix: core-review-${{ needs.get_pr_details.outputs.pr_number }} concurrency_tag: ${{ needs.get_pr_details.outputs.pr_number }} environment: review + ref: ${{ needs.get_pr_details.outputs.pr_head_sha }} permissions: id-token: write From cee09718fd230695c420c441ec356822db6950cd Mon Sep 17 00:00:00 2001 From: Nat Dean-Lewis <94526761+natdeanlewissoftwire@users.noreply.github.com> Date: Wed, 11 Mar 2026 16:24:23 +0000 Subject: [PATCH 3/5] CLDC-4236: misc pipeline updates (#3227) * CLDC-4236: remove workflow dispatch from prod pipeline * CLDC-4236: update review pipeline flow with concurrency group, correct ref and inputs * CLDC-4236: test copy change * CLDC-4236: fix aws creds step * CLDC-4236: set permissions * Revert "CLDC-4236: test copy change" This reverts commit c0d50b1e4b53065f6b0447872c1ff280c03543ca. * empty commit * CLDC-4136: change check if review app exists to check if review app deployment started to avoid not deploying changes made during first deployment * CLDC-4136: don't allow manual teardown to avoid unnecessary edge cases * CLDC-4236: add permissions to manual_review_code_pipeline.yml --- .../workflows/manual_review_code_pipeline.yml | 34 +++++++-- .github/workflows/production_pipeline.yml | 1 - .github/workflows/review_pipeline.yml | 70 ++++++++++++++++++- .../workflows/review_teardown_pipeline.yml | 67 ++++++++++++++++-- 4 files changed, 160 insertions(+), 12 deletions(-) diff --git a/.github/workflows/manual_review_code_pipeline.yml b/.github/workflows/manual_review_code_pipeline.yml index 2ea0719ca..15e17b2ae 100644 --- a/.github/workflows/manual_review_code_pipeline.yml +++ b/.github/workflows/manual_review_code_pipeline.yml @@ -1,29 +1,51 @@ -name: Manual review app code pipeline +name: Manual review app build and deploy concurrency: - group: review-${{ inputs.review_app_key }} + group: deploy-review${{ inputs.pr_number }} on: workflow_dispatch: inputs: - review_app_key: + pr_number: required: true type: string - description: "The review app ID to deploy code for." + description: "The PR number of the review app to deploy code for. Note: this is NOT the ticket number" + +permissions: {} defaults: run: shell: bash jobs: + get_pr_head_sha: + name: Get PR HEAD SHA + runs-on: ubuntu-latest + outputs: + pr_head_sha: ${{ steps.get_sha.outputs.pr_head_sha }} + steps: + - name: Get PR HEAD SHA + id: get_sha + uses: actions/github-script@v7 + with: + script: | + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: parseInt('${{ inputs.pr_number }}'), + }); + core.setOutput('pr_head_sha', pr.head.sha); + code: name: Deploy review app code + needs: [get_pr_head_sha] uses: ./.github/workflows/aws_deploy.yml with: aws_account_id: 837698168072 aws_role_prefix: core-dev - aws_task_prefix: core-review-${{ inputs.review_app_key }} - concurrency_tag: ${{ inputs.review_app_key }} + aws_task_prefix: core-review-${{ inputs.pr_number }} + concurrency_tag: ${{ inputs.pr_number }} environment: review + ref: ${{ needs.get_pr_head_sha.outputs.pr_head_sha }} permissions: id-token: write diff --git a/.github/workflows/production_pipeline.yml b/.github/workflows/production_pipeline.yml index b4a188415..45a31167f 100644 --- a/.github/workflows/production_pipeline.yml +++ b/.github/workflows/production_pipeline.yml @@ -3,7 +3,6 @@ name: Production CI/CD Pipeline on: release: types: [released] - workflow_dispatch: defaults: run: diff --git a/.github/workflows/review_pipeline.yml b/.github/workflows/review_pipeline.yml index 5c89d91e3..13b1fbf54 100644 --- a/.github/workflows/review_pipeline.yml +++ b/.github/workflows/review_pipeline.yml @@ -9,13 +9,18 @@ on: required: true type: string description: "The number of the PR for which to deploy a review app. Note: this is NOT the ticket number" + pull_request: + types: [synchronize] + +concurrency: + group: deploy-review${{ github.event.pull_request.number || inputs.pr_number || github.event.issue.number }} permissions: {} jobs: get_pr_details: name: Get PR details - if: github.event_name == 'workflow_dispatch' || (github.event.issue.pull_request && startsWith(github.event.comment.body, '/deploy-review')) + if: github.event_name == 'workflow_dispatch' || (github.event.issue.pull_request && startsWith(github.event.comment.body, '/deploy-review')) || github.event_name == 'pull_request' runs-on: ubuntu-latest outputs: pr_number: ${{ steps.get_pr_details.outputs.pr_number }} @@ -29,6 +34,8 @@ jobs: let prNumber; if (context.eventName === 'workflow_dispatch') { prNumber = '${{ inputs.pr_number }}'; + } else if (context.eventName === 'pull_request') { + prNumber = context.payload.pull_request.number.toString(); } else { prNumber = context.issue.number.toString(); } @@ -40,8 +47,52 @@ jobs: }); core.setOutput('pr_head_sha', pr.head.sha); + check_deployment_started: + name: Check if deployment has been started + if: github.event_name == 'pull_request' + needs: [get_pr_details] + runs-on: ubuntu-latest + permissions: + pull-requests: read + outputs: + started: ${{ steps.check.outputs.started }} + steps: + - name: Check for previous deployment workflow runs + id: check + uses: actions/github-script@v7 + with: + script: | + const prNumber = '${{ needs.get_pr_details.outputs.pr_number }}'; + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: parseInt(prNumber), + }); + const deployComment = comments.find(c => c.body === 'Starting review app deployment...'); + core.setOutput('started', deployComment ? 'true' : 'false'); + + deployment_started_comment: + name: Comment deployment started + if: github.event_name != 'pull_request' + needs: [get_pr_details] + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Comment on PR + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: ${{ needs.get_pr_details.outputs.pr_number }}, + body: 'Starting review app deployment...', + }); + infra: name: Deploy review app infrastructure + if: github.event_name != 'pull_request' needs: [get_pr_details] uses: communitiesuk/submit-social-housing-lettings-and-sales-data-infrastructure/.github/workflows/create_review_app_infra.yml@main with: @@ -52,6 +103,7 @@ jobs: code: name: Deploy review app code + if: github.event_name != 'pull_request' needs: [get_pr_details, infra] uses: ./.github/workflows/aws_deploy.yml with: @@ -64,8 +116,24 @@ jobs: permissions: id-token: write + auto_update_code: + name: Auto-update review app code + if: github.event_name == 'pull_request' && needs.check_deployment_started.outputs.started == 'true' + needs: [get_pr_details, check_deployment_started] + uses: ./.github/workflows/aws_deploy.yml + with: + aws_account_id: 837698168072 + aws_role_prefix: core-dev + aws_task_prefix: core-review-${{ needs.get_pr_details.outputs.pr_number }} + concurrency_tag: ${{ needs.get_pr_details.outputs.pr_number }} + environment: review + ref: ${{ needs.get_pr_details.outputs.pr_head_sha }} + permissions: + id-token: write + comment: name: Add link to PR + if: github.event_name != 'pull_request' needs: [get_pr_details, code] runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/review_teardown_pipeline.yml b/.github/workflows/review_teardown_pipeline.yml index 8925b3340..0df3ad0b7 100644 --- a/.github/workflows/review_teardown_pipeline.yml +++ b/.github/workflows/review_teardown_pipeline.yml @@ -1,27 +1,85 @@ name: Review app teardown pipeline concurrency: - group: review-${{ github.event.pull_request.number }} + group: deploy-review${{ github.event.pull_request.number || inputs.pr_number }} on: pull_request: types: - closed workflow_dispatch: + inputs: + pr_number: + required: true + type: string + description: "The PR number of the review app to tear down. Note: this is NOT the ticket number" + +permissions: {} env: app_repo_role: arn:aws:iam::815624722760:role/core-application-repo aws_account_id: 837698168072 aws_region: eu-west-2 aws_role_prefix: core-dev - aws_task_prefix: core-review-${{ github.event.pull_request.number }} jobs: + get_pr_number: + name: Get PR number + runs-on: ubuntu-latest + outputs: + pr_number: ${{ steps.get.outputs.pr_number }} + steps: + - name: Get PR number + id: get + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + echo "pr_number=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT + else + echo "pr_number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT + fi + + check_review_app_exists: + name: Check if review app exists + needs: [get_pr_number] + runs-on: ubuntu-latest + permissions: + id-token: write + outputs: + exists: ${{ steps.check.outputs.exists }} + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ${{ env.aws_region }} + role-to-assume: ${{ env.app_repo_role }} + + - name: Configure AWS credentials for review environment + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-region: ${{ env.aws_region }} + role-to-assume: arn:aws:iam::${{ env.aws_account_id }}:role/${{ env.aws_role_prefix }}-deployment + role-chaining: true + + - name: Check if ECS service exists + id: check + env: + aws_task_prefix: core-review-${{ needs.get_pr_number.outputs.pr_number }} + run: | + if aws ecs describe-services --cluster ${{ env.aws_task_prefix }}-app --services ${{ env.aws_task_prefix }}-app --query "services[?status=='ACTIVE']" | grep -q 'serviceName'; then + echo "exists=true" >> $GITHUB_OUTPUT + else + echo "exists=false" >> $GITHUB_OUTPUT + fi + database: name: Drop database + if: needs.check_review_app_exists.outputs.exists == 'true' + needs: [get_pr_number, check_review_app_exists] runs-on: ubuntu-latest permissions: id-token: write + env: + aws_task_prefix: core-review-${{ needs.get_pr_number.outputs.pr_number }} steps: - name: Configure AWS credentials @@ -55,10 +113,11 @@ jobs: infra: name: Teardown review app - needs: [database] + if: needs.check_review_app_exists.outputs.exists == 'true' + needs: [get_pr_number, check_review_app_exists, database] uses: communitiesuk/submit-social-housing-lettings-and-sales-data-infrastructure/.github/workflows/destroy_review_app_infra.yml@main with: - key: ${{ github.event.pull_request.number }} + key: ${{ needs.get_pr_number.outputs.pr_number }} app_repo_role: arn:aws:iam::815624722760:role/core-application-repo permissions: id-token: write From a675ded32dfd0a5737ae12200236a246bc53458a Mon Sep 17 00:00:00 2001 From: Nat Dean-Lewis <94526761+natdeanlewissoftwire@users.noreply.github.com> Date: Wed, 11 Mar 2026 17:03:25 +0000 Subject: [PATCH 4/5] CLDc-4236: temporarily revert to auto review app deploy setup (#3231) * Revert "CLDC-4236: misc pipeline updates (#3227)" This reverts commit cee09718fd230695c420c441ec356822db6950cd. * Revert "CLDC-4236: use correct sha ref for review apps" This reverts commit 36918740f40f7901d5ec194baf49be63fee41f97. * Revert "CLDC-4236: use list-images in review app deployments" This reverts commit 75ec4d1e753d23ff6dd480c721b00b22b21663c2. * Revert "CLDC-4236: trigger review app deploys manually (#3216)" This reverts commit 8a186d096c2f3881e22dcd5e5f86aede0e7bc9f8. --- .github/workflows/aws_deploy.yml | 15 +- .../workflows/manual_review_code_pipeline.yml | 34 +--- .github/workflows/production_pipeline.yml | 1 + .github/workflows/review_app_prompt.yml | 24 --- .github/workflows/review_pipeline.yml | 151 +++--------------- .../workflows/review_teardown_pipeline.yml | 67 +------- 6 files changed, 37 insertions(+), 255 deletions(-) delete mode 100644 .github/workflows/review_app_prompt.yml diff --git a/.github/workflows/aws_deploy.yml b/.github/workflows/aws_deploy.yml index 9bd78ea3e..5af3c2d08 100644 --- a/.github/workflows/aws_deploy.yml +++ b/.github/workflows/aws_deploy.yml @@ -22,10 +22,6 @@ on: release_tag: required: false type: string - ref: - required: false - type: string - default: "" concurrency: group: deploy-${{ inputs.environment }}${{ inputs.concurrency_tag }} @@ -46,8 +42,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - with: - ref: ${{ inputs.ref || github.sha }} - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 @@ -59,19 +53,16 @@ jobs: id: ecr-login uses: aws-actions/amazon-ecr-login@v2 - - name: Resolve commit SHA - run: echo "commit_sha=${{ inputs.ref || github.sha }}" >> $GITHUB_ENV - - name: Check if image with tag already exists run: | - echo "image-exists=$(if aws ecr describe-images --repository-name=$repository --image-ids imageTag=${{ env.commit_sha }} > /dev/null 2>&1; then echo true; else echo false; fi)" >> $GITHUB_ENV + echo "image-exists=$(if aws ecr list-images --repository-name=$repository --query "imageIds[*].imageTag" | grep -q ${{ github.sha }}; then echo true; else echo false; fi)" >> $GITHUB_ENV - name: Build, tag, and push docker image to ECR if there is no image, failing for releases id: build-image if: ${{ env.image-exists == 'false' }} env: registry: ${{ steps.ecr-login.outputs.registry }} - commit_tag: ${{ env.commit_sha }} + commit_tag: ${{ github.sha }} run: | if [[ ${{ inputs.environment }} == 'production' ]]; then echo "Error: Deployment to production environment is not allowed as there is no docker image (i.e. the AWS deploy on staging was unsuccessful for this commit)." @@ -109,7 +100,7 @@ jobs: id: update-image-tags env: registry: ${{ steps.ecr-login.outputs.registry }} - commit_tag: ${{ inputs.ref || github.sha }} + commit_tag: ${{ github.sha }} readable_tag: ${{ inputs.environment }}-${{ env.additional-tag }} run: | manifest=$(aws ecr batch-get-image --repository-name $repository --image-ids imageTag=$commit_tag --output text --query images[].imageManifest) diff --git a/.github/workflows/manual_review_code_pipeline.yml b/.github/workflows/manual_review_code_pipeline.yml index 15e17b2ae..2ea0719ca 100644 --- a/.github/workflows/manual_review_code_pipeline.yml +++ b/.github/workflows/manual_review_code_pipeline.yml @@ -1,51 +1,29 @@ -name: Manual review app build and deploy +name: Manual review app code pipeline concurrency: - group: deploy-review${{ inputs.pr_number }} + group: review-${{ inputs.review_app_key }} on: workflow_dispatch: inputs: - pr_number: + review_app_key: required: true type: string - description: "The PR number of the review app to deploy code for. Note: this is NOT the ticket number" - -permissions: {} + description: "The review app ID to deploy code for." defaults: run: shell: bash jobs: - get_pr_head_sha: - name: Get PR HEAD SHA - runs-on: ubuntu-latest - outputs: - pr_head_sha: ${{ steps.get_sha.outputs.pr_head_sha }} - steps: - - name: Get PR HEAD SHA - id: get_sha - uses: actions/github-script@v7 - with: - script: | - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: parseInt('${{ inputs.pr_number }}'), - }); - core.setOutput('pr_head_sha', pr.head.sha); - code: name: Deploy review app code - needs: [get_pr_head_sha] uses: ./.github/workflows/aws_deploy.yml with: aws_account_id: 837698168072 aws_role_prefix: core-dev - aws_task_prefix: core-review-${{ inputs.pr_number }} - concurrency_tag: ${{ inputs.pr_number }} + aws_task_prefix: core-review-${{ inputs.review_app_key }} + concurrency_tag: ${{ inputs.review_app_key }} environment: review - ref: ${{ needs.get_pr_head_sha.outputs.pr_head_sha }} permissions: id-token: write diff --git a/.github/workflows/production_pipeline.yml b/.github/workflows/production_pipeline.yml index 45a31167f..b4a188415 100644 --- a/.github/workflows/production_pipeline.yml +++ b/.github/workflows/production_pipeline.yml @@ -3,6 +3,7 @@ name: Production CI/CD Pipeline on: release: types: [released] + workflow_dispatch: defaults: run: diff --git a/.github/workflows/review_app_prompt.yml b/.github/workflows/review_app_prompt.yml deleted file mode 100644 index 9c795cfa9..000000000 --- a/.github/workflows/review_app_prompt.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Review app deploy prompt - -on: - pull_request: - types: [opened] - -jobs: - prompt: - name: Add review app deploy instructions - runs-on: ubuntu-latest - permissions: - pull-requests: write - - steps: - - name: Comment with deploy instructions - uses: actions/github-script@v7 - with: - script: | - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: 'To deploy a review app for this PR, comment `/deploy-review`.', - }); diff --git a/.github/workflows/review_pipeline.yml b/.github/workflows/review_pipeline.yml index 13b1fbf54..b31f81e23 100644 --- a/.github/workflows/review_pipeline.yml +++ b/.github/workflows/review_pipeline.yml @@ -1,162 +1,57 @@ name: Review app pipeline +concurrency: + group: review-${{ github.event.pull_request.number }} + on: - issue_comment: - types: [created] - workflow_dispatch: - inputs: - pr_number: - required: true - type: string - description: "The number of the PR for which to deploy a review app. Note: this is NOT the ticket number" pull_request: - types: [synchronize] - -concurrency: - group: deploy-review${{ github.event.pull_request.number || inputs.pr_number || github.event.issue.number }} + types: + - opened + - synchronize + - reopened + workflow_dispatch: -permissions: {} +defaults: + run: + shell: bash jobs: - get_pr_details: - name: Get PR details - if: github.event_name == 'workflow_dispatch' || (github.event.issue.pull_request && startsWith(github.event.comment.body, '/deploy-review')) || github.event_name == 'pull_request' - runs-on: ubuntu-latest - outputs: - pr_number: ${{ steps.get_pr_details.outputs.pr_number }} - pr_head_sha: ${{ steps.get_pr_details.outputs.pr_head_sha }} - steps: - - name: Get PR number and HEAD SHA - id: get_pr_details - uses: actions/github-script@v7 - with: - script: | - let prNumber; - if (context.eventName === 'workflow_dispatch') { - prNumber = '${{ inputs.pr_number }}'; - } else if (context.eventName === 'pull_request') { - prNumber = context.payload.pull_request.number.toString(); - } else { - prNumber = context.issue.number.toString(); - } - core.setOutput('pr_number', prNumber); - const { data: pr } = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: parseInt(prNumber), - }); - core.setOutput('pr_head_sha', pr.head.sha); - - check_deployment_started: - name: Check if deployment has been started - if: github.event_name == 'pull_request' - needs: [get_pr_details] - runs-on: ubuntu-latest - permissions: - pull-requests: read - outputs: - started: ${{ steps.check.outputs.started }} - steps: - - name: Check for previous deployment workflow runs - id: check - uses: actions/github-script@v7 - with: - script: | - const prNumber = '${{ needs.get_pr_details.outputs.pr_number }}'; - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: parseInt(prNumber), - }); - const deployComment = comments.find(c => c.body === 'Starting review app deployment...'); - core.setOutput('started', deployComment ? 'true' : 'false'); - - deployment_started_comment: - name: Comment deployment started - if: github.event_name != 'pull_request' - needs: [get_pr_details] - runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - - name: Comment on PR - uses: actions/github-script@v7 - with: - script: | - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: ${{ needs.get_pr_details.outputs.pr_number }}, - body: 'Starting review app deployment...', - }); - infra: name: Deploy review app infrastructure - if: github.event_name != 'pull_request' - needs: [get_pr_details] uses: communitiesuk/submit-social-housing-lettings-and-sales-data-infrastructure/.github/workflows/create_review_app_infra.yml@main with: - key: ${{ needs.get_pr_details.outputs.pr_number }} + key: ${{ github.event.pull_request.number }} app_repo_role: arn:aws:iam::815624722760:role/core-application-repo permissions: id-token: write code: name: Deploy review app code - if: github.event_name != 'pull_request' - needs: [get_pr_details, infra] - uses: ./.github/workflows/aws_deploy.yml - with: - aws_account_id: 837698168072 - aws_role_prefix: core-dev - aws_task_prefix: core-review-${{ needs.get_pr_details.outputs.pr_number }} - concurrency_tag: ${{ needs.get_pr_details.outputs.pr_number }} - environment: review - ref: ${{ needs.get_pr_details.outputs.pr_head_sha }} - permissions: - id-token: write - - auto_update_code: - name: Auto-update review app code - if: github.event_name == 'pull_request' && needs.check_deployment_started.outputs.started == 'true' - needs: [get_pr_details, check_deployment_started] + needs: [infra] uses: ./.github/workflows/aws_deploy.yml with: aws_account_id: 837698168072 aws_role_prefix: core-dev - aws_task_prefix: core-review-${{ needs.get_pr_details.outputs.pr_number }} - concurrency_tag: ${{ needs.get_pr_details.outputs.pr_number }} + aws_task_prefix: core-review-${{ github.event.pull_request.number }} + concurrency_tag: ${{ github.event.pull_request.number }} environment: review - ref: ${{ needs.get_pr_details.outputs.pr_head_sha }} permissions: id-token: write comment: name: Add link to PR - if: github.event_name != 'pull_request' - needs: [get_pr_details, code] + needs: [code] runs-on: ubuntu-latest permissions: + issues: write pull-requests: write steps: - name: Comment on PR with URL - uses: actions/github-script@v7 + uses: unsplash/comment-on-pr@v1.3.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - script: | - const prNumber = ${{ needs.get_pr_details.outputs.pr_number }}; - const body = `Created review app at https://review.submit-social-housing-data.communities.gov.uk/${prNumber}. Note that the review app will be automatically deprovisioned after 30 days and will need the review app pipeline running again.`; - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - }); - const duplicate = comments.find(c => c.body.startsWith('Created review app at')); - if (!duplicate) { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body: body, - }); - } + msg: "Created review app at https://review.submit-social-housing-data.communities.gov.uk/${{ github.event.pull_request.number }}. Note that the review app will be automatically deprovisioned after 30 days and will need the review app pipeline running again." + check_for_duplicate_msg: true + duplicate_msg_pattern: Created review app at* diff --git a/.github/workflows/review_teardown_pipeline.yml b/.github/workflows/review_teardown_pipeline.yml index 0df3ad0b7..8925b3340 100644 --- a/.github/workflows/review_teardown_pipeline.yml +++ b/.github/workflows/review_teardown_pipeline.yml @@ -1,85 +1,27 @@ name: Review app teardown pipeline concurrency: - group: deploy-review${{ github.event.pull_request.number || inputs.pr_number }} + group: review-${{ github.event.pull_request.number }} on: pull_request: types: - closed workflow_dispatch: - inputs: - pr_number: - required: true - type: string - description: "The PR number of the review app to tear down. Note: this is NOT the ticket number" - -permissions: {} env: app_repo_role: arn:aws:iam::815624722760:role/core-application-repo aws_account_id: 837698168072 aws_region: eu-west-2 aws_role_prefix: core-dev + aws_task_prefix: core-review-${{ github.event.pull_request.number }} jobs: - get_pr_number: - name: Get PR number - runs-on: ubuntu-latest - outputs: - pr_number: ${{ steps.get.outputs.pr_number }} - steps: - - name: Get PR number - id: get - run: | - if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - echo "pr_number=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT - else - echo "pr_number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT - fi - - check_review_app_exists: - name: Check if review app exists - needs: [get_pr_number] - runs-on: ubuntu-latest - permissions: - id-token: write - outputs: - exists: ${{ steps.check.outputs.exists }} - steps: - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-region: ${{ env.aws_region }} - role-to-assume: ${{ env.app_repo_role }} - - - name: Configure AWS credentials for review environment - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-region: ${{ env.aws_region }} - role-to-assume: arn:aws:iam::${{ env.aws_account_id }}:role/${{ env.aws_role_prefix }}-deployment - role-chaining: true - - - name: Check if ECS service exists - id: check - env: - aws_task_prefix: core-review-${{ needs.get_pr_number.outputs.pr_number }} - run: | - if aws ecs describe-services --cluster ${{ env.aws_task_prefix }}-app --services ${{ env.aws_task_prefix }}-app --query "services[?status=='ACTIVE']" | grep -q 'serviceName'; then - echo "exists=true" >> $GITHUB_OUTPUT - else - echo "exists=false" >> $GITHUB_OUTPUT - fi - database: name: Drop database - if: needs.check_review_app_exists.outputs.exists == 'true' - needs: [get_pr_number, check_review_app_exists] runs-on: ubuntu-latest permissions: id-token: write - env: - aws_task_prefix: core-review-${{ needs.get_pr_number.outputs.pr_number }} steps: - name: Configure AWS credentials @@ -113,11 +55,10 @@ jobs: infra: name: Teardown review app - if: needs.check_review_app_exists.outputs.exists == 'true' - needs: [get_pr_number, check_review_app_exists, database] + needs: [database] uses: communitiesuk/submit-social-housing-lettings-and-sales-data-infrastructure/.github/workflows/destroy_review_app_infra.yml@main with: - key: ${{ needs.get_pr_number.outputs.pr_number }} + key: ${{ github.event.pull_request.number }} app_repo_role: arn:aws:iam::815624722760:role/core-application-repo permissions: id-token: write From 5ce2d0cb4764dd824ea406d8dce8958adf55a15f Mon Sep 17 00:00:00 2001 From: Samuel Young Date: Wed, 11 Mar 2026 17:43:28 +0000 Subject: [PATCH 5/5] =?UTF-8?q?CLDC-4164,=20CLDC-4165:=20Update=20purchase?= =?UTF-8?q?=20price=20minimum=20to=20=C2=A315,000=20for=202026=20onwards?= =?UTF-8?q?=20(#3198)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CLDC-4164: Update purchase price minimum to £15,000 for 2026 onwards * CLDC-4164: Apply £15,000 minimum validation to shared ownership purchase price * CLDC-4164: Update tests * fixup! CLDC-4164: Update tests add min tests for 25 and 26 --- .../form/sales/questions/purchase_price.rb | 2 +- app/models/form/sales/questions/value.rb | 2 +- .../purchase_price_outright_ownership_spec.rb | 2 +- .../form/sales/pages/purchase_price_spec.rb | 2 +- .../pages/value_shared_ownership_spec.rb | 2 +- .../sales/questions/purchase_price_spec.rb | 27 ++++++++++++++++--- .../models/form/sales/questions/value_spec.rb | 23 +++++++++++++--- 7 files changed, 49 insertions(+), 11 deletions(-) diff --git a/app/models/form/sales/questions/purchase_price.rb b/app/models/form/sales/questions/purchase_price.rb index d37d549ce..fbd3ea7a5 100644 --- a/app/models/form/sales/questions/purchase_price.rb +++ b/app/models/form/sales/questions/purchase_price.rb @@ -3,7 +3,7 @@ class Form::Sales::Questions::PurchasePrice < ::Form::Question super(id, hsh, page) @id = "value" @type = "numeric" - @min = 0 + @min = form.start_year_2026_or_later? ? 15_000 : 0 @step = 0.01 @width = 5 @prefix = "£" diff --git a/app/models/form/sales/questions/value.rb b/app/models/form/sales/questions/value.rb index c8b9cadd3..a1b4a155a 100644 --- a/app/models/form/sales/questions/value.rb +++ b/app/models/form/sales/questions/value.rb @@ -4,7 +4,7 @@ class Form::Sales::Questions::Value < ::Form::Question @id = "value" @copy_key = form.start_year_2025_or_later? ? "sales.sale_information.value.#{page.id}" : "sales.sale_information.value" @type = "numeric" - @min = 0 + @min = form.start_year_2026_or_later? ? 15_000 : 0 @step = 1 @width = 5 @prefix = "£" diff --git a/spec/models/form/sales/pages/purchase_price_outright_ownership_spec.rb b/spec/models/form/sales/pages/purchase_price_outright_ownership_spec.rb index 27ab0b422..c9bff2f95 100644 --- a/spec/models/form/sales/pages/purchase_price_outright_ownership_spec.rb +++ b/spec/models/form/sales/pages/purchase_price_outright_ownership_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Form::Sales::Pages::PurchasePriceOutrightOwnership, type: :model let(:page_id) { "purchase_price" } let(:page_definition) { nil } - let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1))) } + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1), start_year_2026_or_later?: false)) } it "has correct subsection" do expect(page.subsection).to eq(subsection) diff --git a/spec/models/form/sales/pages/purchase_price_spec.rb b/spec/models/form/sales/pages/purchase_price_spec.rb index ad857ee9f..f8bbde0e7 100644 --- a/spec/models/form/sales/pages/purchase_price_spec.rb +++ b/spec/models/form/sales/pages/purchase_price_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Form::Sales::Pages::PurchasePrice, type: :model do let(:subsection) { instance_double(Form::Subsection) } before do - allow(subsection).to receive(:form).and_return(instance_double(Form, start_year_2024_or_later?: false, start_date: Time.zone.local(2023, 4, 1))) + allow(subsection).to receive(:form).and_return(instance_double(Form, start_year_2024_or_later?: false, start_year_2026_or_later?: false, start_date: Time.zone.local(2023, 4, 1))) end it "has correct subsection" do diff --git a/spec/models/form/sales/pages/value_shared_ownership_spec.rb b/spec/models/form/sales/pages/value_shared_ownership_spec.rb index 579084b35..e7e709d28 100644 --- a/spec/models/form/sales/pages/value_shared_ownership_spec.rb +++ b/spec/models/form/sales/pages/value_shared_ownership_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Form::Sales::Pages::ValueSharedOwnership, type: :model do let(:page_id) { "value_shared_ownership" } let(:page_definition) { nil } - let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)), id: "shared_ownership") } + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1), start_year_2026_or_later?: false), id: "shared_ownership") } before do allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(false) diff --git a/spec/models/form/sales/questions/purchase_price_spec.rb b/spec/models/form/sales/questions/purchase_price_spec.rb index c3d2999a0..6790b4a35 100644 --- a/spec/models/form/sales/questions/purchase_price_spec.rb +++ b/spec/models/form/sales/questions/purchase_price_spec.rb @@ -1,11 +1,15 @@ require "rails_helper" RSpec.describe Form::Sales::Questions::PurchasePrice, type: :model do + include CollectionTimeHelper + subject(:question) { described_class.new(question_id, question_definition, page, ownershipsch: 1) } let(:question_id) { nil } let(:question_definition) { nil } - let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } + let(:start_year) { current_collection_start_year } + let(:start_year_2026_or_later?) { false } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: collection_start_date_for_year(start_year), start_year_2026_or_later?: start_year_2026_or_later?))) } it "has correct page" do expect(question.page).to eq(page) @@ -30,6 +34,8 @@ RSpec.describe Form::Sales::Questions::PurchasePrice, type: :model do context "when discounted ownership scheme" do subject(:question) { described_class.new(question_id, question_definition, page, ownershipsch: 2) } + let(:start_year) { 2023 } + it "has the correct question_number" do expect(question.question_number).to eq(100) end @@ -38,6 +44,8 @@ RSpec.describe Form::Sales::Questions::PurchasePrice, type: :model do context "when outright sale" do subject(:question) { described_class.new(question_id, question_definition, page, ownershipsch: 3) } + let(:start_year) { 2023 } + it "has the correct question_number" do expect(question.question_number).to eq(110) end @@ -51,7 +59,20 @@ RSpec.describe Form::Sales::Questions::PurchasePrice, type: :model do expect(question.prefix).to eq("£") end - it "has correct min" do - expect(question.min).to eq(0) + context "with year 2025", metadata: { year: 25 } do + let(:start_year) { 2025 } + + it "has correct min" do + expect(question.min).to eq(0) + end + end + + context "with year 2026", metadata: { year: 26 } do + let(:start_year) { 2026 } + let(:start_year_2026_or_later?) { true } + + it "has correct min" do + expect(question.min).to eq(15_000) + end end end diff --git a/spec/models/form/sales/questions/value_spec.rb b/spec/models/form/sales/questions/value_spec.rb index bed4c7f96..2ebbf6d89 100644 --- a/spec/models/form/sales/questions/value_spec.rb +++ b/spec/models/form/sales/questions/value_spec.rb @@ -1,11 +1,15 @@ require "rails_helper" RSpec.describe Form::Sales::Questions::Value, type: :model do + include CollectionTimeHelper + subject(:question) { described_class.new(question_id, question_definition, page) } let(:question_id) { nil } let(:question_definition) { nil } - let(:page) { instance_double(Form::Page, id: "value_shared_ownership", subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)), id: "shared_ownership")) } + let(:start_year) { current_collection_start_year } + let(:start_year_2026_or_later?) { false } + let(:page) { instance_double(Form::Page, id: "value_shared_ownership", subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: collection_start_date_for_year(start_year), start_year_2026_or_later?: start_year_2026_or_later?), id: "shared_ownership")) } before do allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(false) @@ -35,7 +39,20 @@ RSpec.describe Form::Sales::Questions::Value, type: :model do expect(question.prefix).to eq("£") end - it "has correct min" do - expect(question.min).to eq(0) + context "with year 2025", metadata: { year: 25 } do + let(:start_year) { 2025 } + + it "has correct min" do + expect(question.min).to eq(0) + end + end + + context "with year 2026", metadata: { year: 26 } do + let(:start_year) { 2026 } + let(:start_year_2026_or_later?) { true } + + it "has correct min" do + expect(question.min).to eq(15_000) + end end end