Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
323bf16
[CDAPI-55]: Initial changes to swap lambda to expect test result bundle
nhsd-jack-wainwright Jan 7, 2026
0ebe90f
[CDAPI-55]: Ensured that Resources are correctly deserialized based o…
nhsd-jack-wainwright Jan 9, 2026
0a16de9
[CDAPI-55]: Updated lambda to accept Bundle within curl request
nhsd-jack-wainwright Jan 12, 2026
294e9b6
[CDAPI-55]: Updated Bundle resource to expect 'entry' json field
nhsd-jack-wainwright Jan 13, 2026
40883d4
[CDAPI-55]: Added additional unit tests for new FHIR elements
nhsd-jack-wainwright Jan 13, 2026
1e3e0e3
[CDAPI-55]: Added unit tests for resources.py
nhsd-jack-wainwright Jan 14, 2026
85ef9b9
[CDAPI-55]: Added validation to Identifier to check that the provided…
nhsd-jack-wainwright Jan 15, 2026
12d9bc2
[CDAPI-55]: Ensured that returned Bundle includes a last_updated field.
nhsd-jack-wainwright Jan 15, 2026
1596ad3
[CDAPI-55]: Updated integration tests to account for new endpoint def…
nhsd-jack-wainwright Jan 15, 2026
f0050e2
[CDAPI-55]: Added new API gateway mock for accessing lambda locally.
nhsd-jack-wainwright Jan 16, 2026
23312d3
[CDAPI-55]: Updated integration tests to account for them accessing t…
nhsd-jack-wainwright Jan 16, 2026
524b3e6
[CDAPI-55]: Updated acceptance tests to account for new Bundle endpoint
nhsd-jack-wainwright Jan 16, 2026
fb14262
[CDAPI-55]: Fixed schema tests
nhsd-jack-wainwright Jan 19, 2026
22cd629
[CDAPI-55]: Swapped default health path for local lambda action to po…
nhsd-jack-wainwright Jan 20, 2026
8e41655
[CDAPI-55]: Updated start-local-lambda action to run command in-line …
nhsd-jack-wainwright Jan 20, 2026
b7d482e
[CDAPI-55]: Ensured lambda container is built for x86 architecture
nhsd-jack-wainwright Jan 20, 2026
39866e5
[CDAPI-55]: Minor sonar fixes
nhsd-jack-wainwright Jan 20, 2026
cdf4dbf
[CDAPI-55]: Updated preview-env action to build lambda via the `build…
nhsd-jack-wainwright Jan 20, 2026
9f032dc
[CDAPI-55]: Added project setup to preview-env workflow
nhsd-jack-wainwright Jan 20, 2026
1d9078e
[CDAPI-55]: Minor fix to preview-env workflow environment variables
nhsd-jack-wainwright Jan 20, 2026
6a3dbaa
[CDAPI-55]: Updated preview-env workflow to pick up Python artifact f…
nhsd-jack-wainwright Jan 20, 2026
d4ac480
[CDAPI-55]: Updated preview environment workflow to reference correct…
nhsd-jack-wainwright Jan 20, 2026
d0cabf3
[CDAPI-55]: Swapped pathology lambda to utilise APIGatewayRestResolver
nhsd-jack-wainwright Jan 22, 2026
fdf348c
[CDAPI-55]: Swapped to APIGatewayHttpResolver
nhsd-jack-wainwright Jan 22, 2026
25c4c0d
[CDAPI-55]: Fixed local API gateway mock to send requests in expected…
nhsd-jack-wainwright Jan 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 2 additions & 23 deletions .github/actions/start-local-lambda/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ inputs:
health-path:
description: "Health probe path to POST"
required: false
default: "/2015-03-31/functions/function/invocations"
default: "/"
max-seconds:
description: "Maximum seconds to wait for readiness"
required: false
Expand All @@ -26,25 +26,4 @@ runs:
run: |
set -euo pipefail
echo "Starting local Lambda: '${{ inputs.deploy-command }}'"
nohup ${{ inputs.deploy-command }} >/tmp/lambda.log 2>&1 &
echo $! > /tmp/lambda.pid
echo "PID: $(cat /tmp/lambda.pid)"
- name: "Wait for Lambda to be ready"
shell: bash
run: |
set -euo pipefail
BASE_URL="${BASE_URL:-http://localhost:5001}"
HEALTH_URL="${BASE_URL}${{ inputs.health-path }}"
MAX="${{ inputs.max-seconds }}"
echo "Waiting for Lambda at ${HEALTH_URL} (max ${MAX}s)..."
for i in $(seq 1 "${MAX}"); do
if curl -sSf -X POST "${HEALTH_URL}" -d '{}' >/dev/null; then
echo "Lambda is ready"
exit 0
fi
sleep 1
done
echo "Lambda did not become ready in time"
echo "---- recent lambda log ----"
tail -n 200 /tmp/lambda.log || true
exit 1
bash -c "${{ inputs.deploy-command }}"
16 changes: 10 additions & 6 deletions .github/workflows/preview-env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ permissions:
env:
AWS_REGION: eu-west-2
PREVIEW_PREFIX: pr-
PYTHON_VERSION: 3.14
LAMBDA_RUNTIME: python3.14
LAMBDA_HANDLER: handler.handler
LAMBDA_HANDLER: lambda_handler.handler

jobs:
pr-preview:
Expand All @@ -34,13 +35,16 @@ jobs:
- name: Set up Python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
with:
python-version: "3.14"
python-version: "${{ env.PYTHON_VERSION }}"

- name: "Setup Python project"
uses: ./.github/actions/setup-python-project
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Package artifact
run: |
cd infrastructure/environments/preview
rm -f artifact.zip
zip -r artifact.zip .
make build

- name: Select AWS role inputs
id: role-select
Expand Down Expand Up @@ -86,7 +90,7 @@ jobs:
- name: Create or update preview Lambda (on open/sync/reopen)
if: github.event.action != 'closed'
run: |
cd infrastructure/environments/preview
cd pathology-api/target/
FN="${{ steps.names.outputs.function_name }}"
echo "Deploying preview function: $FN"
wait_for_lambda_ready() {
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/stage-2-test.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: "Test stage"

env:
BASE_URL: "http://localhost:5001"
BASE_URL: "http://localhost:5002"
HOST: "localhost"

on:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ pathology-api/test-artefacts/
**/.env

**/.DS_Store
**/.coverage
60 changes: 40 additions & 20 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ docker := doas docker
else
docker := docker
endif

dockerNetwork := pathology-local

# ==============================================================================

# Example CI/CD targets are: dependencies, build, publish, deploy, clean, etc.
Expand All @@ -17,49 +20,66 @@ endif
dependencies: # Install dependencies needed to build and test the project @Pipeline
cd pathology-api && poetry sync

.PHONY: build-pathology-api
build-pathology-api: dependencies
.PHONY: build
build: clean-artifacts dependencies
@cd pathology-api
@echo "Running type checks..."
@rm -rf target && rm -rf dist
@poetry run mypy --no-namespace-packages .
@echo "Packaging dependencies..."
@poetry build --format=wheel
@pip install "dist/pathology_api-0.1.0-py3-none-any.whl" --target "./target/pathology-api"
# Copy main file separately as it is not included within the package.
VERSION=$$(poetry version -s)
@pip install "dist/pathology_api-$$VERSION-py3-none-any.whl" --target "./target/pathology-api" --platform manylinux2014_x86_64 --only-binary=:all:
# Copy lambda_handler file separately as it is not included within the package.
@cp lambda_handler.py ./target/pathology-api/
@rm -rf ../infrastructure/images/pathology-api/resources/build/
@mkdir ../infrastructure/images/pathology-api/resources/build/
@cp -r ./target/pathology-api ../infrastructure/images/pathology-api/resources/build/
# Remove temporary build artefacts once build has completed
@rm -rf target && rm -rf dist
@cd ./target/pathology-api
@zip -r "../artifact.zip" .

.PHONY: build-images
build-images: build # Build the project artefact @Pipeline
@mkdir infrastructure/images/pathology-api/resources/build/
@cp pathology-api/target/artifact.zip infrastructure/images/pathology-api/resources/build/
@mkdir infrastructure/images/pathology-api/resources/build/pathology-api
@unzip infrastructure/images/pathology-api/resources/build/artifact.zip -d infrastructure/images/pathology-api/resources/build/pathology-api

.PHONY: build
build: build-pathology-api # Build the project artefact @Pipeline
@echo "Building Docker image using Docker. Utilising python version: ${PYTHON_VERSION} ..."
@$(docker) buildx build --load --provenance=false --build-arg PYTHON_VERSION=${PYTHON_VERSION} -t localhost/pathology-api-image infrastructure/images/pathology-api
@$(docker) buildx build --load --platform=linux/amd64 --provenance=false --build-arg PYTHON_VERSION=${PYTHON_VERSION} -t localhost/pathology-api-image infrastructure/images/pathology-api
@echo "Docker image 'pathology-api-image' built successfully!"

@echo "Building api-gateway-mock using Docker. Utilising python version: ${PYTHON_VERSION} ..."
@$(docker) buildx build --load --build-arg PYTHON_VERSION=${PYTHON_VERSION} -t localhost/api-gateway-mock-image infrastructure/images/api-gateway-mock
@echo "Docker image 'api-gateway-mock-image' built successfully!"

publish: # Publish the project artefact @Pipeline
# TODO: Implement the artefact publishing step

deploy: clean build # Deploy the project artefact to the target environment @Pipeline
@if [[ -n "$${IN_BUILD_CONTAINER}" ]]; then \
echo "Starting using local docker network ..." ; \
$(docker) run --name pathology-api -p 5001:8080 --network pathology-local -d localhost/pathology-api-image ; \
else \
$(docker) run --name pathology-api -p 5001:8080 -d localhost/pathology-api-image ; \
fi
deploy: clean-docker build-images # Deploy the project artefact to the target environment @Pipeline
$(docker) network create $(dockerNetwork) || echo "Docker network '$(dockerNetwork)' already exists."
$(docker) run --platform linux/amd64 --name pathology-api -p 5001:8080 --network $(dockerNetwork) -d localhost/pathology-api-image
$(docker) run --name api-gateway-mock -p 5002:5000 --network $(dockerNetwork) -d localhost/api-gateway-mock-image

clean-artifacts:
@echo "Removing build artefacts..."
@rm -rf infrastructure/images/pathology-api/resources/build/
@rm -rf pathology-api/target && rm -rf pathology-api/dist

clean:: stop # Clean-up project resources (main) @Operations
clean-docker: stop
@echo "Removing pathology API container..."
@$(docker) rm pathology-api || echo "No pathology API container currently exists."

@echo "Removing api-gateway-mock container..."
@$(docker) rm api-gateway-mock || echo "No api-gateway-mock container currently exists."

clean:: clean-artifacts clean-docker # Clean-up project resources (main) @Operations

.PHONY: stop
stop:
@echo "Stopping pathology API container..."
@$(docker) stop pathology-api || echo "No pathology API container currently running."

@echo "Stopping api-gateway-mock container..."
@$(docker) stop api-gateway-mock || echo "No api-gateway-mock container currently running."

config:: # Configure development environment (main) @Configuration
# Configure poetry to trust dev certificate if specified
@if [[ -n "$${DEV_CERTS_INCLUDED}" ]]; then \
Expand Down
28 changes: 0 additions & 28 deletions infrastructure/environments/preview/handler.py

This file was deleted.

12 changes: 12 additions & 0 deletions infrastructure/images/api-gateway-mock/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Retrieve the python version from build arguments, deliberately set to "invalid" by default to highlight when no version is provided when building the container.
ARG PYTHON_VERSION=invalid
# Use the specified python version to retrieve the required base lambda image.
ARG url=python:${PYTHON_VERSION}-alpine3.23
FROM $url

COPY resources/ /resources
WORKDIR /resources

RUN pip install --no-cache-dir -r requirements.txt

ENTRYPOINT ["flask", "--app", "server", "run", "--host=0.0.0.0"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
flask==3.1.2
requests==2.32.5
72 changes: 72 additions & 0 deletions infrastructure/images/api-gateway-mock/resources/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from logging.config import dictConfig

import requests
from flask import Flask, request

# Very simple logging configuration taken from https://flask.palletsprojects.com/en/stable/logging/
dictConfig(
{
"version": 1,
"formatters": {
"default": {
"format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s",
},
},
"handlers": {
"wsgi": {
"class": "logging.StreamHandler",
"stream": "ext://sys.stdout",
"formatter": "default",
}
},
"root": {"level": "INFO", "handlers": ["wsgi"]},
}
)

app = Flask(__name__)


@app.route("/", methods=["POST", "GET"], defaults={"path_params": None})
@app.route("/<path:path_params>", methods=["POST", "GET"])
def forward_request(path_params):
app.logger.info("received request with data: %s", request.get_data(as_text=True))

response = requests.post(
"http://pathology-api:8080/2015-03-31/functions/function/invocations",
json={
"body": request.get_data(as_text=True).replace("\n", "").replace(" ", ""),
"requestContext": {
"http": {
"path": f"/{path_params}",
"method": request.method,
},
"requestId": "request-id",
"stage": "$default",
},
"httpMethod": request.method,
"rawPath": f"/{path_params}",
"rawQueryString": "",
"pathParameters": {"proxy": path_params},
},
headers={"Content-Type": "application/json"},
timeout=120,
)

app.logger.info(
"response: status_code=%s, body=%s", response.status_code, response.text
)

app.logger.info("response: %s", response.text)
response_data = response.json()

output = (
(
response_data["body"],
response_data["statusCode"],
response_data["headers"],
)
if "body" in response_data
else (response_data, 500, {"Content-Type": "text/plain"})
)

return output
Loading
Loading