Skip to content

Commit 9581d1b

Browse files
Merge branch 'main' into dependabot/pip/docs/adr/assets/ADR-003/examples/python/pyjwt-2.12.0
2 parents fa6f42e + 298c253 commit 9581d1b

File tree

4 files changed

+320
-23
lines changed

4 files changed

+320
-23
lines changed

.github/workflows/preview-env.yaml

Lines changed: 216 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ env:
1717
AWS_REGION: eu-west-2
1818
PREVIEW_PREFIX: pr-
1919
MOCK_PREFIX: mock-
20+
PREVIEW_INT_PREFIX: int-
2021
PYTHON_VERSION: 3.14
2122
LAMBDA_RUNTIME: python3.14
2223
LAMBDA_HANDLER: lambda_handler.handler
@@ -25,7 +26,8 @@ env:
2526
PROXYGEN_KEY_ID: ${{ vars.PREVIEW_ENV_PROXYGEN_KEY_ID }}
2627
PROXYGEN_CLIENT_ID: ${{ vars.PREVIEW_ENV_PROXYGEN_CLIENT_ID }}
2728
PROXYGEN_API_NAME: ${{ vars.PROXYGEN_API_NAME }}
28-
BASE_URL: 'https://internal-dev.api.service.nhs.uk/${{ vars.PROXYGEN_API_NAME }}-pr-${{ github.event.pull_request.number }}'
29+
BASE_URL: "https://internal-dev.api.service.nhs.uk/${{ vars.PROXYGEN_API_NAME }}-pr-${{ github.event.pull_request.number }}"
30+
INT_BASE_URL: "https://internal-dev.api.service.nhs.uk/${{ vars.PROXYGEN_API_NAME }}-pri-${{ github.event.pull_request.number }}"
2931
ENV: "remote"
3032
PR_NUMBER: ${{ github.event.pull_request.number }}
3133
HOST: "internal-dev.api.service.nhs.uk"
@@ -93,7 +95,13 @@ jobs:
9395
run: |
9496
branch=$RAW_BRANCH_NAME
9597
if [ -z "$branch" ]; then branch="${{ github.ref_name }}"; fi
96-
safe=$(echo "$branch" | sed -E 's/[^a-zA-Z0-9._-]+/-/g' | tr '[:upper:]' '[:lower:]')
98+
99+
if [ "${{ github.actor }}" = "dependabot[bot]" ]; then
100+
safe="dependabot-${{ github.event.pull_request.number }}"
101+
else
102+
safe=$(echo "$branch" | sed -E 's/[^a-zA-Z0-9._-]+/-/g' | tr '[:upper:]' '[:lower:]')
103+
fi
104+
97105
echo "branch=$branch" >> $GITHUB_OUTPUT
98106
echo "safe=$safe" >> $GITHUB_OUTPUT
99107
@@ -103,6 +111,7 @@ jobs:
103111
SAFE=${{ steps.branch.outputs.safe }}
104112
PREFIX=${{ env.PREVIEW_PREFIX }}
105113
MOCK_PREFIX=${{ env.MOCK_PREFIX }}
114+
INT_PREFIX=${{ env.PREVIEW_INT_PREFIX }}
106115
MAX_FN_LEN=62
107116
MAX_PREFIX_LEN=${#PREFIX}
108117
if [ ${#MOCK_PREFIX} -gt "$MAX_PREFIX_LEN" ]; then
@@ -114,20 +123,24 @@ jobs:
114123
fi
115124
FN="${PREFIX}${SAFE}"
116125
MFN="${MOCK_PREFIX}${SAFE}"
126+
IFN="${INT_PREFIX}${SAFE}"
117127
echo "function_name=$FN" >> "$GITHUB_OUTPUT"
118128
echo "mock_function_name=$MFN" >> "$GITHUB_OUTPUT"
129+
echo "int_function_name=$IFN" >> "$GITHUB_OUTPUT"
119130
URL="https://${SAFE}.dev.endpoints.${{ env.PROXYGEN_API_NAME }}.national.nhs.uk"
120131
MOCK_URL="https://${SAFE}.m.dev.endpoints.${{ env.PROXYGEN_API_NAME }}.national.nhs.uk"
132+
INT_URL="https://${SAFE}.i.dev.endpoints.${{ env.PROXYGEN_API_NAME }}.national.nhs.uk"
121133
echo "preview_url=$URL" >> "$GITHUB_OUTPUT"
122134
echo "mock_preview_url=$MOCK_URL" >> "$GITHUB_OUTPUT"
135+
echo "int_preview_url=$INT_URL" >> "$GITHUB_OUTPUT"
123136
124-
# ---------- Handle application ----------
125-
- name: Create or update preview Lambda (on open/sync/reopen)
137+
# ---------- Handle application with mock ----------
138+
- name: Create or update preview Lambda with mock (on open/sync/reopen)
126139
if: github.event.action != 'closed'
127140
env:
128141
MOCK_URL: ${{ steps.names.outputs.mock_preview_url }}
129-
EXPIRY_THRESHOLD: ${{ secrets.APIM_TOKEN_EXPIRY_THRESHOLD }}
130-
JWKS_SECRET: ${{ secrets.JWKS_SECRET }}
142+
TOKEN_EXPIRY_THRESHOLD: ${{ secrets.APIM_TOKEN_EXPIRY_THRESHOLD }}
143+
JWKS_SECRET_NAME: ${{ secrets.JWKS_SECRET }}
131144
APIM_PRIVATE_KEY: ${{ secrets.APIM_PRIVATE_KEY }}
132145
APIM_APIKEY: ${{ secrets.APIM_APIKEY }}
133146
API_MTLS_CERT: ${{ secrets.API_MTLS_CERT }}
@@ -194,18 +207,104 @@ jobs:
194207
wait_for_lambda_ready
195208
fi
196209
197-
- name: Delete preview Lambda (on PR closed)
210+
- name: Delete preview Lambda with mock (on PR closed)
198211
if: github.event.action == 'closed'
199212
run: |
200213
FN="${{ steps.names.outputs.function_name }}"
201214
echo "Deleting preview function: $FN"
202215
aws lambda delete-function --function-name "$FN" || true
203216
204-
- name: Output function name
217+
- name: Output function name with mock
205218
run: |
206219
echo "function = ${{ steps.names.outputs.function_name }}"
207220
echo "url = ${{ steps.names.outputs.preview_url }}"
208221
222+
# ---------- Handle application with integration----------
223+
- name: Create or update preview Lambda with integration (on open/sync/reopen)
224+
if: github.event.action != 'closed' && github.event.pull_request.user.login != 'dependabot[bot]'
225+
env:
226+
MOCK_URL: ${{ steps.names.outputs.mock_preview_url }}
227+
TOKEN_EXPIRY_THRESHOLD: ${{ secrets.APIM_TOKEN_EXPIRY_THRESHOLD }}
228+
JWKS_SECRET_NAME: ${{ secrets.JWKS_SECRET }}
229+
APIM_PRIVATE_KEY: ${{ secrets.APIM_PRIVATE_KEY }}
230+
APIM_APIKEY: ${{ secrets.APIM_APIKEY }}
231+
API_MTLS_CERT: ${{ secrets.API_MTLS_CERT }}
232+
API_MTLS_KEY: ${{ secrets.API_MTLS_KEY }}
233+
run: |
234+
cd pathology-api/target/
235+
FN="${{ steps.names.outputs.int_function_name }}"
236+
EXPIRY_THRESHOLD="${TOKEN_EXPIRY_THRESHOLD:-30s}"
237+
JWKS_SECRET="${JWKS_SECRET_NAME:-/cds/pathology/dev/jwks/secret}"
238+
PRIVATE_KEY="${APIM_PRIVATE_KEY:-/cds/pathology/dev/apim/private-key}"
239+
API_KEY="${APIM_APIKEY:-/cds/pathology/dev/apim/api-key}"
240+
MTLS_CERT="${API_MTLS_CERT:-/cds/pathology/dev/mtls/client1-key-public}"
241+
MTLS_KEY="${API_MTLS_KEY:-/cds/pathology/dev/mtls/client1-key-secret}"
242+
echo "Deploying preview function: $FN"
243+
wait_for_lambda_ready() {
244+
while true; do
245+
status=$(aws lambda get-function-configuration --function-name "$FN" \
246+
--query 'LastUpdateStatus' \
247+
--output text 2>/dev/null || echo "Unknown")
248+
if [ "$status" = "Successful" ] || [ "$status" = "Unknown" ]; then
249+
break
250+
fi
251+
if [ "$status" = "Failed" ]; then
252+
echo "Lambda is in Failed state; check logs." >&2
253+
exit 1
254+
fi
255+
echo "Lambda update status: $status — waiting..."
256+
sleep 5
257+
done
258+
}
259+
if aws lambda get-function --function-name "$FN" >/dev/null 2>&1; then
260+
wait_for_lambda_ready
261+
aws lambda update-function-configuration --function-name "$FN" \
262+
--handler "${{ env.LAMBDA_HANDLER }}" \
263+
--environment "Variables={APIM_TOKEN_EXPIRY_THRESHOLD=$EXPIRY_THRESHOLD, \
264+
APIM_PRIVATE_KEY_NAME=$PRIVATE_KEY, \
265+
APIM_API_KEY_NAME=$API_KEY, \
266+
APIM_MTLS_CERT_NAME=$MTLS_CERT, \
267+
APIM_MTLS_KEY_NAME=$MTLS_KEY, \
268+
APIM_TOKEN_URL=$MOCK_URL/apim, \
269+
PDM_BUNDLE_URL=$MOCK_URL/pdm, \
270+
MNS_EVENT_URL=$MOCK_URL/mns, \
271+
JWKS_SECRET_NAME=$JWKS_SECRET}" || true
272+
wait_for_lambda_ready
273+
aws lambda update-function-code --function-name "$FN" \
274+
--zip-file "fileb://artifact.zip" \
275+
--publish
276+
else
277+
aws lambda create-function --function-name "$FN" \
278+
--runtime "${{ env.LAMBDA_RUNTIME }}" \
279+
--handler "${{ env.LAMBDA_HANDLER }}" \
280+
--zip-file "fileb://artifact.zip" \
281+
--role "${{ steps.role-select.outputs.lambda_role }}" \
282+
--environment "Variables={APIM_TOKEN_EXPIRY_THRESHOLD=$EXPIRY_THRESHOLD, \
283+
APIM_PRIVATE_KEY_NAME=$PRIVATE_KEY, \
284+
APIM_API_KEY_NAME=$API_KEY, \
285+
APIM_MTLS_CERT_NAME=$MTLS_CERT, \
286+
APIM_MTLS_KEY_NAME=$MTLS_KEY, \
287+
APIM_TOKEN_URL=$MOCK_URL/apim, \
288+
PDM_BUNDLE_URL=$MOCK_URL/pdm, \
289+
MNS_EVENT_URL=$MOCK_URL/mns, \
290+
JWKS_SECRET_NAME=$JWKS_SECRET}" \
291+
--publish
292+
wait_for_lambda_ready
293+
fi
294+
295+
- name: Delete preview Lambda with integration (on PR closed)
296+
if: github.event.action == 'closed' && github.event.pull_request.user.login != 'dependabot[bot]'
297+
run: |
298+
FN="${{ steps.names.outputs.int_function_name }}"
299+
echo "Deleting preview function: $FN"
300+
aws lambda delete-function --function-name "$FN" || true
301+
302+
- name: Output function name with integration
303+
if: github.event.pull_request.user.login != 'dependabot[bot]'
304+
run: |
305+
echo "function = ${{ steps.names.outputs.int_function_name }}"
306+
echo "url = ${{ steps.names.outputs.int_preview_url }}"
307+
209308
# ---------- Handle mock endpoints ----------
210309
- name: Create or update mock Lambda (on open/sync/reopen)
211310
if: github.event.action != 'closed'
@@ -378,6 +477,57 @@ jobs:
378477
echo "http_result=unexpected-status" >> "$GITHUB_OUTPUT"
379478
exit 0
380479
480+
- name: Smoke test int URL
481+
if: github.event.action != 'closed' && github.event.pull_request.user.login != 'dependabot[bot]'
482+
id: smoke-int
483+
env:
484+
PREVIEW_URL: ${{ steps.names.outputs.int_preview_url }}
485+
run: |
486+
if [ -z "$PREVIEW_URL" ] || [ "$PREVIEW_URL" = "null" ]; then
487+
echo "Int URL missing"
488+
echo "http_status=missing" >> "$GITHUB_OUTPUT"
489+
echo "http_result=missing-url" >> "$GITHUB_OUTPUT"
490+
exit 0
491+
fi
492+
493+
# Reachability check: allow 404 (app routes might not exist yet) but fail otherwise
494+
printf '%s' "$_cds_pathology_dev_mtls_client1_key_secret" > /tmp/client1-key.pem
495+
printf '%s' "$_cds_pathology_dev_mtls_client1_key_public" > /tmp/client1-cert.pem
496+
STATUS=$(curl \
497+
--cert /tmp/client1-cert.pem \
498+
--key /tmp/client1-key.pem \
499+
--silent \
500+
--output /tmp/preview.headers \
501+
--write-out '%{http_code}' \
502+
--head \
503+
--max-time 30 \
504+
-X GET "$PREVIEW_URL"/_status || true)
505+
rm -f /tmp/client1-key.pem
506+
rm -f /tmp/client1-cert.pem
507+
508+
if [ "$STATUS" = "404" ]; then
509+
echo "Int responded with expected 404"
510+
echo "http_status=404" >> "$GITHUB_OUTPUT"
511+
echo "http_result=allowed-404" >> "$GITHUB_OUTPUT"
512+
exit 0
513+
fi
514+
515+
if [[ "$STATUS" =~ ^[0-9]{3}$ ]] && [ "$STATUS" -ge 200 ] && [ "$STATUS" -lt 400 ]; then
516+
echo "Int responded with status $STATUS"
517+
echo "http_status=$STATUS" >> "$GITHUB_OUTPUT"
518+
echo "http_result=success" >> "$GITHUB_OUTPUT"
519+
exit 0
520+
fi
521+
522+
echo "Int responded with unexpected status $STATUS"
523+
if [ -f /tmp/preview.headers ]; then
524+
echo "Response headers:"
525+
cat /tmp/preview.headers
526+
fi
527+
echo "http_status=$STATUS" >> "$GITHUB_OUTPUT"
528+
echo "http_result=unexpected-status" >> "$GITHUB_OUTPUT"
529+
exit 0
530+
381531
- name: Get proxygen machine user details
382532
id: proxygen-machine-user
383533
uses: aws-actions/aws-secretsmanager-get-secrets@a9a7eb4e2f2871d30dc5b892576fde60a2ecc802
@@ -397,6 +547,18 @@ jobs:
397547
proxygen-client-id: ${{ env.PROXYGEN_CLIENT_ID }}
398548
proxygen-api-name: ${{ env.PROXYGEN_API_NAME }}
399549

550+
- name: Deploy int preview API proxy
551+
if: github.event.action != 'closed' && github.event.pull_request.user.login != 'dependabot[bot]'
552+
uses: ./.github/actions/proxy/deploy-proxy
553+
with:
554+
mtls-secret-name: ${{ env.MTLS_SECRET_NAME }}
555+
target-url: ${{ steps.names.outputs.int_preview_url }}
556+
proxy-base-path: "${{ env.PROXYGEN_API_NAME }}-pri-${{ github.event.pull_request.number }}"
557+
proxygen-key-secret: ${{ env._cds_pathology_dev_proxygen_proxygen_key_secret }}
558+
proxygen-key-id: ${{ env.PROXYGEN_KEY_ID }}
559+
proxygen-client-id: ${{ env.PROXYGEN_CLIENT_ID }}
560+
proxygen-api-name: ${{ env.PROXYGEN_API_NAME }}
561+
400562
- name: Tear down preview API proxy
401563
if: github.event.action == 'closed'
402564
uses: ./.github/actions/proxy/tear-down-proxy
@@ -407,6 +569,16 @@ jobs:
407569
proxygen-client-id: ${{ env.PROXYGEN_CLIENT_ID }}
408570
proxygen-api-name: ${{ env.PROXYGEN_API_NAME }}
409571

572+
- name: Tear down int preview API proxy
573+
if: github.event.action == 'closed' && github.event.pull_request.user.login != 'dependabot[bot]'
574+
uses: ./.github/actions/proxy/tear-down-proxy
575+
with:
576+
proxy-base-path: "${{ env.PROXYGEN_API_NAME }}-pri-${{ github.event.pull_request.number }}"
577+
proxygen-key-secret: ${{ env._cds_pathology_dev_proxygen_proxygen_key_secret }}
578+
proxygen-key-id: ${{ env.PROXYGEN_KEY_ID }}
579+
proxygen-client-id: ${{ env.PROXYGEN_CLIENT_ID }}
580+
proxygen-api-name: ${{ env.PROXYGEN_API_NAME }}
581+
410582
- name: Retrieve Apigee Token
411583
id: apigee-token
412584
shell: bash
@@ -495,7 +667,7 @@ jobs:
495667
name: ${{ steps.create-name.outputs.artefact-name }}
496668
path: coverage-reports/
497669
- name: "SonarCloud Scan"
498-
if: always() && github.event.action != 'closed'
670+
if: always() && github.event.action != 'closed' && github.actor != 'dependabot[bot]'
499671
uses: SonarSource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 #7.0.0
500672
env:
501673
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
@@ -510,18 +682,24 @@ jobs:
510682
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
511683
with:
512684
script: |
513-
const fn = '${{ steps.names.outputs.function_name }}';
514-
const mock_fn = '${{ steps.names.outputs.mock_function_name }}';
685+
const fn = '${{ steps.names.outputs.function_name }}' || 'not-set';
686+
const mock_fn = '${{ steps.names.outputs.mock_function_name }}' || 'not-set';
687+
const int_fn = '${{ steps.names.outputs.int_function_name }}' || 'not-set';
515688
const url = '${{ steps.names.outputs.preview_url }}';
516689
const mock_url = '${{ steps.names.outputs.mock_preview_url }}';
690+
const int_url = '${{ steps.names.outputs.int_preview_url }}';
517691
const proxy_url = '${{ env.BASE_URL }}';
692+
const int_proxy_url = '${{ env.INT_BASE_URL}}';
693+
const isDependabotPr = '${{ github.event.pull_request.user.login }}' === 'dependabot[bot]';
518694
const owner = context.repo.owner;
519695
const repo = context.repo.repo;
520696
const issueNumber = context.issue.number;
521697
const smokeStatus = '${{ steps.smoke-test.outputs.http_status }}' || 'n/a';
522698
const smokeResult = '${{ steps.smoke-test.outputs.http_result }}' || 'not-run';
523699
const smokeMockStatus = '${{ steps.smoke-mock.outputs.http_status }}' || 'n/a';
524700
const smokeMockResult = '${{ steps.smoke-mock.outputs.http_result }}' || 'not-run';
701+
const smokeIntStatus = '${{ steps.smoke-int.outputs.http_status }}' || 'n/a';
702+
const smokeIntResult = '${{ steps.smoke-int.outputs.http_result }}' || 'not-run';
525703
526704
const smokeLabels = {
527705
success: ':white_check_mark: Passed',
@@ -532,6 +710,19 @@ jobs:
532710
533711
const smokeReadable = smokeLabels[smokeResult] ?? smokeResult;
534712
const smokeMockReadable = smokeLabels[smokeMockResult] ?? smokeMockResult;
713+
const smokeIntReadable = smokeLabels[smokeIntResult] ?? smokeIntResult;
714+
const intUrlDisplay = isDependabotPr
715+
? 'Skipped for Dependabot PR'
716+
: `[${int_url}](${int_url}) — [Status](${int_url}/_status)`;
717+
const intSmokeDisplay = isDependabotPr
718+
? 'Skipped for Dependabot PR'
719+
: `${smokeIntReadable} (HTTP ${smokeIntStatus})`;
720+
const intProxyDisplay = isDependabotPr
721+
? 'Skipped for Dependabot PR'
722+
: `[${int_proxy_url}](${int_proxy_url})`;
723+
const intLambdaDisplay = isDependabotPr
724+
? 'Skipped for Dependabot PR'
725+
: int_fn;
535726
const { data: comments } = await github.rest.issues.listComments({
536727
owner,
537728
repo,
@@ -554,13 +745,20 @@ jobs:
554745
555746
const lines = [
556747
'**Deployment Complete**',
557-
`- Preview URL: [${url}](${url}) — [Status](${url}/_status)`,
558-
` - Smoke Test: ${smokeReadable} (HTTP ${smokeStatus})`,
559-
`- Mock URL: [${mock_url}](${mock_url})`,
560-
` - Smoke Mock Test: ${smokeMockReadable} (HTTP ${smokeMockStatus})`,
561-
`- Proxy URL: [${proxy_url}](${proxy_url})`,
562-
`- Lambda Function: ${fn}`,
563-
`- Mock Lambda Function: ${mock_fn}`,
748+
`- Preview with mock:`,
749+
` - URL: [${url}](${url}) — [Status](${url}/_status)`,
750+
` - Smoke test result: ${smokeReadable} (HTTP ${smokeStatus})`,
751+
` - Proxy URL: [${proxy_url}](${proxy_url})`,
752+
` - Lambda function: ${fn}`,
753+
`- Mock endpoints:`,
754+
` - URL: [${mock_url}](${mock_url})`,
755+
` - Smoke test result: ${smokeMockReadable} (HTTP ${smokeMockStatus})`,
756+
` - Lambda function: ${mock_fn}`,
757+
`- Preview with integration:`,
758+
` - URL: ${intUrlDisplay}`,
759+
` - Smoke test result: ${intSmokeDisplay}`,
760+
` - Proxy URL: ${intProxyDisplay}`,
761+
` - Lambda function: ${intLambdaDisplay}`,
564762
];
565763
566764
await github.rest.issues.createComment({

0 commit comments

Comments
 (0)