Skip to content

Commit aa32cbb

Browse files
committed
[CDAPI-77] Create preview mock environments and pass env variable to lambdas
1 parent 30a543f commit aa32cbb

File tree

9 files changed

+233
-103
lines changed

9 files changed

+233
-103
lines changed

.github/actions/lint-terraform/action.yaml

Lines changed: 0 additions & 20 deletions
This file was deleted.

.github/workflows/preview-env.yaml

Lines changed: 184 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ permissions:
1616
env:
1717
AWS_REGION: eu-west-2
1818
PREVIEW_PREFIX: pr-
19+
MOCK_PREFIX: mock-
1920
PYTHON_VERSION: 3.14
2021
LAMBDA_RUNTIME: python3.14
2122
LAMBDA_HANDLER: lambda_handler.handler
23+
MOCK_LAMBDA_HANDLER: handler.handler
2224
MTLS_SECRET_NAME: ${{ vars.PREVIEW_ENV_MTLS_SECRET_NAME }}
2325
PROXYGEN_KEY_ID: ${{ vars.PREVIEW_ENV_PROXYGEN_KEY_ID }}
2426
PROXYGEN_CLIENT_ID: ${{ vars.PREVIEW_ENV_PROXYGEN_CLIENT_ID }}
@@ -50,6 +52,14 @@ jobs:
5052
run: |
5153
make build
5254
55+
# Place holder mock artifact packaging to allow testing of mock API in preview environment;
56+
# can be extended to build a real mock Lambda if needed
57+
- name: Package mock artifact
58+
run: |
59+
cd infrastructure/environments/preview
60+
rm -f mock_artifact.zip
61+
zip -r mock_artifact.zip .
62+
5363
- name: Select AWS role inputs
5464
id: role-select
5565
env:
@@ -88,25 +98,51 @@ jobs:
8898
run: |
8999
SAFE=${{ steps.branch.outputs.safe }}
90100
PREFIX=${{ env.PREVIEW_PREFIX }}
91-
MAX_FN_LEN=64
92-
MAX_SAFE_LEN=$((MAX_FN_LEN - ${#PREFIX}))
101+
MOCK_PREFIX=${{ env.MOCK_PREFIX }}
102+
MAX_FN_LEN=62
103+
MAX_PREFIX_LEN=${#PREFIX}
104+
if [ ${#MOCK_PREFIX} -gt "$MAX_PREFIX_LEN" ]; then
105+
MAX_PREFIX_LEN=${#MOCK_PREFIX}
106+
fi
107+
MAX_SAFE_LEN=$((MAX_FN_LEN - MAX_PREFIX_LEN))
93108
if [ ${#SAFE} -gt "$MAX_SAFE_LEN" ]; then
94109
SAFE=${SAFE:0:MAX_SAFE_LEN}
95110
fi
96111
FN="${PREFIX}${SAFE}"
112+
MFN="${MOCK_PREFIX}${SAFE}"
97113
echo "function_name=$FN" >> "$GITHUB_OUTPUT"
114+
echo "mock_function_name=$MFN" >> "$GITHUB_OUTPUT"
98115
URL="https://${SAFE}.dev.endpoints.${{ env.PROXYGEN_API_NAME }}.national.nhs.uk"
116+
MOCK_URL="https://${SAFE}.m.dev.endpoints.${{ env.PROXYGEN_API_NAME }}.national.nhs.uk"
99117
echo "preview_url=$URL" >> "$GITHUB_OUTPUT"
118+
echo "mock_preview_url=$MOCK_URL" >> "$GITHUB_OUTPUT"
100119
120+
# ---------- Handle application ----------
101121
- name: Create or update preview Lambda (on open/sync/reopen)
102122
if: github.event.action != 'closed'
123+
env:
124+
MOCK_URL: ${{ steps.names.outputs.mock_preview_url }}
125+
EXPIRY_THRESHOLD: ${{ secrets.APIM_TOKEN_EXPIRY_THRESHOLD }}
126+
JWKS_SECRET: ${{ secrets.JWKS_SECRET }}
127+
APIM_PRIVATE_KEY: ${{ secrets.APIM_PRIVATE_KEY }}
128+
APIM_APIKEY: ${{ secrets.APIM_APIKEY }}
129+
API_MTLS_CERT: ${{ secrets.API_MTLS_CERT }}
130+
API_MTLS_KEY: ${{ secrets.API_MTLS_KEY }}
103131
run: |
104132
cd pathology-api/target/
105133
FN="${{ steps.names.outputs.function_name }}"
134+
EXPIRY_THRESHOLD="${TOKEN_EXPIRY_THRESHOLD:-30s}"
135+
JWKS_SECRET="${JWKS_SECRET_NAME:-/cds/pathology/dev/jwks/secret}"
136+
PRIVATE_KEY="${APIM_PRIVATE_KEY:-/cds/pathology/dev/apim/private-key}"
137+
API_KEY="${APIM_APIKEY:-/cds/pathology/dev/apim/api-key}"
138+
MTLS_CERT="${API_MTLS_CERT:-/cds/pathology/dev/mtls/client1-key-public}"
139+
MTLS_KEY="${API_MTLS_KEY:-/cds/pathology/dev/mtls/client1-key-secret}"
106140
echo "Deploying preview function: $FN"
107141
wait_for_lambda_ready() {
108142
while true; do
109-
status=$(aws lambda get-function-configuration --function-name "$FN" --query 'LastUpdateStatus' --output text 2>/dev/null || echo "Unknown")
143+
status=$(aws lambda get-function-configuration --function-name "$FN" \
144+
--query 'LastUpdateStatus' \
145+
--output text 2>/dev/null || echo "Unknown")
110146
if [ "$status" = "Successful" ] || [ "$status" = "Unknown" ]; then
111147
break
112148
fi
@@ -120,15 +156,36 @@ jobs:
120156
}
121157
if aws lambda get-function --function-name "$FN" >/dev/null 2>&1; then
122158
wait_for_lambda_ready
123-
aws lambda update-function-configuration --function-name "$FN" --handler "${{ env.LAMBDA_HANDLER }}" || true
159+
aws lambda update-function-configuration --function-name "$FN" \
160+
--handler "${{ env.LAMBDA_HANDLER }}" \
161+
--environment "Variables={APIM_TOKEN_EXPIRY_THRESHOLD=$EXPIRY_THRESHOLD, \
162+
APIM_PRIVATE_KEY_NAME=$PRIVATE_KEY, \
163+
APIM_API_KEY_NAME=$API_KEY, \
164+
APIM_MTLS_CERT_NAME=$MTLS_CERT, \
165+
APIM_MTLS_KEY_NAME=$MTLS_KEY, \
166+
APIM_TOKEN_URL=$MOCK_URL/apim, \
167+
PDM_BUNDLE_URL=$MOCK_URL/pdm, \
168+
MNS_EVENT_URL=$MOCK_URL/mns, \
169+
JWKS_SECRET_NAME=$JWKS_SECRET}" || true
124170
wait_for_lambda_ready
125-
aws lambda update-function-code --function-name "$FN" --zip-file "fileb://artifact.zip" --publish
171+
aws lambda update-function-code --function-name "$FN" \
172+
--zip-file "fileb://artifact.zip" \
173+
--publish
126174
else
127175
aws lambda create-function --function-name "$FN" \
128176
--runtime "${{ env.LAMBDA_RUNTIME }}" \
129177
--handler "${{ env.LAMBDA_HANDLER }}" \
130178
--zip-file "fileb://artifact.zip" \
131179
--role "${{ steps.role-select.outputs.lambda_role }}" \
180+
--environment "Variables={APIM_TOKEN_EXPIRY_THRESHOLD=$EXPIRY_THRESHOLD, \
181+
APIM_PRIVATE_KEY_NAME=$PRIVATE_KEY, \
182+
APIM_API_KEY_NAME=$API_KEY, \
183+
APIM_MTLS_CERT_NAME=$MTLS_CERT, \
184+
APIM_MTLS_KEY_NAME=$MTLS_KEY, \
185+
APIM_TOKEN_URL=$MOCK_URL/apim, \
186+
PDM_BUNDLE_URL=$MOCK_URL/pdm, \
187+
MNS_EVENT_URL=$MOCK_URL/mns, \
188+
JWKS_SECRET_NAME=$JWKS_SECRET}" \
132189
--publish
133190
wait_for_lambda_ready
134191
fi
@@ -145,6 +202,65 @@ jobs:
145202
echo "function = ${{ steps.names.outputs.function_name }}"
146203
echo "url = ${{ steps.names.outputs.preview_url }}"
147204
205+
# ---------- Handle mock endpoints ----------
206+
- name: Create or update mock Lambda (on open/sync/reopen)
207+
if: github.event.action != 'closed'
208+
env:
209+
TOKEN_EXPIRY_TIME: ${{ secrets.TOKEN_LIFETIME }}
210+
run: |
211+
cd infrastructure/environments/preview
212+
MFN="${{ steps.names.outputs.mock_function_name }}"
213+
SAFE="${{ steps.branch.outputs.safe }}"
214+
TOKEN_LIFETIME="${TOKEN_EXPIRY_TIME:-15m}"
215+
echo "Deploying mock function: $MFN"
216+
wait_for_lambda_ready() {
217+
while true; do
218+
status=$(aws lambda get-function-configuration --function-name "$MFN" --query 'LastUpdateStatus' --output text 2>/dev/null || echo "Unknown")
219+
if [ "$status" = "Successful" ] || [ "$status" = "Unknown" ]; then
220+
break
221+
fi
222+
if [ "$status" = "Failed" ]; then
223+
echo "Lambda is in Failed state; check logs." >&2
224+
exit 1
225+
fi
226+
echo "Lambda update status: $status — waiting..."
227+
sleep 5
228+
done
229+
}
230+
if aws lambda get-function --function-name "$MFN" >/dev/null 2>&1; then
231+
wait_for_lambda_ready
232+
aws lambda update-function-configuration --function-name "$MFN" \
233+
--handler "${{ env.MOCK_LAMBDA_HANDLER }}" \
234+
--environment "Variables={CLIENT_PUBLIC_KEY_ARN=mock, \
235+
DDB_INDEX_TAG=$SAFE, \
236+
TOKEN_LIFETIME=$TOKEN_LIFETIME}" || true
237+
wait_for_lambda_ready
238+
aws lambda update-function-code --function-name "$MFN" --zip-file "fileb://mock_artifact.zip" --publish
239+
else
240+
aws lambda create-function --function-name "$MFN" \
241+
--runtime "${{ env.LAMBDA_RUNTIME }}" \
242+
--handler "${{ env.MOCK_LAMBDA_HANDLER }}" \
243+
--zip-file "fileb://mock_artifact.zip" \
244+
--role "${{ steps.role-select.outputs.lambda_role }}" \
245+
--environment "Variables={CLIENT_PUBLIC_KEY_ARN=mock, \
246+
DDB_INDEX_TAG=$SAFE, \
247+
TOKEN_LIFETIME=$TOKEN_LIFETIME}" \
248+
--publish
249+
wait_for_lambda_ready
250+
fi
251+
252+
- name: Delete mock Lambda (on PR closed)
253+
if: github.event.action == 'closed'
254+
run: |
255+
MFN="${{ steps.names.outputs.mock_function_name }}"
256+
echo "Deleting mock function: $MFN"
257+
aws lambda delete-function --function-name "$MFN" || true
258+
259+
- name: Output mock function name
260+
run: |
261+
echo "mock_function = ${{ steps.names.outputs.mock_function_name }}"
262+
echo "mock_url = ${{ steps.names.outputs.mock_preview_url }}"
263+
148264
# ---------- Wait on AWS tasks and notify ----------
149265
- name: Get mTLS certs for testing
150266
if: github.event.action != 'closed'
@@ -207,6 +323,57 @@ jobs:
207323
echo "http_result=unexpected-status" >> "$GITHUB_OUTPUT"
208324
exit 0
209325
326+
- name: Smoke test mock URL
327+
if: github.event.action != 'closed'
328+
id: smoke-mock
329+
env:
330+
PREVIEW_URL: ${{ steps.names.outputs.mock_preview_url }}
331+
run: |
332+
if [ -z "$PREVIEW_URL" ] || [ "$PREVIEW_URL" = "null" ]; then
333+
echo "Mock URL missing"
334+
echo "http_status=missing" >> "$GITHUB_OUTPUT"
335+
echo "http_result=missing-url" >> "$GITHUB_OUTPUT"
336+
exit 0
337+
fi
338+
339+
# Reachability check: allow 404 (app routes might not exist yet) but fail otherwise
340+
printf '%s' "$_cds_pathology_dev_mtls_client1_key_secret" > /tmp/client1-key.pem
341+
printf '%s' "$_cds_pathology_dev_mtls_client1_key_public" > /tmp/client1-cert.pem
342+
STATUS=$(curl \
343+
--cert /tmp/client1-cert.pem \
344+
--key /tmp/client1-key.pem \
345+
--silent \
346+
--output /tmp/preview.headers \
347+
--write-out '%{http_code}' \
348+
--head \
349+
--max-time 30 \
350+
-X GET "$PREVIEW_URL"/_status || true)
351+
rm -f /tmp/client1-key.pem
352+
rm -f /tmp/client1-cert.pem
353+
354+
if [ "$STATUS" = "404" ]; then
355+
echo "Mock responded with expected 404"
356+
echo "http_status=404" >> "$GITHUB_OUTPUT"
357+
echo "http_result=allowed-404" >> "$GITHUB_OUTPUT"
358+
exit 0
359+
fi
360+
361+
if [[ "$STATUS" =~ ^[0-9]{3}$ ]] && [ "$STATUS" -ge 200 ] && [ "$STATUS" -lt 400 ]; then
362+
echo "Mock responded with status $STATUS"
363+
echo "http_status=$STATUS" >> "$GITHUB_OUTPUT"
364+
echo "http_result=success" >> "$GITHUB_OUTPUT"
365+
exit 0
366+
fi
367+
368+
echo "Mock responded with unexpected status $STATUS"
369+
if [ -f /tmp/preview.headers ]; then
370+
echo "Response headers:"
371+
cat /tmp/preview.headers
372+
fi
373+
echo "http_status=$STATUS" >> "$GITHUB_OUTPUT"
374+
echo "http_result=unexpected-status" >> "$GITHUB_OUTPUT"
375+
exit 0
376+
210377
- name: Get proxygen machine user details
211378
id: proxygen-machine-user
212379
uses: aws-actions/aws-secretsmanager-get-secrets@a9a7eb4e2f2871d30dc5b892576fde60a2ecc802
@@ -220,7 +387,7 @@ jobs:
220387
with:
221388
mtls-secret-name: ${{ env.MTLS_SECRET_NAME }}
222389
target-url: ${{ steps.names.outputs.preview_url }}
223-
proxy-base-path: '${{ env.PROXYGEN_API_NAME }}-pr-${{ github.event.pull_request.number }}'
390+
proxy-base-path: "${{ env.PROXYGEN_API_NAME }}-pr-${{ github.event.pull_request.number }}"
224391
proxygen-key-secret: ${{ env._cds_pathology_dev_proxygen_proxygen_key_secret }}
225392
proxygen-key-id: ${{ env.PROXYGEN_KEY_ID }}
226393
proxygen-client-id: ${{ env.PROXYGEN_CLIENT_ID }}
@@ -230,7 +397,7 @@ jobs:
230397
if: github.event.action == 'closed'
231398
uses: ./.github/actions/proxy/tear-down-proxy
232399
with:
233-
proxy-base-path: '${{ env.PROXYGEN_API_NAME }}-pr-${{ github.event.pull_request.number }}'
400+
proxy-base-path: "${{ env.PROXYGEN_API_NAME }}-pr-${{ github.event.pull_request.number }}"
234401
proxygen-key-secret: ${{ env._cds_pathology_dev_proxygen_proxygen_key_secret }}
235402
proxygen-key-id: ${{ env.PROXYGEN_KEY_ID }}
236403
proxygen-client-id: ${{ env.PROXYGEN_CLIENT_ID }}
@@ -242,13 +409,17 @@ jobs:
242409
with:
243410
script: |
244411
const fn = '${{ steps.names.outputs.function_name }}';
412+
const mock_fn = '${{ steps.names.outputs.mock_function_name }}';
245413
const url = '${{ steps.names.outputs.preview_url }}';
414+
const mock_url = '${{ steps.names.outputs.mock_preview_url }}';
246415
const proxy_url = 'https://internal-dev.api.service.nhs.uk/${{ env.PROXYGEN_API_NAME }}-pr-${{ github.event.pull_request.number }}';
247416
const owner = context.repo.owner;
248417
const repo = context.repo.repo;
249418
const issueNumber = context.issue.number;
250419
const smokeStatus = '${{ steps.smoke-test.outputs.http_status }}' || 'n/a';
251420
const smokeResult = '${{ steps.smoke-test.outputs.http_result }}' || 'not-run';
421+
const smokeMockStatus = '${{ steps.smoke-mock.outputs.http_status }}' || 'n/a';
422+
const smokeMockResult = '${{ steps.smoke-mock.outputs.http_result }}' || 'not-run';
252423
253424
const smokeLabels = {
254425
success: ':white_check_mark: Passed',
@@ -258,7 +429,7 @@ jobs:
258429
};
259430
260431
const smokeReadable = smokeLabels[smokeResult] ?? smokeResult;
261-
432+
const smokeMockReadable = smokeLabels[smokeMockResult] ?? smokeMockResult;
262433
const { data: comments } = await github.rest.issues.listComments({
263434
owner,
264435
repo,
@@ -281,10 +452,13 @@ jobs:
281452
282453
const lines = [
283454
'**Deployment Complete**',
284-
`- Preview URL: [${url}](${url}) — [Status endpoint](${url}/_status)`,
285-
`- Smoke Test: ${smokeReadable} (HTTP ${smokeStatus})`,
455+
`- Preview URL: [${url}](${url}) — [Status](${url}/_status)`,
456+
` - Smoke Test: ${smokeReadable} (HTTP ${smokeStatus})`,
457+
`- Mock URL: [${mock_url}](${mock_url})`,
458+
` - Smoke Mock Test: ${smokeMockReadable} (HTTP ${smokeMockStatus})`,
286459
`- Proxy URL: [${proxy_url}](${proxy_url})`,
287460
`- Lambda Function: ${fn}`,
461+
`- Mock Lambda Function: ${mock_fn}`,
288462
];
289463
290464
await github.rest.issues.createComment({

.github/workflows/stage-1-commit.yaml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,6 @@ jobs:
8888
fetch-depth: 0 # Full history is needed to compare branches
8989
- name: "Check English usage"
9090
uses: ./.github/actions/check-english-usage
91-
lint-terraform:
92-
name: "Lint Terraform"
93-
runs-on: ubuntu-latest
94-
timeout-minutes: 2
95-
steps:
96-
- name: "Checkout code"
97-
uses: actions/checkout@v6
98-
- name: "Lint Terraform"
99-
uses: ./.github/actions/lint-terraform
10091
count-lines-of-code:
10192
name: "Count lines of code"
10293
runs-on: ubuntu-latest

.gitleaksignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ cd9c0efec38c5d63053dd865e5d4e207c0760d91:docs/guides/Perform_static_analysis.md:
66

77
pathology-api/pyproject.toml:ipv4:51
88
pathology-api/pyproject.toml:ipv4:50
9+
10+
mocks/pyproject.toml:ipv4:54
11+
mocks/pyproject.toml:ipv4:55

.tool-versions

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# This file is for you! Please, updated to the versions agreed by your team.
22

3-
terraform 1.7.0
43
pre-commit 3.6.0
54
gitleaks 8.18.4
65

@@ -15,7 +14,6 @@ gitleaks 8.18.4
1514
# docker/ghcr.io/make-ops-tools/gocloc latest@sha256:6888e62e9ae693c4ebcfed9f1d86c70fd083868acb8815fe44b561b9a73b5032 # SEE: https://github.com/make-ops-tools/gocloc/pkgs/container/gocloc
1615
# docker/ghcr.io/nhs-england-tools/github-runner-image 20230909-321fd1e-rt@sha256:ce4fd6035dc450a50d3cbafb4986d60e77cb49a71ab60a053bb1b9518139a646 # SEE: https://github.com/nhs-england-tools/github-runner-image/pkgs/container/github-runner-image
1716
# docker/hadolint/hadolint 2.12.0-alpine@sha256:7dba9a9f1a0350f6d021fb2f6f88900998a4fb0aaf8e4330aa8c38544f04db42 # SEE: https://hub.docker.com/r/hadolint/hadolint/tags
18-
# docker/hashicorp/terraform 1.12.2@sha256:b3d13c9037d2bd858fe10060999aa7ca56d30daafe067d7715b29b3d4f5b162f # SEE: https://hub.docker.com/r/hashicorp/terraform/tags
1917
# docker/koalaman/shellcheck latest@sha256:e40388688bae0fcffdddb7e4dea49b900c18933b452add0930654b2dea3e7d5c # SEE: https://hub.docker.com/r/koalaman/shellcheck/tags
2018
# docker/mstruebing/editorconfig-checker 2.7.1@sha256:dd3ca9ea50ef4518efe9be018d669ef9cf937f6bb5cfe2ef84ff2a620b5ddc24 # SEE: https://hub.docker.com/r/mstruebing/editorconfig-checker/tags
2119
# docker/sonarsource/sonar-scanner-cli 10.0@sha256:0bc49076468d2955948867620b2d98d67f0d59c0fd4a5ef1f0afc55cf86f2079 # SEE: https://hub.docker.com/r/sonarsource/sonar-scanner-cli/tags

0 commit comments

Comments
 (0)