@@ -17,13 +17,11 @@ jobs:
1717 name : Manage preview environment
1818 runs-on : ubuntu-latest
1919
20- # Needed for OIDC → AWS (recommended)
2120 permissions :
2221 id-token : write
2322 contents : read
2423 pull-requests : write
2524
26- # One job per branch at a time
2725 concurrency :
2826 group : preview-${{ github.head_ref || github.ref_name }}
2927 cancel-in-progress : true
3533 - name : Checkout repo
3634 uses : actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
3735
38- # Configure AWS credentials (OIDC recommended)
3936 - name : Configure AWS credentials
4037 uses : aws-actions/configure-aws-credentials@4c2b9cc816c86555b61460789ac95da17d7e829b
4138 with :
@@ -49,35 +46,23 @@ jobs:
4946 - name : Compute branch metadata
5047 id : meta
5148 run : |
52- # For PRs, head_ref is the source branch name
5349 RAW_BRANCH="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME}}"
54-
55- # Sanitize branch name for tags / hostnames (lowercase, only allowed chars)
5650 SANITIZED_BRANCH=$(
5751 printf '%s' "$RAW_BRANCH" \
5852 | tr '[:upper:]' '[:lower:]' \
5953 | tr '._' '-' \
6054 | tr -c 'a-z0-9-' '-' \
6155 | sed -E 's/-{2,}/-/g; s/^-+//; s/-+$//'
6256 )
63-
64- # Last resort fallback if everything got stripped
6557 if [ -z "$SANITIZED_BRANCH" ]; then
6658 SANITIZED_BRANCH="invalid-branch-name"
6759 fi
68-
6960 echo "raw_branch=$RAW_BRANCH" >> $GITHUB_OUTPUT
7061 echo "branch_name=$SANITIZED_BRANCH" >> $GITHUB_OUTPUT
71-
72- # ECR repo URL (must match core stack's ECR repo)
7362 ECR_URL="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY_NAME}"
7463 echo "ecr_url=$ECR_URL" >> $GITHUB_OUTPUT
75-
76- # Terraform state key for this preview env
7764 TF_STATE_KEY="${PREVIEW_STATE_PREFIX}${SANITIZED_BRANCH}.tfstate"
7865 echo "tf_state_key=$TF_STATE_KEY" >> $GITHUB_OUTPUT
79-
80- # ALB listener rule priority - derive from PR number (must be unique per listener)
8166 if [ -n "${{ github.event.number }}" ]; then
8267 PRIORITY=$(( 1000 + ${{ github.event.number }} ))
8368 else
@@ -98,24 +83,20 @@ jobs:
9883 run : |
9984 IMAGE_TAG="${{ steps.meta.outputs.branch_name }}"
10085 ECR_URL="${{ steps.meta.outputs.ecr_url }}"
101-
10286 make build IMAGE_TAG="${IMAGE_TAG}" ECR_URL="${ECR_URL}"
10387
10488 - name : Push Docker image to ECR
10589 if : github.event.action != 'closed'
10690 run : |
10791 IMAGE_TAG="${{ steps.meta.outputs.branch_name }}"
10892 ECR_URL="${{ steps.meta.outputs.ecr_url }}"
109-
11093 docker push "${ECR_URL}:${IMAGE_TAG}"
11194
11295 - name : Setup Terraform
11396 uses : hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd
11497 with :
11598 terraform_version : 1.14.0
11699
117- # ---------- APPLY (PR opened / updated) ----------
118-
119100 - name : Terraform init (apply)
120101 if : github.event.action != 'closed'
121102 working-directory : infrastructure/environments/preview
@@ -133,25 +114,20 @@ jobs:
133114 TF_VAR_image_tag : ${{ steps.meta.outputs.branch_name }}
134115 TF_VAR_alb_rule_priority : ${{ steps.meta.outputs.alb_rule_priority }}
135116 run : |
136- terraform apply \
137- -auto-approve
117+ terraform apply -auto-approve
138118
139119 - name : Capture preview TF outputs
140120 if : github.event.action != 'closed'
141121 id : tf-output
142122 working-directory : infrastructure/environments/preview
143123 run : |
144124 terraform output -json > tf-output.json
145-
146125 URL=$(jq -r '.url.value' tf-output.json)
147126 echo "preview_url=$URL" >> $GITHUB_OUTPUT
148-
149127 TG=$(jq -r '.target_group_arn.value' tf-output.json)
150128 echo "target_group=$TG" >> $GITHUB_OUTPUT
151-
152129 ECS_SERVICE=$(jq -r '.ecs_service_name.value' tf-output.json)
153130 echo "ecs_service=$ECS_SERVICE" >> $GITHUB_OUTPUT
154-
155131 ECS_CLUSTER=$(jq -r '.ecs_cluster_name.value' tf-output.json)
156132 echo "ecs_cluster=$ECS_CLUSTER" >> $GITHUB_OUTPUT
157133
@@ -184,7 +160,6 @@ jobs:
184160 proxygen-api-name : ${{ vars.PROXYGEN_API_NAME }}
185161 proxygen-client-id : ${{ vars.PREVIEW_ENV_PROXYGEN_CLIENT_ID }}
186162
187- # ---------- Ensure re-deployment (PR updated) ----------
188163 - name : Force ECS service redeployment
189164 if : github.event.action == 'synchronize'
190165 id : await-redeployment
@@ -195,7 +170,6 @@ jobs:
195170 --force-new-deployment \
196171 --region ${{ env.AWS_REGION }}
197172
198- # ---------- DESTROY (PR closed) ----------
199173 - name : Terraform init (destroy)
200174 if : github.event.action == 'closed'
201175 working-directory : infrastructure/environments/preview
@@ -215,14 +189,13 @@ jobs:
215189 run : |
216190 terraform destroy -auto-approve
217191
218- # ---------- Wait on AWS tasks and notify ----------
219192 - name : Await deployment completion
220193 if : github.event.action != 'closed'
221194 run : |
222195 aws ecs wait services-stable \
223- --cluster ${{ steps.tf-output.outputs.ecs_cluster }} \
224- --services ${{ steps.tf-output.outputs.ecs_service }} \
225- --region ${{ env.AWS_REGION }}
196+ --cluster ${{ steps.tf-output.outputs.ecs_cluster }} \
197+ --services ${{ steps.tf-output.outputs.ecs_service }} \
198+ --region ${{ env.AWS_REGION }}
226199
227200 - name : Get mTLS certs for testing
228201 if : github.event.action != 'closed'
@@ -246,8 +219,6 @@ jobs:
246219 echo "http_result=missing-url" >> "$GITHUB_OUTPUT"
247220 exit 0
248221 fi
249-
250- # Reachability check: allow 404 (app routes might not exist yet) but fail otherwise
251222 printf '%s' "$_cds_gateway_dev_mtls_client1_key_secret" > /tmp/client1-key.pem
252223 printf '%s' "$_cds_gateway_dev_mtls_client1_key_public" > /tmp/client1-cert.pem
253224 STATUS=$(curl \
@@ -285,6 +256,138 @@ jobs:
285256 echo "http_result=unexpected-status" >> "$GITHUB_OUTPUT"
286257 exit 0
287258
259+ - name : Prepare mTLS cert files for tests
260+ if : github.event.action != 'closed'
261+ run : |
262+ printf '%s' "$_cds_gateway_dev_mtls_client1_key_secret" > /tmp/client1-key.pem
263+ printf '%s' "$_cds_gateway_dev_mtls_client1_key_public" > /tmp/client1-cert.pem
264+ chmod 600 /tmp/client1-key.pem /tmp/client1-cert.pem
265+
266+ - name : Run unit tests against preview
267+ if : github.event.action != 'closed'
268+ env :
269+ BASE_URL : ${{ steps.tf-output.outputs.preview_url }}
270+ MTLS_CERT : /tmp/client1-cert.pem
271+ MTLS_KEY : /tmp/client1-key.pem
272+ run : |
273+ echo "Running unit tests pointing at ${BASE_URL}"
274+ make test-unit
275+
276+ - name : Upload unit test results
277+ if : always()
278+ uses : actions/upload-artifact@v5
279+ with :
280+ name : unit-test-results
281+ path : gateway-api/test-artefacts/
282+ retention-days : 30
283+
284+ - name : Publish unit test results to summary
285+ if : always()
286+ uses : test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86
287+ with :
288+ paths : gateway-api/test-artefacts/unit-tests.xml
289+
290+ - name : Run contract tests against preview
291+ if : github.event.action != 'closed'
292+ env :
293+ BASE_URL : ${{ steps.tf-output.outputs.preview_url }}
294+ MTLS_CERT : /tmp/client1-cert.pem
295+ MTLS_KEY : /tmp/client1-key.pem
296+ run : |
297+ echo "Running contract tests pointing at ${BASE_URL}"
298+ make test-contract
299+
300+ - name : Upload contract test results
301+ if : always()
302+ uses : actions/upload-artifact@v5
303+ with :
304+ name : contract-test-results
305+ path : gateway-api/test-artefacts/
306+ retention-days : 30
307+
308+ - name : Publish contract test results to summary
309+ if : always()
310+ uses : test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86
311+ with :
312+ paths : gateway-api/test-artefacts/contract-tests.xml
313+
314+ - name : Run schema validation tests against preview
315+ if : github.event.action != 'closed'
316+ env :
317+ BASE_URL : ${{ steps.tf-output.outputs.preview_url }}
318+ MTLS_CERT : /tmp/client1-cert.pem
319+ MTLS_KEY : /tmp/client1-key.pem
320+ run : |
321+ echo "Running schema validation tests pointing at ${BASE_URL}"
322+ make test-schema
323+
324+ - name : Upload schema test results
325+ if : always()
326+ uses : actions/upload-artifact@v5
327+ with :
328+ name : schema-test-results
329+ path : gateway-api/test-artefacts/
330+ retention-days : 30
331+
332+ - name : Publish schema test results to summary
333+ if : always()
334+ uses : test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86
335+ with :
336+ paths : gateway-api/test-artefacts/schema-tests.xml
337+
338+ - name : Run integration tests against preview
339+ if : github.event.action != 'closed'
340+ env :
341+ BASE_URL : ${{ steps.tf-output.outputs.preview_url }}
342+ MTLS_CERT : /tmp/client1-cert.pem
343+ MTLS_KEY : /tmp/client1-key.pem
344+ run : |
345+ echo "Running integration tests pointing at ${BASE_URL}"
346+ make test-integration
347+
348+ - name : Upload integration test results
349+ if : always()
350+ uses : actions/upload-artifact@v5
351+ with :
352+ name : integration-test-results
353+ path : gateway-api/test-artefacts/
354+ retention-days : 30
355+
356+ - name : Publish integration test results to summary
357+ if : always()
358+ uses : test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86
359+ with :
360+ paths : gateway-api/test-artefacts/integration-tests.xml
361+
362+ - name : Run acceptance tests against preview
363+ if : github.event.action != 'closed'
364+ env :
365+ BASE_URL : ${{ steps.tf-output.outputs.preview_url }}
366+ MTLS_CERT : /tmp/client1-cert.pem
367+ MTLS_KEY : /tmp/client1-key.pem
368+ run : |
369+ echo "Running acceptance tests pointing at ${BASE_URL}"
370+ make test-acceptance
371+
372+ - name : Upload acceptance test results
373+ if : always()
374+ uses : actions/upload-artifact@v5
375+ with :
376+ name : acceptance-test-results
377+ path : gateway-api/test-artefacts/
378+ retention-days : 30
379+
380+ - name : Publish acceptance test results to summary
381+ if : always()
382+ uses : test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86
383+ with :
384+ paths : gateway-api/test-artefacts/acceptance-tests.xml
385+
386+ - name : Remove mTLS temp files
387+ if : github.event.action != 'closed'
388+ run : |
389+ rm -f /tmp/client1-key.pem /tmp/client1-cert.pem || true
390+
288391 - name : Comment function name on PR
289392 if : github.event_name == 'pull_request' && github.event.action != 'closed'
290393 uses : actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
@@ -300,27 +403,22 @@ jobs:
300403 const issueNumber = context.issue.number;
301404 const smokeStatus = '${{ steps.smoke-test.outputs.http_status }}' || 'n/a';
302405 const smokeResult = '${{ steps.smoke-test.outputs.http_result }}' || 'not-run';
303-
304406 const smokeLabels = {
305407 success: ':white_check_mark: Passed',
306408 'allowed-404': ':white_check_mark: Allowed 404',
307409 'unexpected-status': ':x: Unexpected status',
308410 'missing-url': ':x: Missing URL',
309411 };
310-
311412 const smokeReadable = smokeLabels[smokeResult] ?? smokeResult;
312-
313413 const { data: comments } = await github.rest.issues.listComments({
314414 owner,
315415 repo,
316416 issue_number: issueNumber,
317417 per_page: 100,
318418 });
319-
320419 for (const comment of comments) {
321420 const isBot = comment.user?.login === 'github-actions[bot]';
322421 const isPreviewUpdate = comment.body?.includes('Deployment Complete');
323-
324422 if (isBot && isPreviewUpdate) {
325423 await github.rest.issues.deleteComment({
326424 owner,
@@ -329,7 +427,6 @@ jobs:
329427 });
330428 }
331429 }
332-
333430 const lines = [
334431 '**Deployment Complete**',
335432 `- Preview URL: [${url}](${url}) — [Health endpoint](${url}/health)`,
@@ -339,15 +436,13 @@ jobs:
339436 `- ECS Service: \`${service}\``,
340437 `- ALB Target: \`${alb}\``,
341438 ];
342-
343439 await github.rest.issues.createComment({
344440 owner,
345441 repo,
346442 issue_number: issueNumber,
347443 body: lines.join('\n'),
348444 });
349445
350- # ---------- Security scanning ----------
351446 - name : Trivy filesystem scan
352447 if : github.event.action != 'closed'
353448 uses : nhs-england-tools/trivy-action/image-scan@3456c1657a37d500027fd782e6b08911725392da
@@ -356,8 +451,8 @@ jobs:
356451 artifact-name : trivy-scan-${{ steps.meta.outputs.branch_name }}
357452
358453 - name : Generate SBOM
359- uses : nhs-england-tools/trivy-action/sbom-scan@3456c1657a37d500027fd782e6b08911725392da
360454 if : github.event.action != 'closed'
455+ uses : nhs-england-tools/trivy-action/sbom-scan@3456c1657a37d500027fd782e6b08911725392da
361456 with :
362457 image-ref : ${{steps.meta.outputs.ecr_url}}:${{steps.meta.outputs.branch_name}}
363458 artifact-name : trivy-sbom-${{ steps.meta.outputs.branch_name }}
0 commit comments