Log GITHUB_SHA on /health; move zip-size gate to its own PR workflow

/health already returned GITHUB_SHA in its response but never logged
it, and the fastapi lambda's Terraform environment never actually set
GITHUB_SHA — so every health check response contained "unknown" in
production. Wire var.github_sha through the fastapi lambda module
(default "unknown" for local/other invocations) via a new
TF_VAR_github_sha env var set from `github.sha` in
_deploy_lambda.yml's Terraform Plan step, and log it on every /health
call so a request in CloudWatch can be tied back to the deploy that
served it.

Also fix the zip-size gate added for PR #1469: putting it in
tests/test_lambda_zip_size.py (run via the Docker-based ddd_tests.yml
suite) broke CI, because Dockerfile.test's build context excludes
deployment/* (.dockerignore), so check_lambda_zip_size.py couldn't
find variables.tf to read zip_excludes from inside that container.
Move the check to its own lightweight workflow,
check_lambda_zip_size.yml, triggered on pull_request into main — a
plain checkout (no Docker build) has the full repo, so the check
works, runs fast, and still gates merges to main before a regression
can roll into the dev deploy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jun-te Kim 2026-07-06 13:43:36 +00:00
parent e816972c45
commit adb1bf4b56
6 changed files with 40 additions and 49 deletions

View file

@ -148,6 +148,7 @@ jobs:
- name: Terraform Plan
working-directory: ${{ inputs.lambda_path }}
env:
TF_VAR_github_sha: ${{ github.sha }}
TF_VAR_db_host: ${{ secrets.TF_VAR_db_host }}
TF_VAR_db_name: ${{ secrets.TF_VAR_db_name }}
TF_VAR_db_port: ${{ secrets.TF_VAR_db_port }}

View file

@ -0,0 +1,23 @@
name: Check FastAPI Lambda package size
# Runs on PRs targeting main so a Lambda-size regression fails the PR before
# it can merge to main and roll into the dev deploy — deploy_terraform.yml
# only triggers on push to dev/prod, which is too late (see PR #1469: this
# broke dev for weeks before anyone noticed).
on:
pull_request:
branches:
- main
jobs:
fast_api_lambda_zip_size_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Check projected unzipped Lambda size
run: python3 backend/app/requirements/check_lambda_zip_size.py

View file

@ -53,7 +53,9 @@ async def log_requests(request: Request, call_next):
@app.get("/health")
async def health():
return {"status": "ok", "sha": os.getenv("GITHUB_SHA", "unknown")}
sha = os.getenv("GITHUB_SHA", "unknown")
logger.info(f"Health check OK - deployed sha={sha}")
return {"status": "ok", "sha": sha}
app.include_router(tasks_router.router, prefix="/v1")

View file

@ -86,6 +86,7 @@ module "fastapi" {
environment = {
ENVIRONMENT = var.stage
GITHUB_SHA = var.github_sha
API_KEY = var.api_key
SECRET_KEY = var.secret_key
# DOMAIN_NAME = var.domain_name
@ -110,11 +111,11 @@ module "fastapi" {
CARBON_BASELINE_PREDICTIONS_BUCKET = data.terraform_remote_state.shared.outputs.retrofit_carbon_baseline_predictions_bucket_name
HEAT_BASELINE_PREDICTIONS_BUCKET = data.terraform_remote_state.shared.outputs.retrofit_heat_baseline_predictions_bucket_name
ENGINE_SQS_URL = data.terraform_remote_state.engine.outputs.ara_engine_queue_url
CATEGORISATION_SQS_URL = data.terraform_remote_state.categorisation.outputs.categorisation_queue_url
POSTCODE_SPLITTER_SQS_URL = data.terraform_remote_state.postcode_splitter.outputs.postcode_splitter_queue_url
COMBINER_SQS_URL = data.terraform_remote_state.bulk_address2uprn_combiner.outputs.bulk_address2uprn_combiner_queue_url
FINALISER_SQS_URL = data.terraform_remote_state.bulk_upload_finaliser.outputs.bulk_upload_finaliser_queue_url
ENGINE_SQS_URL = data.terraform_remote_state.engine.outputs.ara_engine_queue_url
CATEGORISATION_SQS_URL = data.terraform_remote_state.categorisation.outputs.categorisation_queue_url
POSTCODE_SPLITTER_SQS_URL = data.terraform_remote_state.postcode_splitter.outputs.postcode_splitter_queue_url
COMBINER_SQS_URL = data.terraform_remote_state.bulk_address2uprn_combiner.outputs.bulk_address2uprn_combiner_queue_url
FINALISER_SQS_URL = data.terraform_remote_state.bulk_upload_finaliser.outputs.bulk_upload_finaliser_queue_url
}
}

View file

@ -41,4 +41,10 @@ variable "epc_auth_token" {
variable "google_solar_api_key" {
type = string
sensitive = true
}
}
variable "github_sha" {
type = string
description = "Commit SHA being deployed, surfaced via /health so cloud logs can be tied back to a deploy."
default = "unknown"
}

View file

@ -1,42 +0,0 @@
"""Guard against the fastapi Lambda's unzipped package exceeding AWS's
262144000 byte (250 MiB) limit.
dev deployed with `InvalidParameterValueException: Unzipped size must be
smaller than 262144000 bytes` for several weeks (2026-06-15 to 2026-07-03)
before anyone noticed, because the failure only ever surfaced deep inside
`terraform apply` on a push to dev/prod well after a PR to main had merged.
This runs the same check as part of the PR-gated test suite so a regression
(e.g. a large fixture or dependency landing outside the Lambda's zip_excludes)
fails the PR instead of the dev deploy.
Runs the real `check_lambda_zip_size.py` script as a subprocess (not an
in-process import) so this exercises exactly what a human or CI would run
locally: `python3 backend/app/requirements/check_lambda_zip_size.py`.
"""
from __future__ import annotations
import subprocess
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
CHECK_SCRIPT = REPO_ROOT / "backend/app/requirements/check_lambda_zip_size.py"
def test_fastapi_lambda_unzipped_size_within_aws_limit() -> None:
result = subprocess.run(
[sys.executable, str(CHECK_SCRIPT)],
cwd=REPO_ROOT,
capture_output=True,
text=True,
)
print(result.stdout)
assert result.returncode == 0, (
"fastapi Lambda's projected unzipped size exceeds (or is dangerously "
"close to) AWS's 250 MiB limit — see output above. If this is a real "
"new dependency/fixture, either trim it or extend `zip_excludes` in "
"deployment/terraform/modules/lambda_with_api_gateway/variables.tf "
"(only if it's not needed by the Lambda at runtime).\n"
+ result.stdout
+ result.stderr
)