From 2973a4f2f16fc255b50cfa002ae0273814c1ad19 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 9 Mar 2026 09:39:25 +0000 Subject: [PATCH 01/77] ensure bucket root is included in s3 iam policy rules --- infrastructure/terraform/modules/s3_iam_policy/main.tf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infrastructure/terraform/modules/s3_iam_policy/main.tf b/infrastructure/terraform/modules/s3_iam_policy/main.tf index 397bd963..bb317302 100644 --- a/infrastructure/terraform/modules/s3_iam_policy/main.tf +++ b/infrastructure/terraform/modules/s3_iam_policy/main.tf @@ -1,10 +1,10 @@ # Dynamically build S3 resources list from bucket ARNs and resource paths locals { - # Generate full resource ARNs by combining bucket ARNs with resource paths resources = flatten([ - for bucket_arn in var.bucket_arns : [ - for path in var.resource_paths : "${bucket_arn}${path}" - ] + for bucket_arn in var.bucket_arns : concat( + [bucket_arn], # bare ARN for bucket-level actions like ListBucket + [for path in var.resource_paths : "${bucket_arn}${path}"] + ) ]) } From 4d2305fdd28c2937bed22843a31e33761218791e Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 9 Mar 2026 09:40:35 +0000 Subject: [PATCH 02/77] add comment back in --- infrastructure/terraform/modules/s3_iam_policy/main.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/infrastructure/terraform/modules/s3_iam_policy/main.tf b/infrastructure/terraform/modules/s3_iam_policy/main.tf index bb317302..0ef5c4be 100644 --- a/infrastructure/terraform/modules/s3_iam_policy/main.tf +++ b/infrastructure/terraform/modules/s3_iam_policy/main.tf @@ -1,5 +1,6 @@ # Dynamically build S3 resources list from bucket ARNs and resource paths locals { + # Generate full resource ARNs by combining bucket ARNs with resource paths resources = flatten([ for bucket_arn in var.bucket_arns : concat( [bucket_arn], # bare ARN for bucket-level actions like ListBucket From 71c0e6b2f3fe8a445b3c3e2c08860d2f62011b5b Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 9 Mar 2026 13:39:09 +0000 Subject: [PATCH 03/77] move lambda_with_sqs to terraform/modules --- infrastructure/terraform/lambda/_template/main.tf | 2 +- .../terraform/{lambda => }/modules/lambda_with_sqs/main.tf | 0 .../terraform/{lambda => }/modules/lambda_with_sqs/outputs.tf | 0 .../terraform/{lambda => }/modules/lambda_with_sqs/variables.tf | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename infrastructure/terraform/{lambda => }/modules/lambda_with_sqs/main.tf (100%) rename infrastructure/terraform/{lambda => }/modules/lambda_with_sqs/outputs.tf (100%) rename infrastructure/terraform/{lambda => }/modules/lambda_with_sqs/variables.tf (100%) diff --git a/infrastructure/terraform/lambda/_template/main.tf b/infrastructure/terraform/lambda/_template/main.tf index c6015ea1..81b1c7f1 100644 --- a/infrastructure/terraform/lambda/_template/main.tf +++ b/infrastructure/terraform/lambda/_template/main.tf @@ -26,7 +26,7 @@ data "terraform_remote_state" "shared" { } module "lambda" { - source = "../modules/lambda_with_sqs" + source = "../../modules/lambda_with_sqs" name = REPLACE ME #"address2uprn" for example stage = var.stage diff --git a/infrastructure/terraform/lambda/modules/lambda_with_sqs/main.tf b/infrastructure/terraform/modules/lambda_with_sqs/main.tf similarity index 100% rename from infrastructure/terraform/lambda/modules/lambda_with_sqs/main.tf rename to infrastructure/terraform/modules/lambda_with_sqs/main.tf diff --git a/infrastructure/terraform/lambda/modules/lambda_with_sqs/outputs.tf b/infrastructure/terraform/modules/lambda_with_sqs/outputs.tf similarity index 100% rename from infrastructure/terraform/lambda/modules/lambda_with_sqs/outputs.tf rename to infrastructure/terraform/modules/lambda_with_sqs/outputs.tf diff --git a/infrastructure/terraform/lambda/modules/lambda_with_sqs/variables.tf b/infrastructure/terraform/modules/lambda_with_sqs/variables.tf similarity index 100% rename from infrastructure/terraform/lambda/modules/lambda_with_sqs/variables.tf rename to infrastructure/terraform/modules/lambda_with_sqs/variables.tf From c8d9c58380fa852ff679de8fd63130988eacb717 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 9 Mar 2026 13:39:36 +0000 Subject: [PATCH 04/77] udpate references to lambda_with_sqs --- infrastructure/terraform/lambda/address2UPRN/main.tf | 2 +- infrastructure/terraform/lambda/categorisation/main.tf | 2 +- infrastructure/terraform/lambda/condition-etl/main.tf | 2 +- infrastructure/terraform/lambda/engine/main.tf | 2 +- infrastructure/terraform/lambda/postcodeSplitter/main.tf | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/infrastructure/terraform/lambda/address2UPRN/main.tf b/infrastructure/terraform/lambda/address2UPRN/main.tf index 2d185497..f7750cb3 100644 --- a/infrastructure/terraform/lambda/address2UPRN/main.tf +++ b/infrastructure/terraform/lambda/address2UPRN/main.tf @@ -15,7 +15,7 @@ locals { } module "address2uprn" { - source = "../modules/lambda_with_sqs" + source = "../../modules/lambda_with_sqs" name = "address2uprn" stage = var.stage diff --git a/infrastructure/terraform/lambda/categorisation/main.tf b/infrastructure/terraform/lambda/categorisation/main.tf index b7193da4..01e16213 100644 --- a/infrastructure/terraform/lambda/categorisation/main.tf +++ b/infrastructure/terraform/lambda/categorisation/main.tf @@ -16,7 +16,7 @@ locals { } module "lambda" { - source = "../modules/lambda_with_sqs" + source = "../../modules/lambda_with_sqs" name = "categorisation" stage = var.stage diff --git a/infrastructure/terraform/lambda/condition-etl/main.tf b/infrastructure/terraform/lambda/condition-etl/main.tf index 0128f975..d654223c 100644 --- a/infrastructure/terraform/lambda/condition-etl/main.tf +++ b/infrastructure/terraform/lambda/condition-etl/main.tf @@ -17,7 +17,7 @@ locals { module "lambda" { - source = "../modules/lambda_with_sqs" + source = "../../modules/lambda_with_sqs" name = "condition-etl" stage = var.stage diff --git a/infrastructure/terraform/lambda/engine/main.tf b/infrastructure/terraform/lambda/engine/main.tf index 35c00fa3..3f28933b 100644 --- a/infrastructure/terraform/lambda/engine/main.tf +++ b/infrastructure/terraform/lambda/engine/main.tf @@ -17,7 +17,7 @@ locals { module "lambda" { - source = "../modules/lambda_with_sqs" + source = "../../modules/lambda_with_sqs" name = "engine" stage = var.stage diff --git a/infrastructure/terraform/lambda/postcodeSplitter/main.tf b/infrastructure/terraform/lambda/postcodeSplitter/main.tf index d37a01c9..94c5cd4e 100644 --- a/infrastructure/terraform/lambda/postcodeSplitter/main.tf +++ b/infrastructure/terraform/lambda/postcodeSplitter/main.tf @@ -26,7 +26,7 @@ data "terraform_remote_state" "address2uprn" { } module "lambda" { - source = "../modules/lambda_with_sqs" + source = "../../modules/lambda_with_sqs" name = "postcode-splitter" stage = var.stage From 51be55a70635c4403e706ea43be86d864d91b709 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 9 Mar 2026 14:00:30 +0000 Subject: [PATCH 05/77] create fastapi files and add ecr to shared tf --- .../terraform/lambda/fast-api/main.tf | 49 +++++++++++++++++++ .../terraform/lambda/fast-api/provider.tf | 16 ++++++ .../terraform/lambda/fast-api/variables.tf | 37 ++++++++++++++ infrastructure/terraform/shared/main.tf | 14 ++++++ 4 files changed, 116 insertions(+) create mode 100644 infrastructure/terraform/lambda/fast-api/main.tf create mode 100644 infrastructure/terraform/lambda/fast-api/provider.tf create mode 100644 infrastructure/terraform/lambda/fast-api/variables.tf diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf new file mode 100644 index 00000000..104d4a4d --- /dev/null +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -0,0 +1,49 @@ +data "terraform_remote_state" "shared" { + backend = "s3" + config = { + bucket = "assessment-model-terraform-state" + key = "env:/${var.stage}/terraform.tfstate" + region = "eu-west-2" + } +} + +module "lambda" { + source = "../../modules/lambda_with_sqs" + + name = REPLACE ME #"address2uprn" for example + stage = var.stage + + image_uri = local.image_uri + + # Optional: Set maximum_concurrency to limit concurrent SQS-triggered invocations (2-1000) + maximum_concurrency = var.maximum_concurrency + + batch_size = var.batch_size + + environment = { + STAGE = var.stage + LOG_LEVEL = "info" + } +} + +# ====================================================================== +# OPTIONAL: Attach S3 IAM policy to Lambda execution role +# ====================================================================== +# Uncomment and configure the resource below to attach S3 permissions +# +# Example 1: Attach existing policy from shared state +# resource "aws_iam_role_policy_attachment" "lambda_s3_policy" { +# role = module.lambda.role_name +# policy_arn = data.terraform_remote_state.shared.outputs.YOUR_POLICY_OUTPUT_NAME_arn +# } +# +# Example 2: Attach multiple policies +# resource "aws_iam_role_policy_attachment" "lambda_read_policy" { +# role = module.lambda.role_name +# policy_arn = data.terraform_remote_state.shared.outputs.postcode_splitter_s3_read_arn +# } +# +# resource "aws_iam_role_policy_attachment" "lambda_write_policy" { +# role = module.lambda.role_name +# policy_arn = data.terraform_remote_state.shared.outputs.another_policy_arn +# } diff --git a/infrastructure/terraform/lambda/fast-api/provider.tf b/infrastructure/terraform/lambda/fast-api/provider.tf new file mode 100644 index 00000000..37c412ce --- /dev/null +++ b/infrastructure/terraform/lambda/fast-api/provider.tf @@ -0,0 +1,16 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.16" + } + } + + backend "s3" { + bucket = REPLACE_ME + key = "terraform.tfstate" + region = "eu-west-2" + } + + required_version = ">= 1.2.0" +} \ No newline at end of file diff --git a/infrastructure/terraform/lambda/fast-api/variables.tf b/infrastructure/terraform/lambda/fast-api/variables.tf new file mode 100644 index 00000000..e7646811 --- /dev/null +++ b/infrastructure/terraform/lambda/fast-api/variables.tf @@ -0,0 +1,37 @@ +variable "lambda_name" { + type = string + description = "Logical name of the lambda (e.g. address2uprn)" +} + +variable "stage" { + description = "Deployment stage (e.g. dev, prod)" + type = string +} +variable "ecr_repo_url" { + type = string + description = "ECR repository URL (no tag, no digest)" +} + +variable "image_digest" { + type = string + description = "Image digest (sha256:...)" +} + +variable "maximum_concurrency" { + type = number + default = null + description = "Maximum number of concurrent Lambda invocations from SQS (2-1000). null = no limit." +} + +variable "batch_size" { + type = number + default = 1 +} + +locals { + image_uri = "${var.ecr_repo_url}@${var.image_digest}" +} + +output "resolved_image_uri" { + value = local.image_uri +} diff --git a/infrastructure/terraform/shared/main.tf b/infrastructure/terraform/shared/main.tf index c19e3a0c..15198a34 100644 --- a/infrastructure/terraform/shared/main.tf +++ b/infrastructure/terraform/shared/main.tf @@ -489,3 +489,17 @@ module "engine_s3_read_and_write" { output "engine_s3_read_and_write_arn" { value = module.engine_s3_read_and_write.policy_arn } + +################################################ +# FastAPI – Lambda +################################################ +module "ara_fast_api_state_bucket" { + source = "../modules/tf_state_bucket" + bucket_name = "ara-fast-api-terraform-state" +} + +module "ara_fastapi_registry" { + source = "../modules/container_registry" + name = "engine" + stage = var.stage +} From 255d7e5dbf8a3d1df860b51f9cda8101db1f6c93 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 9 Mar 2026 14:12:41 +0000 Subject: [PATCH 06/77] correct ecr name --- infrastructure/terraform/shared/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/terraform/shared/main.tf b/infrastructure/terraform/shared/main.tf index 15198a34..0cac8d66 100644 --- a/infrastructure/terraform/shared/main.tf +++ b/infrastructure/terraform/shared/main.tf @@ -500,6 +500,6 @@ module "ara_fast_api_state_bucket" { module "ara_fastapi_registry" { source = "../modules/container_registry" - name = "engine" + name = "ara_fastapi" stage = var.stage } From cf3c4ea7a8b577abdb53e0aa497bd5c6a68b9f89 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 9 Mar 2026 14:14:04 +0000 Subject: [PATCH 07/77] correct ecr name --- infrastructure/terraform/shared/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/terraform/shared/main.tf b/infrastructure/terraform/shared/main.tf index 0cac8d66..05a3665e 100644 --- a/infrastructure/terraform/shared/main.tf +++ b/infrastructure/terraform/shared/main.tf @@ -500,6 +500,6 @@ module "ara_fast_api_state_bucket" { module "ara_fastapi_registry" { source = "../modules/container_registry" - name = "ara_fastapi" + name = "ara-fastapi" stage = var.stage } From 63cb200e95f8ff2c7cf6083db0132b516b6d62a2 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 9 Mar 2026 14:42:51 +0000 Subject: [PATCH 08/77] correct relative paths in moved module --- infrastructure/terraform/modules/lambda_with_sqs/main.tf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infrastructure/terraform/modules/lambda_with_sqs/main.tf b/infrastructure/terraform/modules/lambda_with_sqs/main.tf index 74345d24..fe8c615b 100644 --- a/infrastructure/terraform/modules/lambda_with_sqs/main.tf +++ b/infrastructure/terraform/modules/lambda_with_sqs/main.tf @@ -2,7 +2,7 @@ # IAM role ############################################ module "role" { - source = "../../../modules/lambda_execution_role" + source = "../modules/lambda_execution_role" name = "${var.name}-lambda-${var.stage}" } @@ -14,7 +14,7 @@ output "role_name" { # SQS queue + DLQ ############################################ module "queue" { - source = "../../../modules/sqs_queue" + source = "../modules/sqs_queue" name = "${var.name}-queue-${var.stage}" } @@ -22,7 +22,7 @@ module "queue" { # Lambda ############################################ module "lambda" { - source = "../../../modules/lambda_service" + source = "../modules/lambda_service" name = "${var.name}-${var.stage}" role_arn = module.role.role_arn @@ -38,7 +38,7 @@ module "lambda" { # SQS → Lambda trigger ############################################ module "sqs_trigger" { - source = "../../../modules/lambda_sqs_trigger" + source = "../modules/lambda_sqs_trigger" lambda_arn = module.lambda.lambda_arn lambda_role_name = module.role.role_name From 7a2587fc9b6a6886a021ec0c83900bacdc563c8f Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 9 Mar 2026 14:51:53 +0000 Subject: [PATCH 09/77] correct relative paths in moved module --- infrastructure/terraform/modules/lambda_with_sqs/main.tf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/infrastructure/terraform/modules/lambda_with_sqs/main.tf b/infrastructure/terraform/modules/lambda_with_sqs/main.tf index fe8c615b..35626487 100644 --- a/infrastructure/terraform/modules/lambda_with_sqs/main.tf +++ b/infrastructure/terraform/modules/lambda_with_sqs/main.tf @@ -2,7 +2,7 @@ # IAM role ############################################ module "role" { - source = "../modules/lambda_execution_role" + source = "../lambda_execution_role" name = "${var.name}-lambda-${var.stage}" } @@ -14,7 +14,7 @@ output "role_name" { # SQS queue + DLQ ############################################ module "queue" { - source = "../modules/sqs_queue" + source = "../sqs_queue" name = "${var.name}-queue-${var.stage}" } @@ -22,7 +22,7 @@ module "queue" { # Lambda ############################################ module "lambda" { - source = "../modules/lambda_service" + source = "../lambda_service" name = "${var.name}-${var.stage}" role_arn = module.role.role_arn @@ -38,7 +38,7 @@ module "lambda" { # SQS → Lambda trigger ############################################ module "sqs_trigger" { - source = "../modules/lambda_sqs_trigger" + source = "../lambda_sqs_trigger" lambda_arn = module.lambda.lambda_arn lambda_role_name = module.role.role_name From 54255942f93acaf71521cf9d7d6c4e9baf2459fb Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 10 Mar 2026 08:51:08 +0000 Subject: [PATCH 10/77] add lambda_service_zip and lambda_with_api_gateway terraform modules --- .github/workflows/deploy_terraform.yml | 14 +++++------ .../modules/lambda_service_zip/main.tf | 24 +++++++++++++++++++ .../modules/lambda_with_api_gateway/main.tf | 0 3 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 infrastructure/terraform/modules/lambda_service_zip/main.tf create mode 100644 infrastructure/terraform/modules/lambda_with_api_gateway/main.tf diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index bde7eb21..042bce35 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -41,7 +41,7 @@ jobs: fi # ============================================================ - # 1️⃣ Shared Terraform (infra) + # Shared Terraform (infra) # ============================================================ shared_terraform: needs: determine_stage @@ -79,7 +79,7 @@ jobs: run: terraform apply -auto-approve tfplan # ============================================================ - # 2️⃣ Build Address 2 UPRN image and Push + # Build Address 2 UPRN image and Push # ============================================================ address2uprn_image: needs: [determine_stage, shared_terraform] @@ -103,7 +103,7 @@ jobs: EPC_AUTH_TOKEN: ${{ secrets.DEV_EPC_AUTH_TOKEN }} # ============================================================ - # 3️⃣ Deploy Address 2 UPRN Lambda + # Deploy Address 2 UPRN Lambda # ============================================================ address2uprn_lambda: needs: [address2uprn_image, determine_stage] @@ -122,7 +122,7 @@ jobs: # ============================================================ - # 2️⃣ Build Postcode Splitter image and Push + # Build Postcode Splitter image and Push # ============================================================ postcodeSplitter_image: needs: [determine_stage, shared_terraform] @@ -144,7 +144,7 @@ jobs: DEV_DB_NAME: ${{ secrets.DEV_DB_NAME }} # ============================================================ - # 3️⃣ Deploy Postcode Splitter Lambda + # Deploy Postcode Splitter Lambda # ============================================================ postcodeSplitter_lambda: needs: [postcodeSplitter_image, determine_stage, address2uprn_lambda] @@ -283,7 +283,7 @@ jobs: TF_VAR_google_solar_api_key: ${{ secrets.DEV_GOOGLE_SOLAR_API_KEY }} # ============================================================ - # 2️⃣ Build OrdanceSurvey image and Push + # Build OrdanceSurvey image and Push # ============================================================ ordnanceSurvey_image: needs: [determine_stage, shared_terraform] @@ -305,7 +305,7 @@ jobs: DEV_DB_NAME: ${{ secrets.DEV_DB_NAME }} # ============================================================ - # 3️⃣ Deploy OrdanceSurvey Lambda + # Deploy OrdanceSurvey Lambda # ============================================================ ordnanceSurvey_lambda: needs: [ordnanceSurvey_image, determine_stage] diff --git a/infrastructure/terraform/modules/lambda_service_zip/main.tf b/infrastructure/terraform/modules/lambda_service_zip/main.tf new file mode 100644 index 00000000..285aa9d4 --- /dev/null +++ b/infrastructure/terraform/modules/lambda_service_zip/main.tf @@ -0,0 +1,24 @@ +resource "aws_lambda_function" "this" { + function_name = var.name + role = var.role_arn + package_type = "Zip" + filename = var.filename + source_code_hash = var.source_code_hash + handler = var.handler + runtime = var.runtime + timeout = var.timeout + memory_size = var.memory_size + publish = true + + environment { + variables = var.environment + } +} + +output "lambda_arn" { + value = aws_lambda_function.this.arn +} + +output "function_name" { + value = aws_lambda_function.this.function_name +} \ No newline at end of file diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf new file mode 100644 index 00000000..e69de29b From db50f8613f5223d11b0a40ff20f73fc0a158e23a Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 10 Mar 2026 08:58:40 +0000 Subject: [PATCH 11/77] make ecr_repo_url and image_digest optional in _deploy_lambda.yml --- .github/workflows/_deploy_lambda.yml | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/_deploy_lambda.yml b/.github/workflows/_deploy_lambda.yml index ce1a0e77..7e11cf32 100644 --- a/.github/workflows/_deploy_lambda.yml +++ b/.github/workflows/_deploy_lambda.yml @@ -16,12 +16,14 @@ on: type: string ecr_repo: - required: true + required: false type: string + default: '' image_digest: - required: true + required: false type: string + default: '' terraform_apply: required: false @@ -116,11 +118,15 @@ jobs: TF_VAR_epc_auth_token: ${{ secrets.TF_VAR_epc_auth_token }} TF_VAR_google_solar_api_key: ${{ secrets.TF_VAR_google_solar_api_key }} run: | + EXTRA_VARS="" + if [[ -n "${{ inputs.ecr_repo }}" ]]; then + EXTRA_VARS="-var=ecr_repo_url=${{ steps.repo.outputs.ecr_repo_url }} -var=image_digest=${{ inputs.image_digest }}" + fi + terraform plan \ -var="stage=${{ inputs.stage }}" \ -var="lambda_name=${{ inputs.lambda_name }}" \ - -var="ecr_repo_url=${{ steps.repo.outputs.ecr_repo_url }}" \ - -var="image_digest=${{ inputs.image_digest }}" \ + $EXTRA_VARS \ -out=lambdaplan - name: Terraform Apply @@ -141,8 +147,12 @@ jobs: TF_VAR_epc_auth_token: ${{ secrets.TF_VAR_epc_auth_token }} TF_VAR_google_solar_api_key: ${{ secrets.TF_VAR_google_solar_api_key }} run: | + EXTRA_VARS="" + if [[ -n "${{ inputs.ecr_repo }}" ]]; then + EXTRA_VARS="-var=ecr_repo_url=${{ steps.repo.outputs.ecr_repo_url }} -var=image_digest=${{ inputs.image_digest }}" + fi + terraform destroy -auto-approve \ -var="stage=${{ inputs.stage }}" \ -var="lambda_name=${{ inputs.lambda_name }}" \ - -var="ecr_repo_url=${{ steps.repo.outputs.ecr_repo_url }}" \ - -var="image_digest=${{ inputs.image_digest }}" + $EXTRA_VARS From 3423ca76fb4adb137eb57825ab66112731c28f9f Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 10 Mar 2026 10:00:08 +0000 Subject: [PATCH 12/77] define terraform for lambda_with_api_gateway and lambda_service_zip --- .../modules/lambda_service_zip/variables.tf | 9 ++ .../modules/lambda_with_api_gateway/main.tf | 103 ++++++++++++++++++ .../lambda_with_api_gateway/outputs.tf | 11 ++ .../lambda_with_api_gateway/variables.tf | 18 +++ 4 files changed, 141 insertions(+) create mode 100644 infrastructure/terraform/modules/lambda_service_zip/variables.tf create mode 100644 infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf create mode 100644 infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf diff --git a/infrastructure/terraform/modules/lambda_service_zip/variables.tf b/infrastructure/terraform/modules/lambda_service_zip/variables.tf new file mode 100644 index 00000000..3a243c49 --- /dev/null +++ b/infrastructure/terraform/modules/lambda_service_zip/variables.tf @@ -0,0 +1,9 @@ +variable "name" { type = string } +variable "role_arn" { type = string } +variable "filename" { type = string } +variable "source_code_hash" { type = string } +variable "handler" { type = string } +variable "runtime" { type = string } +variable "timeout" { type = number default = 30 } +variable "memory_size" { type = number default = 128 } +variable "environment" { type = map(string) default = {} } \ No newline at end of file diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf index e69de29b..23ccc4b1 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf @@ -0,0 +1,103 @@ +############################################ +# IAM role +############################################ +module "role" { + source = "../lambda_execution_role" + name = "${var.name}-lambda-${var.stage}" +} + +############################################ +# Zip the source code +############################################ +data "archive_file" "this" { + type = "zip" + source_dir = var.source_dir + output_path = "${path.module}/lambda_package.zip" + excludes = var.zip_excludes +} + +############################################ +# Lambda +############################################ +module "lambda" { + source = "../lambda_service_zip" + + name = "${var.name}-${var.stage}" + role_arn = module.role.role_arn + filename = data.archive_file.this.output_path + source_code_hash = data.archive_file.this.output_base64sha256 + handler = var.handler + runtime = var.runtime + timeout = var.timeout + memory_size = var.memory_size + environment = var.environment +} + +############################################ +# API Gateway +############################################ +resource "aws_apigatewayv2_api" "this" { + name = "${var.name}-api-${var.stage}" + protocol_type = "HTTP" +} + +resource "aws_apigatewayv2_stage" "this" { + api_id = aws_apigatewayv2_api.this.id + name = "$default" + auto_deploy = true +} + +resource "aws_apigatewayv2_integration" "this" { + api_id = aws_apigatewayv2_api.this.id + integration_type = "AWS_PROXY" + integration_uri = module.lambda.lambda_arn + payload_format_version = "2.0" +} + +resource "aws_apigatewayv2_route" "catch_all" { + api_id = aws_apigatewayv2_api.this.id + route_key = "$default" + target = "integrations/${aws_apigatewayv2_integration.this.id}" +} + +resource "aws_lambda_permission" "apigw_invoke" { + statement_id = "AllowAPIGatewayInvoke" + action = "lambda:InvokeFunction" + function_name = module.lambda.lambda_arn + principal = "apigateway.amazonaws.com" + source_arn = "${aws_apigatewayv2_api.this.execution_arn}/*/*" +} + +############################################ +# Custom domain +############################################ +resource "aws_apigatewayv2_domain_name" "this" { + count = var.domain_name != null ? 1 : 0 + domain_name = var.domain_name + + domain_name_configuration { + certificate_arn = var.certificate_arn + endpoint_type = "REGIONAL" + security_policy = "TLS_1_2" + } +} + +resource "aws_apigatewayv2_api_mapping" "this" { + count = var.domain_name != null ? 1 : 0 + api_id = aws_apigatewayv2_api.this.id + domain_name = aws_apigatewayv2_domain_name.this[0].id + stage = aws_apigatewayv2_stage.this.id +} + +resource "aws_route53_record" "this" { + count = var.domain_name != null ? 1 : 0 + name = aws_apigatewayv2_domain_name.this[0].domain_name + type = "A" + zone_id = var.route53_zone_id + + alias { + name = aws_apigatewayv2_domain_name.this[0].domain_name_configuration[0].target_domain_name + zone_id = aws_apigatewayv2_domain_name.this[0].domain_name_configuration[0].hosted_zone_id + evaluate_target_health = false + } +} \ No newline at end of file diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf new file mode 100644 index 00000000..52db1ff9 --- /dev/null +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf @@ -0,0 +1,11 @@ +output "role_name" { + value = module.role.role_name +} + +output "api_endpoint" { + value = aws_apigatewayv2_stage.this.invoke_url +} + +output "custom_domain_endpoint" { + value = var.domain_name != null ? "https://${var.domain_name}" : null +} \ No newline at end of file diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf new file mode 100644 index 00000000..1a08ff2e --- /dev/null +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf @@ -0,0 +1,18 @@ +variable "name" { type = string } +variable "stage" { type = string } +variable "source_dir" { type = string } +variable "handler" { type = string } +variable "runtime" { type = string } + +variable "zip_excludes" { + type = list(string) + default = ["**/__pycache__/**", "**/*.pyc", "**/.pytest_cache/**"] +} + +variable "timeout" { type = number default = 600 } +variable "memory_size" { type = number default = 512 } +variable "environment" { type = map(string) default = {} } + +variable "domain_name" { type = string default = null } +variable "certificate_arn" { type = string default = null } +variable "route53_zone_id" { type = string default = null } \ No newline at end of file From dcb3efdb7ed97fdd2818cccd8986551f5d88ccbd Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 10 Mar 2026 11:42:06 +0000 Subject: [PATCH 13/77] update fast-api terraform --- .../terraform/lambda/fast-api/main.tf | 122 +++++++++++++----- .../terraform/lambda/fast-api/provider.tf | 2 +- .../terraform/lambda/fast-api/variables.tf | 47 ++++--- 3 files changed, 118 insertions(+), 53 deletions(-) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index 104d4a4d..aabc1c52 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -7,43 +7,101 @@ data "terraform_remote_state" "shared" { } } -module "lambda" { - source = "../../modules/lambda_with_sqs" +data "aws_secretsmanager_secret_version" "db_credentials" { + secret_id = "${var.stage}/assessment_model/db_credentials" +} - name = REPLACE ME #"address2uprn" for example - stage = var.stage +locals { + db_credentials = jsondecode(data.aws_secretsmanager_secret_version.db_credentials.secret_string) +} - image_uri = local.image_uri - # Optional: Set maximum_concurrency to limit concurrent SQS-triggered invocations (2-1000) - maximum_concurrency = var.maximum_concurrency +data "aws_ssm_parameter" "certificate_arn" { + name = "/ssl_certificate_arn" +} - batch_size = var.batch_size +data "aws_route53_zone" "this" { + name = var.domain_name +} - environment = { - STAGE = var.stage - LOG_LEVEL = "info" +############################################ +# Install Python requirements +############################################ +resource "null_resource" "pip_install" { + triggers = { + requirements_hash = filemd5("${path.root}/../../../../backend/app/requirements/requirements.txt") + } + + provisioner "local-exec" { + command = < Date: Tue, 10 Mar 2026 12:04:53 +0000 Subject: [PATCH 14/77] read queue names from terraform state --- .../lambda/categorisation/outputs.tf | 9 ++++++++ .../terraform/lambda/engine/outputs.tf | 9 ++++++++ .../terraform/lambda/fast-api/main.tf | 22 +++++++++++++++++-- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 infrastructure/terraform/lambda/categorisation/outputs.tf create mode 100644 infrastructure/terraform/lambda/engine/outputs.tf diff --git a/infrastructure/terraform/lambda/categorisation/outputs.tf b/infrastructure/terraform/lambda/categorisation/outputs.tf new file mode 100644 index 00000000..be1ac118 --- /dev/null +++ b/infrastructure/terraform/lambda/categorisation/outputs.tf @@ -0,0 +1,9 @@ +output "categorisation_queue_url" { + value = module.lambda.queu_url + description = "URL of the Categorisation SQS queue" +} + +output "categorisation_queue_arn" { + value = module.lambda.queu_arn + description = "ARN of the Categorisation SQS queue" +} \ No newline at end of file diff --git a/infrastructure/terraform/lambda/engine/outputs.tf b/infrastructure/terraform/lambda/engine/outputs.tf new file mode 100644 index 00000000..bba2046b --- /dev/null +++ b/infrastructure/terraform/lambda/engine/outputs.tf @@ -0,0 +1,9 @@ +output "ara_engine_queue_url" { + value = module.lambda.queu_url + description = "URL of the Engine SQS queue" +} + +output "ara_engine_queue_arn" { + value = module.lambda.queu_arn + description = "ARN of the Engine SQS queue" +} \ No newline at end of file diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index aabc1c52..82d2cc60 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -7,6 +7,24 @@ data "terraform_remote_state" "shared" { } } +data "terraform_remote_state" "engine" { + backend = "s3" + config = { + bucket = "ara-engine-terraform-state", + key = "env:/${var.stage}/teraform.tfstate" + region = "eu-west-2" + } +} + +data "terraform_remote_state" "categorisation" { + backend = "s3" + config = { + bucket = "categorisation-terraform-state", + key = "env:/${var.stage}/teraform.tfstate" + region = "eu-west-2" + } +} + data "aws_secretsmanager_secret_version" "db_credentials" { secret_id = "${var.stage}/assessment_model/db_credentials" } @@ -88,8 +106,8 @@ module "fastapi" { HOTWATER_KWH_PREDICTIONS_BUCKET = data.terraform_remote_state.shared.outputs.retrofit_hotwater_kwh_predictions_bucket_name ENERGY_ASSESSMENTS_BUCKET = data.terraform_remote_state.shared.outputs.retrofit_energy_assessments_bucket_name - ENGINE_SQS_URL = data.terraform_remote_state.shared.outputs.engine_queue_url - CATEGORISATION_SQS_URL = "https://sqs.eu-west-2.amazonaws.com/337213553626/categorisation-queue-dev" + ENGINE_SQS_URL = data.terraform_remote_state.engine.ara_engine_queue_url + CATEGORISATION_SQS_URL = data.terraform_remote_state.categorisation.categorisation_queue_url } } From 783ddd7be008120bbc9a9cd0d2cf23b9ec0c8bbe Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 10 Mar 2026 12:09:52 +0000 Subject: [PATCH 15/77] add some comments --- infrastructure/terraform/lambda/fast-api/main.tf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index 82d2cc60..ebf436c3 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -1,3 +1,6 @@ +############################################ +# Load Terraform State +############################################ data "terraform_remote_state" "shared" { backend = "s3" config = { @@ -25,6 +28,9 @@ data "terraform_remote_state" "categorisation" { } } +############################################ +# Load Credentials +############################################ data "aws_secretsmanager_secret_version" "db_credentials" { secret_id = "${var.stage}/assessment_model/db_credentials" } From 891ccd4a8b45f9239b854ff648e914b37feb9134 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 10 Mar 2026 13:55:45 +0000 Subject: [PATCH 16/77] fast api s3 policy --- .../terraform/lambda/fast-api/main.tf | 4 +-- infrastructure/terraform/shared/main.tf | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index ebf436c3..40e0f4f9 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -122,10 +122,10 @@ module "fastapi" { ############################################ resource "aws_iam_role_policy_attachment" "fastapi_s3_read" { role = module.fastapi.role_name - policy_arn = data.terraform_remote_state.shared.outputs.fastapi_s3_read_arn + policy_arn = data.terraform_remote_state.shared.outputs.fast_api_s3_read_arn } resource "aws_iam_role_policy_attachment" "fastapi_sqs_send" { role = module.fastapi.role_name - policy_arn = data.terraform_remote_state.shared.outputs.fastapi_sqs_send_arn + policy_arn = data.terraform_remote_state.shared.outputs.fast_api_sqs_send_arn } \ No newline at end of file diff --git a/infrastructure/terraform/shared/main.tf b/infrastructure/terraform/shared/main.tf index eaacddec..f4b2cd3f 100644 --- a/infrastructure/terraform/shared/main.tf +++ b/infrastructure/terraform/shared/main.tf @@ -535,3 +535,29 @@ module "ara_fastapi_registry" { name = "ara-fastapi" stage = var.stage } + +# S3 policy for FastAPI app to read and write from various S3 buckets +module "fast_api_s3_read_and_write" { + source = "../modules/s3_iam_policy" + + policy_name = "FastAPIReadandWriteS3" + policy_description = "Allow FastAPI Lambda to read from and write to various S3 buckets" + bucket_arns = [ + "arn:aws:s3:::${module.s3_presignable_bucket.bucket_name}", + "arn:aws:s3:::${module.retrofit_sap_data.bucket_name}", + "arn:aws:s3:::${module.retrofit_sap_predictions.bucket_name}", + "arn:aws:s3:::${module.retrofit_carbon_predictions.bucket_name}", + "arn:aws:s3:::${module.retrofit_heat_predictions.bucket_name}", + "arn:aws:s3:::${module.retrofit_heating_kwh_predictions.bucket_name}", + "arn:aws:s3:::${module.retrofit_hotwater_kwh_predictions.bucket_name}", + "arn:aws:s3:::${module.retrofit_energy_assessments.bucket_name}" + ] + actions = ["s3:GetObject", "s3:ListBucket"] + resource_paths = ["/*"] +} + +output "fast_api_s3_read_and_write_arn" { + value = module.fast_api_s3_read_and_write.policy_arn +} + + From f3d51c4c7c0d7bb19101ca93c03fa69f8f2578d6 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 10 Mar 2026 14:59:46 +0000 Subject: [PATCH 17/77] sqs permissions --- .../terraform/lambda/fast-api/main.tf | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index 40e0f4f9..cb4c923d 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -120,12 +120,36 @@ module "fastapi" { ############################################ # IAM policy attachments ############################################ -resource "aws_iam_role_policy_attachment" "fastapi_s3_read" { +resource "aws_iam_role_policy_attachment" "fast_api_s3_read" { role = module.fastapi.role_name policy_arn = data.terraform_remote_state.shared.outputs.fast_api_s3_read_arn } +module "fastapi_sqs_policy" { + source = "../../modules/generic_iam_policy" + + policy_name = "fastapi-sqs-send-${var.stage}" + policy_description = "Allow FastAPI to send messages to engine & categorisation queues" + + actions = [ + "sqs:SendMessage" + ] + + resources = [ + data.terraform_remote_state.engine.outputs.ara_engine_queue_arn, + data.terraform_remote_state.categorisation.outputs.categorisation_queue_arn + ] + + conditions = null + + tags = { + Service = "fastapi" + Stage = var.stage + } +} + + resource "aws_iam_role_policy_attachment" "fastapi_sqs_send" { role = module.fastapi.role_name - policy_arn = data.terraform_remote_state.shared.outputs.fast_api_sqs_send_arn + policy_arn = module.fastapi_sqs_policy.policy_arn } \ No newline at end of file From 9c4a8e11dba8f8a1644abb56be88603b3660d9a3 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 10 Mar 2026 15:15:21 +0000 Subject: [PATCH 18/77] correct s3 policy attachment --- infrastructure/terraform/lambda/fast-api/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index cb4c923d..e339b582 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -149,7 +149,7 @@ module "fastapi_sqs_policy" { } -resource "aws_iam_role_policy_attachment" "fastapi_sqs_send" { +resource "aws_iam_role_policy_attachment" "fastapi_sqs_read_and_write" { role = module.fastapi.role_name - policy_arn = module.fastapi_sqs_policy.policy_arn + policy_arn = data.terraform_remote_state.shared.outputs.fast_api_s3_read_and_write_arn } \ No newline at end of file From 201888bc3a05d3c5daee24ab289a7fd80cb00a38 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 10 Mar 2026 16:24:10 +0000 Subject: [PATCH 19/77] addres JTK PR: split optional ecr uri and image digest logic in deploy lambda --- .github/workflows/_deploy_lambda.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/_deploy_lambda.yml b/.github/workflows/_deploy_lambda.yml index 420f9504..3cef705e 100644 --- a/.github/workflows/_deploy_lambda.yml +++ b/.github/workflows/_deploy_lambda.yml @@ -121,15 +121,21 @@ jobs: TF_VAR_google_solar_api_key: ${{ secrets.TF_VAR_google_solar_api_key }} TF_VAR_ordnance_survey_api_key: ${{ secrets.TF_VAR_ordnance_survey_api_key}} run: | - EXTRA_VARS="" + ECR_REPO_URL_VAR="" if [[ -n "${{ inputs.ecr_repo }}" ]]; then - EXTRA_VARS="-var=ecr_repo_url=${{ steps.repo.outputs.ecr_repo_url }} -var=image_digest=${{ inputs.image_digest }}" + ECR_REPO_URL_VAR="-var=ecr_repo_url=${{ steps.repo.outputs.ecr_repo_url }}" + fi + + IMAGE_DIGEST_VAR="" + if [[ -n "${{ inputs.ecr_repo }}" ]]; then + IMAGE_DIGEST_VAR="-var=image_digest=${{ inputs.image_digest }}" fi terraform plan \ -var="stage=${{ inputs.stage }}" \ -var="lambda_name=${{ inputs.lambda_name }}" \ - $EXTRA_VARS \ + $ECR_REPO_URL_VAR \ + $IMAGE_DIGEST_VAR \ -out=lambdaplan - name: Terraform Apply From 510e2736340af049a2928e606102e8f338d04eb0 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 10 Mar 2026 16:30:27 +0000 Subject: [PATCH 20/77] addres JTK PR: remove pip upgrade after installing requirements --- infrastructure/terraform/lambda/fast-api/main.tf | 1 - 1 file changed, 1 deletion(-) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index e339b582..c87897c7 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -65,7 +65,6 @@ resource "null_resource" "pip_install" { --implementation cp \ --python-version 3.11 \ --only-binary=:all: \ - --upgrade EOT } } From 76a095e81583072ea35697bb4fbde9c92af94e91 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 09:48:15 +0000 Subject: [PATCH 21/77] add github workflow --- .github/workflows/deploy_terraform.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index 366cd004..3a234873 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -322,3 +322,27 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} AWS_REGION: ${{ secrets.DEV_AWS_REGION }} TF_VAR_ORDNANCE_SURVEY_API_KEY: ${{ secrets.ORDNANCE_SURVEY_API_KEY }} + + # ============================================================ + # Deploy FastAPI Lambda + # ============================================================ + fast_api_lambda: + needs: [determine_stage] + uses: ./.github/workflows/_deploy_lambda.yml + with: + lambda_name: ara_fast_api + lambda_path: infrastructure/terraform/lambda/fast-api + stage: ${{ needs.determine_stage.outputs.stage }} + terraform_apply: ${{ needs.determine_stage.outputs.terraform_apply }} + secrets: + AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.DEV_AWS_REGION }} + TF_VAR_db_host: ${{ secrets.DEV_DB_HOST }} + TF_VAR_db_name: ${{ secrets.DEV_DB_NAME }} + TF_VAR_db_port: ${{ secrets.DEV_DB_PORT }} + TF_VAR_api_key: ${{ secrets.DEV_API_KEY }} + TF_VAR_secret_key: ${{ secrets.DEV_SECRET_KEY }} + TF_VAR_domain_name: ${{ secrets.DEV_DOMAIN_NAME }} + TF_VAR_epc_auth_token: ${{ secrets.DEV_EPC_AUTH_TOKEN }} + TF_VAR_google_solar_api_key: ${{ secrets.DEV_GOOGLE_SOLAR_API_KEY }} From 8828e4da15c6fe5329d7e9764e1d9b294ce55d97 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 10:04:02 +0000 Subject: [PATCH 22/77] move engine and fastapi depoyments to top of deploy_terraform --- .github/workflows/deploy_terraform.yml | 130 +++++++++++++------------ 1 file changed, 66 insertions(+), 64 deletions(-) diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index 3a234873..6291dd2a 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -77,6 +77,71 @@ jobs: if: env.TERRAFORM_APPLY == 'true' working-directory: infrastructure/terraform/shared run: terraform apply -auto-approve tfplan + + # ============================================================ + # Ara Engine image and Push + # ============================================================ + ara_engine_image: + needs: [determine_stage, shared_terraform] + uses: ./.github/workflows/_build_image.yml + with: + ecr_repo: engine-${{ needs.determine_stage.outputs.stage }} + dockerfile_path: backend/docker/engine.Dockerfile + build_context: . + secrets: + AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.DEV_AWS_REGION }} + + # ============================================================ + # Deploy Ara Engine Lambda + # ============================================================ + ara_engine_lambda: + needs: [ara_engine_image, determine_stage] + uses: ./.github/workflows/_deploy_lambda.yml + with: + lambda_name: ara_engine + lambda_path: infrastructure/terraform/lambda/engine + stage: ${{ needs.determine_stage.outputs.stage }} + ecr_repo: engine-${{ needs.determine_stage.outputs.stage }} + image_digest: ${{ needs.ara_engine_image.outputs.image_digest }} + terraform_apply: ${{ needs.determine_stage.outputs.terraform_apply }} + secrets: + AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.DEV_AWS_REGION }} + TF_VAR_db_host: ${{ secrets.DEV_DB_HOST }} + TF_VAR_db_name: ${{ secrets.DEV_DB_NAME }} + TF_VAR_db_port: ${{ secrets.DEV_DB_PORT }} + TF_VAR_api_key: ${{ secrets.DEV_API_KEY }} + TF_VAR_secret_key: ${{ secrets.DEV_SECRET_KEY }} + TF_VAR_domain_name: ${{ secrets.DEV_DOMAIN_NAME }} + TF_VAR_epc_auth_token: ${{ secrets.DEV_EPC_AUTH_TOKEN }} + TF_VAR_google_solar_api_key: ${{ secrets.DEV_GOOGLE_SOLAR_API_KEY }} + + # ============================================================ + # Deploy FastAPI Lambda + # ============================================================ + fast_api_lambda: + needs: [determine_stage] + uses: ./.github/workflows/_deploy_lambda.yml + with: + lambda_name: ara_fast_api + lambda_path: infrastructure/terraform/lambda/fast-api + stage: ${{ needs.determine_stage.outputs.stage }} + terraform_apply: ${{ needs.determine_stage.outputs.terraform_apply }} + secrets: + AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.DEV_AWS_REGION }} + TF_VAR_db_host: ${{ secrets.DEV_DB_HOST }} + TF_VAR_db_name: ${{ secrets.DEV_DB_NAME }} + TF_VAR_db_port: ${{ secrets.DEV_DB_PORT }} + TF_VAR_api_key: ${{ secrets.DEV_API_KEY }} + TF_VAR_secret_key: ${{ secrets.DEV_SECRET_KEY }} + TF_VAR_domain_name: ${{ secrets.DEV_DOMAIN_NAME }} + TF_VAR_epc_auth_token: ${{ secrets.DEV_EPC_AUTH_TOKEN }} + TF_VAR_google_solar_api_key: ${{ secrets.DEV_GOOGLE_SOLAR_API_KEY }} # ============================================================ # Build Address 2 UPRN image and Push @@ -241,47 +306,6 @@ jobs: AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} AWS_REGION: ${{ secrets.DEV_AWS_REGION }} - # ============================================================ - # Ara Engine image and Push - # ============================================================ - ara_engine_image: - needs: [determine_stage, shared_terraform] - uses: ./.github/workflows/_build_image.yml - with: - ecr_repo: engine-${{ needs.determine_stage.outputs.stage }} - dockerfile_path: backend/docker/engine.Dockerfile - build_context: . - secrets: - AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.DEV_AWS_REGION }} - - # ============================================================ - # Deploy Ara Engine Lambda - # ============================================================ - ara_engine_lambda: - needs: [ara_engine_image, determine_stage] - uses: ./.github/workflows/_deploy_lambda.yml - with: - lambda_name: ara_engine - lambda_path: infrastructure/terraform/lambda/engine - stage: ${{ needs.determine_stage.outputs.stage }} - ecr_repo: engine-${{ needs.determine_stage.outputs.stage }} - image_digest: ${{ needs.ara_engine_image.outputs.image_digest }} - terraform_apply: ${{ needs.determine_stage.outputs.terraform_apply }} - secrets: - AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.DEV_AWS_REGION }} - TF_VAR_db_host: ${{ secrets.DEV_DB_HOST }} - TF_VAR_db_name: ${{ secrets.DEV_DB_NAME }} - TF_VAR_db_port: ${{ secrets.DEV_DB_PORT }} - TF_VAR_api_key: ${{ secrets.DEV_API_KEY }} - TF_VAR_secret_key: ${{ secrets.DEV_SECRET_KEY }} - TF_VAR_domain_name: ${{ secrets.DEV_DOMAIN_NAME }} - TF_VAR_epc_auth_token: ${{ secrets.DEV_EPC_AUTH_TOKEN }} - TF_VAR_google_solar_api_key: ${{ secrets.DEV_GOOGLE_SOLAR_API_KEY }} - # ============================================================ # Build OrdanceSurvey image and Push # ============================================================ @@ -323,26 +347,4 @@ jobs: AWS_REGION: ${{ secrets.DEV_AWS_REGION }} TF_VAR_ORDNANCE_SURVEY_API_KEY: ${{ secrets.ORDNANCE_SURVEY_API_KEY }} - # ============================================================ - # Deploy FastAPI Lambda - # ============================================================ - fast_api_lambda: - needs: [determine_stage] - uses: ./.github/workflows/_deploy_lambda.yml - with: - lambda_name: ara_fast_api - lambda_path: infrastructure/terraform/lambda/fast-api - stage: ${{ needs.determine_stage.outputs.stage }} - terraform_apply: ${{ needs.determine_stage.outputs.terraform_apply }} - secrets: - AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.DEV_AWS_REGION }} - TF_VAR_db_host: ${{ secrets.DEV_DB_HOST }} - TF_VAR_db_name: ${{ secrets.DEV_DB_NAME }} - TF_VAR_db_port: ${{ secrets.DEV_DB_PORT }} - TF_VAR_api_key: ${{ secrets.DEV_API_KEY }} - TF_VAR_secret_key: ${{ secrets.DEV_SECRET_KEY }} - TF_VAR_domain_name: ${{ secrets.DEV_DOMAIN_NAME }} - TF_VAR_epc_auth_token: ${{ secrets.DEV_EPC_AUTH_TOKEN }} - TF_VAR_google_solar_api_key: ${{ secrets.DEV_GOOGLE_SOLAR_API_KEY }} + From 7cade00db0658582542ccd018d2fec683bfd60e2 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 10:54:44 +0000 Subject: [PATCH 23/77] commented out custom domain stuff for now --- .../terraform/lambda/fast-api/main.tf | 19 ++++--- .../modules/lambda_with_api_gateway/main.tf | 52 +++++++++---------- 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index c87897c7..d9377b79 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -39,14 +39,13 @@ locals { db_credentials = jsondecode(data.aws_secretsmanager_secret_version.db_credentials.secret_string) } +# data "aws_ssm_parameter" "certificate_arn" { +# name = "/ssl_certificate_arn" +# } -data "aws_ssm_parameter" "certificate_arn" { - name = "/ssl_certificate_arn" -} - -data "aws_route53_zone" "this" { - name = var.domain_name -} +# data "aws_route53_zone" "this" { +# name = var.domain_name +# } ############################################ # Install Python requirements @@ -84,9 +83,9 @@ module "fastapi" { timeout = 600 memory_size = 512 - domain_name = "api.${var.domain_name}" - certificate_arn = data.aws_ssm_parameter.certificate_arn.value - route53_zone_id = data.aws_route53_zone.this.zone_id + # domain_name = "api.${var.domain_name}" + # certificate_arn = data.aws_ssm_parameter.certificate_arn.value + # route53_zone_id = data.aws_route53_zone.this.zone_id environment = { ENVIRONMENT = var.stage diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf index 23ccc4b1..61e24c32 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf @@ -71,33 +71,33 @@ resource "aws_lambda_permission" "apigw_invoke" { ############################################ # Custom domain ############################################ -resource "aws_apigatewayv2_domain_name" "this" { - count = var.domain_name != null ? 1 : 0 - domain_name = var.domain_name +# resource "aws_apigatewayv2_domain_name" "this" { +# count = var.domain_name != null ? 1 : 0 +# domain_name = var.domain_name - domain_name_configuration { - certificate_arn = var.certificate_arn - endpoint_type = "REGIONAL" - security_policy = "TLS_1_2" - } -} +# domain_name_configuration { +# certificate_arn = var.certificate_arn +# endpoint_type = "REGIONAL" +# security_policy = "TLS_1_2" +# } +# } -resource "aws_apigatewayv2_api_mapping" "this" { - count = var.domain_name != null ? 1 : 0 - api_id = aws_apigatewayv2_api.this.id - domain_name = aws_apigatewayv2_domain_name.this[0].id - stage = aws_apigatewayv2_stage.this.id -} +# resource "aws_apigatewayv2_api_mapping" "this" { +# count = var.domain_name != null ? 1 : 0 +# api_id = aws_apigatewayv2_api.this.id +# domain_name = aws_apigatewayv2_domain_name.this[0].id +# stage = aws_apigatewayv2_stage.this.id +# } -resource "aws_route53_record" "this" { - count = var.domain_name != null ? 1 : 0 - name = aws_apigatewayv2_domain_name.this[0].domain_name - type = "A" - zone_id = var.route53_zone_id +# resource "aws_route53_record" "this" { +# count = var.domain_name != null ? 1 : 0 +# name = aws_apigatewayv2_domain_name.this[0].domain_name +# type = "A" +# zone_id = var.route53_zone_id - alias { - name = aws_apigatewayv2_domain_name.this[0].domain_name_configuration[0].target_domain_name - zone_id = aws_apigatewayv2_domain_name.this[0].domain_name_configuration[0].hosted_zone_id - evaluate_target_health = false - } -} \ No newline at end of file +# alias { +# name = aws_apigatewayv2_domain_name.this[0].domain_name_configuration[0].target_domain_name +# zone_id = aws_apigatewayv2_domain_name.this[0].domain_name_configuration[0].hosted_zone_id +# evaluate_target_health = false +# } +# } \ No newline at end of file From 05843ab46263f1a150830e5c017a0cf3b77bb6b0 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 10:56:09 +0000 Subject: [PATCH 24/77] comment out custom_domain_endpoint in outputs.tf --- .../terraform/modules/lambda_with_api_gateway/outputs.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf index 52db1ff9..9ced7c8b 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf @@ -6,6 +6,6 @@ output "api_endpoint" { value = aws_apigatewayv2_stage.this.invoke_url } -output "custom_domain_endpoint" { - value = var.domain_name != null ? "https://${var.domain_name}" : null -} \ No newline at end of file +# output "custom_domain_endpoint" { +# value = var.domain_name != null ? "https://${var.domain_name}" : null +# } \ No newline at end of file From 1a161396805d192cee3241fa11fac6098a6e5011 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 11:39:24 +0000 Subject: [PATCH 25/77] comment out DOMAIN_NAME --- infrastructure/terraform/lambda/fast-api/main.tf | 2 +- infrastructure/terraform/lambda/fast-api/variables.tf | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index d9377b79..0a40b14c 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -91,7 +91,7 @@ module "fastapi" { ENVIRONMENT = var.stage API_KEY = var.api_key SECRET_KEY = var.secret_key - DOMAIN_NAME = var.domain_name + # DOMAIN_NAME = var.domain_name EPC_AUTH_TOKEN = var.epc_auth_token GOOGLE_SOLAR_API_KEY = var.google_solar_api_key diff --git a/infrastructure/terraform/lambda/fast-api/variables.tf b/infrastructure/terraform/lambda/fast-api/variables.tf index a3157590..d329e0ca 100644 --- a/infrastructure/terraform/lambda/fast-api/variables.tf +++ b/infrastructure/terraform/lambda/fast-api/variables.tf @@ -29,9 +29,9 @@ variable "secret_key" { sensitive = true } -variable "domain_name" { - type = string -} +# variable "domain_name" { +# type = string +# } variable "epc_auth_token" { type = string From 496ec22705e46a3c377a1fd3e619e8f9c4ec875b Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 11:49:27 +0000 Subject: [PATCH 26/77] fix typos --- infrastructure/terraform/lambda/categorisation/outputs.tf | 2 +- infrastructure/terraform/lambda/engine/outputs.tf | 2 +- infrastructure/terraform/lambda/fast-api/main.tf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/infrastructure/terraform/lambda/categorisation/outputs.tf b/infrastructure/terraform/lambda/categorisation/outputs.tf index be1ac118..06e06623 100644 --- a/infrastructure/terraform/lambda/categorisation/outputs.tf +++ b/infrastructure/terraform/lambda/categorisation/outputs.tf @@ -1,5 +1,5 @@ output "categorisation_queue_url" { - value = module.lambda.queu_url + value = module.lambda.queue_url description = "URL of the Categorisation SQS queue" } diff --git a/infrastructure/terraform/lambda/engine/outputs.tf b/infrastructure/terraform/lambda/engine/outputs.tf index bba2046b..d12e0684 100644 --- a/infrastructure/terraform/lambda/engine/outputs.tf +++ b/infrastructure/terraform/lambda/engine/outputs.tf @@ -1,5 +1,5 @@ output "ara_engine_queue_url" { - value = module.lambda.queu_url + value = module.lambda.queue_url description = "URL of the Engine SQS queue" } diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index 0a40b14c..0449b33d 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -124,7 +124,7 @@ resource "aws_iam_role_policy_attachment" "fast_api_s3_read" { } module "fastapi_sqs_policy" { - source = "../../modules/generic_iam_policy" + source = "../../modules/general_iam_policy" policy_name = "fastapi-sqs-send-${var.stage}" policy_description = "Allow FastAPI to send messages to engine & categorisation queues" From e8c839f3e494f24b736d527e43167eb10647c60a Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 12:02:22 +0000 Subject: [PATCH 27/77] no single line variables with defaults. and typos --- .../lambda/categorisation/outputs.tf | 2 +- .../terraform/lambda/engine/outputs.tf | 2 +- .../modules/lambda_service_zip/variables.tf | 15 ++++++++-- .../lambda_with_api_gateway/variables.tf | 30 +++++++++++++++---- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/infrastructure/terraform/lambda/categorisation/outputs.tf b/infrastructure/terraform/lambda/categorisation/outputs.tf index 06e06623..8e33b8e0 100644 --- a/infrastructure/terraform/lambda/categorisation/outputs.tf +++ b/infrastructure/terraform/lambda/categorisation/outputs.tf @@ -4,6 +4,6 @@ output "categorisation_queue_url" { } output "categorisation_queue_arn" { - value = module.lambda.queu_arn + value = module.lambda.queue_arn description = "ARN of the Categorisation SQS queue" } \ No newline at end of file diff --git a/infrastructure/terraform/lambda/engine/outputs.tf b/infrastructure/terraform/lambda/engine/outputs.tf index d12e0684..c59e0809 100644 --- a/infrastructure/terraform/lambda/engine/outputs.tf +++ b/infrastructure/terraform/lambda/engine/outputs.tf @@ -4,6 +4,6 @@ output "ara_engine_queue_url" { } output "ara_engine_queue_arn" { - value = module.lambda.queu_arn + value = module.lambda.queue_arn description = "ARN of the Engine SQS queue" } \ No newline at end of file diff --git a/infrastructure/terraform/modules/lambda_service_zip/variables.tf b/infrastructure/terraform/modules/lambda_service_zip/variables.tf index 3a243c49..68a35370 100644 --- a/infrastructure/terraform/modules/lambda_service_zip/variables.tf +++ b/infrastructure/terraform/modules/lambda_service_zip/variables.tf @@ -4,6 +4,15 @@ variable "filename" { type = string } variable "source_code_hash" { type = string } variable "handler" { type = string } variable "runtime" { type = string } -variable "timeout" { type = number default = 30 } -variable "memory_size" { type = number default = 128 } -variable "environment" { type = map(string) default = {} } \ No newline at end of file +variable "timeout" { + type = number + default = 30 +} +variable "memory_size" { + type = number + default = 128 +} +variable "environment" { + type = map(string) + default = {} +} \ No newline at end of file diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf index 1a08ff2e..b32380de 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf @@ -9,10 +9,28 @@ variable "zip_excludes" { default = ["**/__pycache__/**", "**/*.pyc", "**/.pytest_cache/**"] } -variable "timeout" { type = number default = 600 } -variable "memory_size" { type = number default = 512 } -variable "environment" { type = map(string) default = {} } +variable "timeout" { + type = number + default = 600 +} +variable "memory_size" { + type = number + default = 512 +} +variable "environment" { + type = map(string) + default = {} +} -variable "domain_name" { type = string default = null } -variable "certificate_arn" { type = string default = null } -variable "route53_zone_id" { type = string default = null } \ No newline at end of file +variable "domain_name" { + type = string + default = null +} +variable "certificate_arn" { + type = string + default = null +} +variable "route53_zone_id" { + type = string + default = null +} \ No newline at end of file From 9aa60c9e70d622326b20ae48efd733fedb3856d2 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 12:58:31 +0000 Subject: [PATCH 28/77] fast api deployment dependencies --- .github/workflows/deploy_terraform.yml | 47 +++++++++++++------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index 6291dd2a..d3a16b35 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -118,30 +118,6 @@ jobs: TF_VAR_domain_name: ${{ secrets.DEV_DOMAIN_NAME }} TF_VAR_epc_auth_token: ${{ secrets.DEV_EPC_AUTH_TOKEN }} TF_VAR_google_solar_api_key: ${{ secrets.DEV_GOOGLE_SOLAR_API_KEY }} - - # ============================================================ - # Deploy FastAPI Lambda - # ============================================================ - fast_api_lambda: - needs: [determine_stage] - uses: ./.github/workflows/_deploy_lambda.yml - with: - lambda_name: ara_fast_api - lambda_path: infrastructure/terraform/lambda/fast-api - stage: ${{ needs.determine_stage.outputs.stage }} - terraform_apply: ${{ needs.determine_stage.outputs.terraform_apply }} - secrets: - AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.DEV_AWS_REGION }} - TF_VAR_db_host: ${{ secrets.DEV_DB_HOST }} - TF_VAR_db_name: ${{ secrets.DEV_DB_NAME }} - TF_VAR_db_port: ${{ secrets.DEV_DB_PORT }} - TF_VAR_api_key: ${{ secrets.DEV_API_KEY }} - TF_VAR_secret_key: ${{ secrets.DEV_SECRET_KEY }} - TF_VAR_domain_name: ${{ secrets.DEV_DOMAIN_NAME }} - TF_VAR_epc_auth_token: ${{ secrets.DEV_EPC_AUTH_TOKEN }} - TF_VAR_google_solar_api_key: ${{ secrets.DEV_GOOGLE_SOLAR_API_KEY }} # ============================================================ # Build Address 2 UPRN image and Push @@ -347,4 +323,27 @@ jobs: AWS_REGION: ${{ secrets.DEV_AWS_REGION }} TF_VAR_ORDNANCE_SURVEY_API_KEY: ${{ secrets.ORDNANCE_SURVEY_API_KEY }} + # ============================================================ + # Deploy FastAPI Lambda + # ============================================================ + fast_api_lambda: + needs: [determine_stage, shared_terraform, ara_engine_lambda, categorisation_lambda] + uses: ./.github/workflows/_deploy_lambda.yml + with: + lambda_name: ara_fast_api + lambda_path: infrastructure/terraform/lambda/fast-api + stage: ${{ needs.determine_stage.outputs.stage }} + terraform_apply: ${{ needs.determine_stage.outputs.terraform_apply }} + secrets: + AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.DEV_AWS_REGION }} + TF_VAR_db_host: ${{ secrets.DEV_DB_HOST }} + TF_VAR_db_name: ${{ secrets.DEV_DB_NAME }} + TF_VAR_db_port: ${{ secrets.DEV_DB_PORT }} + TF_VAR_api_key: ${{ secrets.DEV_API_KEY }} + TF_VAR_secret_key: ${{ secrets.DEV_SECRET_KEY }} + TF_VAR_domain_name: ${{ secrets.DEV_DOMAIN_NAME }} + TF_VAR_epc_auth_token: ${{ secrets.DEV_EPC_AUTH_TOKEN }} + TF_VAR_google_solar_api_key: ${{ secrets.DEV_GOOGLE_SOLAR_API_KEY }} From af031d81d40bc731ea2e6285ec7087f15c01150f Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 14:04:56 +0000 Subject: [PATCH 29/77] fast api does not technically depend on shared --- .github/workflows/deploy_terraform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index d3a16b35..6432bf38 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -327,7 +327,7 @@ jobs: # Deploy FastAPI Lambda # ============================================================ fast_api_lambda: - needs: [determine_stage, shared_terraform, ara_engine_lambda, categorisation_lambda] + needs: [determine_stage, ara_engine_lambda, categorisation_lambda] uses: ./.github/workflows/_deploy_lambda.yml with: lambda_name: ara_fast_api From 3b41ea4a2322ab44374dc6d199a5b2ef33950976 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 14:22:19 +0000 Subject: [PATCH 30/77] correct use of tfstate outputs in fast api terraform --- infrastructure/terraform/lambda/fast-api/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index 0449b33d..8596aa66 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -110,8 +110,8 @@ module "fastapi" { HOTWATER_KWH_PREDICTIONS_BUCKET = data.terraform_remote_state.shared.outputs.retrofit_hotwater_kwh_predictions_bucket_name ENERGY_ASSESSMENTS_BUCKET = data.terraform_remote_state.shared.outputs.retrofit_energy_assessments_bucket_name - ENGINE_SQS_URL = data.terraform_remote_state.engine.ara_engine_queue_url - CATEGORISATION_SQS_URL = data.terraform_remote_state.categorisation.categorisation_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 } } From bf8e65be1f10152894f6d9f88272f247b6386c47 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 14:36:13 +0000 Subject: [PATCH 31/77] fix typo in tfstate s3 location --- infrastructure/terraform/lambda/fast-api/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index 8596aa66..05d3861d 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -14,7 +14,7 @@ data "terraform_remote_state" "engine" { backend = "s3" config = { bucket = "ara-engine-terraform-state", - key = "env:/${var.stage}/teraform.tfstate" + key = "env:/${var.stage}/terraform.tfstate" region = "eu-west-2" } } @@ -23,7 +23,7 @@ data "terraform_remote_state" "categorisation" { backend = "s3" config = { bucket = "categorisation-terraform-state", - key = "env:/${var.stage}/teraform.tfstate" + key = "env:/${var.stage}/terraform.tfstate" region = "eu-west-2" } } From 8722d0cce45f0543e0bcf769d090f8953062e9c2 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 14:46:10 +0000 Subject: [PATCH 32/77] correct s3 policy name --- infrastructure/terraform/lambda/fast-api/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index 05d3861d..fbe5805f 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -120,7 +120,7 @@ module "fastapi" { ############################################ resource "aws_iam_role_policy_attachment" "fast_api_s3_read" { role = module.fastapi.role_name - policy_arn = data.terraform_remote_state.shared.outputs.fast_api_s3_read_arn + policy_arn = data.terraform_remote_state.shared.outputs.fast_api_s3_read_and_write_arn } module "fastapi_sqs_policy" { From 325d2f2cc167d2df243c5e24252ee86f515ea883 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 15:38:16 +0000 Subject: [PATCH 33/77] upgrade terraform AWS provider --- infrastructure/terraform/lambda/_template/provider.tf | 2 +- infrastructure/terraform/lambda/address2UPRN/provider.tf | 2 +- infrastructure/terraform/lambda/categorisation/provider.tf | 2 +- infrastructure/terraform/lambda/condition-etl/provider.tf | 2 +- infrastructure/terraform/lambda/engine/provider.tf | 2 +- infrastructure/terraform/lambda/fast-api/provider.tf | 2 +- infrastructure/terraform/lambda/ordnanceSurvey/provider.tf | 2 +- infrastructure/terraform/lambda/postcodeSplitter/provider.tf | 2 +- infrastructure/terraform/shared/main.tf | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/infrastructure/terraform/lambda/_template/provider.tf b/infrastructure/terraform/lambda/_template/provider.tf index 37c412ce..3d66f392 100644 --- a/infrastructure/terraform/lambda/_template/provider.tf +++ b/infrastructure/terraform/lambda/_template/provider.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 4.16" + version = ">= 5.0" } } diff --git a/infrastructure/terraform/lambda/address2UPRN/provider.tf b/infrastructure/terraform/lambda/address2UPRN/provider.tf index ad873717..3cfa2400 100644 --- a/infrastructure/terraform/lambda/address2UPRN/provider.tf +++ b/infrastructure/terraform/lambda/address2UPRN/provider.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 4.16" + version = ">= 5.0" } } diff --git a/infrastructure/terraform/lambda/categorisation/provider.tf b/infrastructure/terraform/lambda/categorisation/provider.tf index fe497c81..30e73ed2 100644 --- a/infrastructure/terraform/lambda/categorisation/provider.tf +++ b/infrastructure/terraform/lambda/categorisation/provider.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 4.16" + version = ">= 5.0" } } diff --git a/infrastructure/terraform/lambda/condition-etl/provider.tf b/infrastructure/terraform/lambda/condition-etl/provider.tf index c633d238..f7adf65a 100644 --- a/infrastructure/terraform/lambda/condition-etl/provider.tf +++ b/infrastructure/terraform/lambda/condition-etl/provider.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 4.16" + version = ">= 5.0" } } diff --git a/infrastructure/terraform/lambda/engine/provider.tf b/infrastructure/terraform/lambda/engine/provider.tf index 2895d039..74021fd0 100644 --- a/infrastructure/terraform/lambda/engine/provider.tf +++ b/infrastructure/terraform/lambda/engine/provider.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 4.16" + version = ">= 5.0" } } diff --git a/infrastructure/terraform/lambda/fast-api/provider.tf b/infrastructure/terraform/lambda/fast-api/provider.tf index 607f4bcf..afe6f3f6 100644 --- a/infrastructure/terraform/lambda/fast-api/provider.tf +++ b/infrastructure/terraform/lambda/fast-api/provider.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 4.16" + version = ">= 5.0" } } diff --git a/infrastructure/terraform/lambda/ordnanceSurvey/provider.tf b/infrastructure/terraform/lambda/ordnanceSurvey/provider.tf index b7f453f1..12bd0f85 100644 --- a/infrastructure/terraform/lambda/ordnanceSurvey/provider.tf +++ b/infrastructure/terraform/lambda/ordnanceSurvey/provider.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 4.16" + version = ">= 5.0" } } diff --git a/infrastructure/terraform/lambda/postcodeSplitter/provider.tf b/infrastructure/terraform/lambda/postcodeSplitter/provider.tf index dbe323f2..5749143d 100644 --- a/infrastructure/terraform/lambda/postcodeSplitter/provider.tf +++ b/infrastructure/terraform/lambda/postcodeSplitter/provider.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 4.16" + version = ">= 5.0" } } diff --git a/infrastructure/terraform/shared/main.tf b/infrastructure/terraform/shared/main.tf index f4b2cd3f..1e88435d 100644 --- a/infrastructure/terraform/shared/main.tf +++ b/infrastructure/terraform/shared/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 4.16" + version = ">= 5.0" } } backend "s3" { From c7d0a4510742f316b9c383d7a52d3b29746c1eff Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 17:02:18 +0000 Subject: [PATCH 34/77] fix path and add missing env vars --- infrastructure/terraform/lambda/fast-api/main.tf | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index fbe5805f..df130b75 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -77,8 +77,8 @@ module "fastapi" { name = "fastapi" stage = var.stage - source_dir = "${path.root}/../../../../backend" - handler = "app.main.handler" + source_dir = "${path.root}/../../../.." + handler = "backend.app.main.handler" runtime = "python3.11" timeout = 600 memory_size = 512 @@ -109,6 +109,8 @@ module "fastapi" { HEATING_KWH_PREDICTIONS_BUCKET = data.terraform_remote_state.shared.outputs.retrofit_heating_kwh_predictions_bucket_name HOTWATER_KWH_PREDICTIONS_BUCKET = data.terraform_remote_state.shared.outputs.retrofit_hotwater_kwh_predictions_bucket_name ENERGY_ASSESSMENTS_BUCKET = data.terraform_remote_state.shared.outputs.retrofit_energy_assessments_bucket_name + SECRET_KEY = data.terraform_remote_state.shared.outputs.secret_key + API_KEY = data.terraform_remote_state.shared.outputs.api_key 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 @@ -120,7 +122,7 @@ module "fastapi" { ############################################ resource "aws_iam_role_policy_attachment" "fast_api_s3_read" { role = module.fastapi.role_name - policy_arn = data.terraform_remote_state.shared.outputs.fast_api_s3_read_and_write_arn + policy_arn = data.terraform_remote_state.shared.outputs.fast_api_s3_read_arn } module "fastapi_sqs_policy" { From 296166d56869598fb8ed7c8555eb0e05e4675f53 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 17:06:33 +0000 Subject: [PATCH 35/77] add slash at end of path --- infrastructure/terraform/lambda/fast-api/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index df130b75..0cfd09f7 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -77,7 +77,7 @@ module "fastapi" { name = "fastapi" stage = var.stage - source_dir = "${path.root}/../../../.." + source_dir = "${path.root}/../../../../" handler = "backend.app.main.handler" runtime = "python3.11" timeout = 600 From 182fb8931e6054d215ef7e17f7826e3cb939f58f Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 17:23:38 +0000 Subject: [PATCH 36/77] correct environment variables and policy definition --- .github/workflows/deploy_terraform.yml | 4 ++-- infrastructure/terraform/lambda/fast-api/main.tf | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index 6432bf38..ef927ccb 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -341,8 +341,8 @@ jobs: TF_VAR_db_host: ${{ secrets.DEV_DB_HOST }} TF_VAR_db_name: ${{ secrets.DEV_DB_NAME }} TF_VAR_db_port: ${{ secrets.DEV_DB_PORT }} - TF_VAR_api_key: ${{ secrets.DEV_API_KEY }} - TF_VAR_secret_key: ${{ secrets.DEV_SECRET_KEY }} + TF_VAR_api_key: ${{ secrets.FASTAPI_API_KEY }} + TF_VAR_secret_key: ${{ secrets.NEXTAUTH_SECRET }} TF_VAR_domain_name: ${{ secrets.DEV_DOMAIN_NAME }} TF_VAR_epc_auth_token: ${{ secrets.DEV_EPC_AUTH_TOKEN }} TF_VAR_google_solar_api_key: ${{ secrets.DEV_GOOGLE_SOLAR_API_KEY }} diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index 0cfd09f7..c9058fb7 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -109,8 +109,6 @@ module "fastapi" { HEATING_KWH_PREDICTIONS_BUCKET = data.terraform_remote_state.shared.outputs.retrofit_heating_kwh_predictions_bucket_name HOTWATER_KWH_PREDICTIONS_BUCKET = data.terraform_remote_state.shared.outputs.retrofit_hotwater_kwh_predictions_bucket_name ENERGY_ASSESSMENTS_BUCKET = data.terraform_remote_state.shared.outputs.retrofit_energy_assessments_bucket_name - SECRET_KEY = data.terraform_remote_state.shared.outputs.secret_key - API_KEY = data.terraform_remote_state.shared.outputs.api_key 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 @@ -120,11 +118,6 @@ module "fastapi" { ############################################ # IAM policy attachments ############################################ -resource "aws_iam_role_policy_attachment" "fast_api_s3_read" { - role = module.fastapi.role_name - policy_arn = data.terraform_remote_state.shared.outputs.fast_api_s3_read_arn -} - module "fastapi_sqs_policy" { source = "../../modules/general_iam_policy" From 5a6294b79e95c8fa05c5ba29681ec3a4b37f03e9 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Wed, 11 Mar 2026 17:45:15 +0000 Subject: [PATCH 37/77] exclude tests from lambda zip --- .../terraform/modules/lambda_with_api_gateway/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf index b32380de..ba2d844e 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf @@ -6,7 +6,7 @@ variable "runtime" { type = string } variable "zip_excludes" { type = list(string) - default = ["**/__pycache__/**", "**/*.pyc", "**/.pytest_cache/**"] + default = ["**/__pycache__/**", "**/*.pyc", "**/.pytest_cache/**", "**/tests/**"] } variable "timeout" { From c58cfee0b631eb699b3ebda5dc5eb3c191473991 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 12 Mar 2026 10:41:08 +0000 Subject: [PATCH 38/77] upload zip to s3 rather than copying via api --- infrastructure/terraform/lambda/fast-api/main.tf | 1 + .../terraform/modules/lambda_service_zip/main.tf | 8 ++++++-- .../modules/lambda_service_zip/variables.tf | 7 +++++-- .../modules/lambda_with_api_gateway/main.tf | 13 ++++++++++++- .../modules/lambda_with_api_gateway/variables.tf | 3 ++- infrastructure/terraform/shared/main.tf | 6 ------ 6 files changed, 26 insertions(+), 12 deletions(-) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index c9058fb7..9e8c7c2b 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -82,6 +82,7 @@ module "fastapi" { runtime = "python3.11" timeout = 600 memory_size = 512 + artifact_bucket = data.terraform_remote_state.shared.outputs.ara_fast_api_state_bucket # domain_name = "api.${var.domain_name}" # certificate_arn = data.aws_ssm_parameter.certificate_arn.value diff --git a/infrastructure/terraform/modules/lambda_service_zip/main.tf b/infrastructure/terraform/modules/lambda_service_zip/main.tf index 285aa9d4..232b5b56 100644 --- a/infrastructure/terraform/modules/lambda_service_zip/main.tf +++ b/infrastructure/terraform/modules/lambda_service_zip/main.tf @@ -2,7 +2,7 @@ resource "aws_lambda_function" "this" { function_name = var.name role = var.role_arn package_type = "Zip" - filename = var.filename + # filename = var.filename source_code_hash = var.source_code_hash handler = var.handler runtime = var.runtime @@ -10,6 +10,10 @@ resource "aws_lambda_function" "this" { memory_size = var.memory_size publish = true + s3_bucket = var.s3_bucket + s3_key = var.s3_key + source_code_hash = var.source_code_hash + environment { variables = var.environment } @@ -21,4 +25,4 @@ output "lambda_arn" { output "function_name" { value = aws_lambda_function.this.function_name -} \ No newline at end of file +} diff --git a/infrastructure/terraform/modules/lambda_service_zip/variables.tf b/infrastructure/terraform/modules/lambda_service_zip/variables.tf index 68a35370..85d1f548 100644 --- a/infrastructure/terraform/modules/lambda_service_zip/variables.tf +++ b/infrastructure/terraform/modules/lambda_service_zip/variables.tf @@ -1,6 +1,6 @@ variable "name" { type = string } variable "role_arn" { type = string } -variable "filename" { type = string } +# variable "filename" { type = string } variable "source_code_hash" { type = string } variable "handler" { type = string } variable "runtime" { type = string } @@ -15,4 +15,7 @@ variable "memory_size" { variable "environment" { type = map(string) default = {} -} \ No newline at end of file +} +variable "s3_bucket" { type = string } +variable "s3_key" { type = string } +variable "source_code_hash" { type = string } \ No newline at end of file diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf index 61e24c32..2277dee5 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf @@ -16,6 +16,16 @@ data "archive_file" "this" { excludes = var.zip_excludes } +############################################ +# Upload zip to S3 +############################################ +resource "aws_s3_object" "lambda_zip" { + bucket = var.artifact_bucket + key = "${var.name}-${var.stage}.zip" + source = data.archive_file.this.output_path + etag = data.archive_file.this.output_md5 +} + ############################################ # Lambda ############################################ @@ -24,7 +34,8 @@ module "lambda" { name = "${var.name}-${var.stage}" role_arn = module.role.role_arn - filename = data.archive_file.this.output_path + s3_bucket = var.artifact_bucket + s3_key = aws_s3_object.lambda_zip.key source_code_hash = data.archive_file.this.output_base64sha256 handler = var.handler runtime = var.runtime diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf index ba2d844e..0b1dfe71 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf @@ -33,4 +33,5 @@ variable "certificate_arn" { variable "route53_zone_id" { type = string default = null -} \ No newline at end of file +} +variable "artifact_bucket" { type = string } \ No newline at end of file diff --git a/infrastructure/terraform/shared/main.tf b/infrastructure/terraform/shared/main.tf index 1e88435d..8a4e4a1f 100644 --- a/infrastructure/terraform/shared/main.tf +++ b/infrastructure/terraform/shared/main.tf @@ -530,12 +530,6 @@ module "ara_fast_api_state_bucket" { bucket_name = "ara-fast-api-terraform-state" } -module "ara_fastapi_registry" { - source = "../modules/container_registry" - name = "ara-fastapi" - stage = var.stage -} - # S3 policy for FastAPI app to read and write from various S3 buckets module "fast_api_s3_read_and_write" { source = "../modules/s3_iam_policy" From 597fb2e764abe7814e705efa105ce1e9d6fd96b5 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 12 Mar 2026 10:47:27 +0000 Subject: [PATCH 39/77] modify key to match tfstate path --- .../terraform/modules/lambda_with_api_gateway/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf index 2277dee5..f72729e4 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf @@ -21,7 +21,7 @@ data "archive_file" "this" { ############################################ resource "aws_s3_object" "lambda_zip" { bucket = var.artifact_bucket - key = "${var.name}-${var.stage}.zip" + key = "env:/${var.stage}/${var.name}.zip" source = data.archive_file.this.output_path etag = data.archive_file.this.output_md5 } From 5f948da2ec3d6facfc7d66d9fe84c94744c01b2c Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 12 Mar 2026 10:59:30 +0000 Subject: [PATCH 40/77] delete duplicate variable in lambda_service_with_zip --- .../terraform/modules/lambda_service_zip/variables.tf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/infrastructure/terraform/modules/lambda_service_zip/variables.tf b/infrastructure/terraform/modules/lambda_service_zip/variables.tf index 85d1f548..095d4a81 100644 --- a/infrastructure/terraform/modules/lambda_service_zip/variables.tf +++ b/infrastructure/terraform/modules/lambda_service_zip/variables.tf @@ -17,5 +17,4 @@ variable "environment" { default = {} } variable "s3_bucket" { type = string } -variable "s3_key" { type = string } -variable "source_code_hash" { type = string } \ No newline at end of file +variable "s3_key" { type = string } \ No newline at end of file From db9f6c1616352162b01353b5ef0dd81389a02edc Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 12 Mar 2026 11:08:27 +0000 Subject: [PATCH 41/77] remove duplicate variable from main.tf --- infrastructure/terraform/modules/lambda_service_zip/main.tf | 1 - 1 file changed, 1 deletion(-) diff --git a/infrastructure/terraform/modules/lambda_service_zip/main.tf b/infrastructure/terraform/modules/lambda_service_zip/main.tf index 232b5b56..d52f5ba4 100644 --- a/infrastructure/terraform/modules/lambda_service_zip/main.tf +++ b/infrastructure/terraform/modules/lambda_service_zip/main.tf @@ -12,7 +12,6 @@ resource "aws_lambda_function" "this" { s3_bucket = var.s3_bucket s3_key = var.s3_key - source_code_hash = var.source_code_hash environment { variables = var.environment From 604dea8a2af23e44270544f6a932995469af15fc Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 12 Mar 2026 11:25:38 +0000 Subject: [PATCH 42/77] output tfstate bucket name --- infrastructure/terraform/shared/main.tf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/infrastructure/terraform/shared/main.tf b/infrastructure/terraform/shared/main.tf index 8a4e4a1f..0a6dfe3f 100644 --- a/infrastructure/terraform/shared/main.tf +++ b/infrastructure/terraform/shared/main.tf @@ -530,6 +530,10 @@ module "ara_fast_api_state_bucket" { bucket_name = "ara-fast-api-terraform-state" } +output "ara_fast_api_state_bucket" { + value = module.ara_fast_api_state_bucket.bucket_name +} + # S3 policy for FastAPI app to read and write from various S3 buckets module "fast_api_s3_read_and_write" { source = "../modules/s3_iam_policy" From b55b601c15627e76a85be47214fdf6b28c0adcf6 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 12 Mar 2026 11:45:25 +0000 Subject: [PATCH 43/77] ignore infrastucture file when zipping python --- .../modules/lambda_with_api_gateway/variables.tf | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf index 0b1dfe71..d0d2b933 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/variables.tf @@ -6,7 +6,13 @@ variable "runtime" { type = string } variable "zip_excludes" { type = list(string) - default = ["**/__pycache__/**", "**/*.pyc", "**/.pytest_cache/**", "**/tests/**"] + default = [ + "**/__pycache__/**", + "**/*.pyc", + "**/.pytest_cache/**", + "**/tests/**", + "**/infrastructure/**" + ] } variable "timeout" { From 93915efd9d6e0f65cb624c515586c8d5af4a911b Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 12 Mar 2026 12:02:55 +0000 Subject: [PATCH 44/77] ensure pip install runs before zip by moving the install command to lambda_with_api_gateway --- .../terraform/lambda/fast-api/main.tf | 22 +------------------ .../modules/lambda_with_api_gateway/main.tf | 13 +++++++++++ .../lambda_with_api_gateway/variables.tf | 7 +++++- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index 9e8c7c2b..4bf88720 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -47,27 +47,6 @@ locals { # name = var.domain_name # } -############################################ -# Install Python requirements -############################################ -resource "null_resource" "pip_install" { - triggers = { - requirements_hash = filemd5("${path.root}/../../../../backend/app/requirements/requirements.txt") - } - - provisioner "local-exec" { - command = < Date: Thu, 12 Mar 2026 12:26:04 +0000 Subject: [PATCH 45/77] move the depends_on pip install to the correct place --- infrastructure/terraform/lambda/fast-api/main.tf | 1 - .../terraform/modules/lambda_with_api_gateway/main.tf | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index 4bf88720..c4769f41 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -51,7 +51,6 @@ locals { # FastAPI Lambda + API Gateway ############################################ module "fastapi" { - depends_on = [null_resource.pip_install] source = "../../modules/lambda_with_api_gateway" name = "fastapi" diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf index 92c7c7f5..8af51420 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf @@ -10,6 +10,8 @@ module "role" { # Install python packages ############################################ resource "null_resource" "pip_install" { + count = var.requirements_file != null ? 1 : 0 + triggers = { requirements_hash = filemd5("${var.requirements_file}") } @@ -23,6 +25,7 @@ resource "null_resource" "pip_install" { # Zip the source code ############################################ data "archive_file" "this" { + depends_on = [null_resource.pip_install] type = "zip" source_dir = var.source_dir output_path = "${path.module}/lambda_package.zip" From 7835a7e98278a24a656181d9dc792e501d5cf707 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 12 Mar 2026 13:21:27 +0000 Subject: [PATCH 46/77] correctly attach sqs policy to fastapi app --- infrastructure/terraform/lambda/fast-api/main.tf | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index c4769f41..1d5224ea 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -98,6 +98,7 @@ module "fastapi" { ############################################ # IAM policy attachments ############################################ +# SQS module "fastapi_sqs_policy" { source = "../../modules/general_iam_policy" @@ -121,8 +122,13 @@ module "fastapi_sqs_policy" { } } +resource "aws_iam_role_policy_attachment" "fastapi_sqs_send" { + role = module.fastapi.role_name + policy_arn = module.fastapi_sqs_policy.policy_arn +} -resource "aws_iam_role_policy_attachment" "fastapi_sqs_read_and_write" { +# S3 +resource "aws_iam_role_policy_attachment" "fastapi_s3_read_and_write" { role = module.fastapi.role_name policy_arn = data.terraform_remote_state.shared.outputs.fast_api_s3_read_and_write_arn } \ No newline at end of file From 2d5357ed7a1964d55a18f66bad6784201cf4c44a Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 12 Mar 2026 13:51:04 +0000 Subject: [PATCH 47/77] ensure pip install takes place every run --- .../terraform/modules/lambda_with_api_gateway/main.tf | 4 ---- 1 file changed, 4 deletions(-) diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf index 8af51420..f33f8d5b 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf @@ -12,10 +12,6 @@ module "role" { resource "null_resource" "pip_install" { count = var.requirements_file != null ? 1 : 0 - triggers = { - requirements_hash = filemd5("${var.requirements_file}") - } - provisioner "local-exec" { command = "pip install -r ${var.requirements_file} -t ${var.source_dir} --platform manylinux2014_x86_64 --implementation cp --python-version 3.11 --only-binary=:all: --upgrade" } From 83e502a361768816c5aaa4a0123341af3ea43d71 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 12 Mar 2026 14:47:28 +0000 Subject: [PATCH 48/77] from domain_name from engine environment --- .github/workflows/_deploy_lambda.yml | 4 ++-- .github/workflows/deploy_terraform.yml | 1 - infrastructure/terraform/lambda/engine/variables.tf | 4 ---- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/_deploy_lambda.yml b/.github/workflows/_deploy_lambda.yml index 3cef705e..dab98d8b 100644 --- a/.github/workflows/_deploy_lambda.yml +++ b/.github/workflows/_deploy_lambda.yml @@ -119,7 +119,7 @@ jobs: TF_VAR_domain_name: ${{ secrets.TF_VAR_domain_name }} TF_VAR_epc_auth_token: ${{ secrets.TF_VAR_epc_auth_token }} TF_VAR_google_solar_api_key: ${{ secrets.TF_VAR_google_solar_api_key }} - TF_VAR_ordnance_survey_api_key: ${{ secrets.TF_VAR_ordnance_survey_api_key}} + TF_VAR_ordnance_survey_api_key: ${{ secrets.TF_VAR_ordnance_survey_api_key }} run: | ECR_REPO_URL_VAR="" if [[ -n "${{ inputs.ecr_repo }}" ]]; then @@ -155,7 +155,7 @@ jobs: TF_VAR_domain_name: ${{ secrets.TF_VAR_domain_name }} TF_VAR_epc_auth_token: ${{ secrets.TF_VAR_epc_auth_token }} TF_VAR_google_solar_api_key: ${{ secrets.TF_VAR_google_solar_api_key }} - TF_VAR_ordnance_survey_api_key: ${{ secrets.TF_VAR_ordnance_survey_api_key}} + TF_VAR_ordnance_survey_api_key: ${{ secrets.TF_VAR_ordnance_survey_api_key }} run: | EXTRA_VARS="" if [[ -n "${{ inputs.ecr_repo }}" ]]; then diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index ef927ccb..c2ecc399 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -115,7 +115,6 @@ jobs: TF_VAR_db_port: ${{ secrets.DEV_DB_PORT }} TF_VAR_api_key: ${{ secrets.DEV_API_KEY }} TF_VAR_secret_key: ${{ secrets.DEV_SECRET_KEY }} - TF_VAR_domain_name: ${{ secrets.DEV_DOMAIN_NAME }} TF_VAR_epc_auth_token: ${{ secrets.DEV_EPC_AUTH_TOKEN }} TF_VAR_google_solar_api_key: ${{ secrets.DEV_GOOGLE_SOLAR_API_KEY }} diff --git a/infrastructure/terraform/lambda/engine/variables.tf b/infrastructure/terraform/lambda/engine/variables.tf index 0a74ad5b..bf0a42a2 100644 --- a/infrastructure/terraform/lambda/engine/variables.tf +++ b/infrastructure/terraform/lambda/engine/variables.tf @@ -65,10 +65,6 @@ variable "secret_key" { sensitive = true } -variable "domain_name" { - type = string -} - variable "epc_auth_token" { type = string sensitive = true From 3cadecfe9b9198eac9e01fd530370e6ce6e4f8a2 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 12 Mar 2026 14:47:55 +0000 Subject: [PATCH 49/77] from domain_name from engine environment --- infrastructure/terraform/lambda/engine/main.tf | 1 - 1 file changed, 1 deletion(-) diff --git a/infrastructure/terraform/lambda/engine/main.tf b/infrastructure/terraform/lambda/engine/main.tf index 3f28933b..1f3ce017 100644 --- a/infrastructure/terraform/lambda/engine/main.tf +++ b/infrastructure/terraform/lambda/engine/main.tf @@ -44,7 +44,6 @@ module "lambda" { DB_PORT = var.db_port API_KEY = var.api_key SECRET_KEY = var.secret_key - DOMAIN_NAME = var.domain_name EPC_AUTH_TOKEN = var.epc_auth_token GOOGLE_SOLAR_API_KEY = var.google_solar_api_key From 49e544d10fce938271f8cb8a4415d97c245f9969 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 12 Mar 2026 17:29:47 +0000 Subject: [PATCH 50/77] add cloudfront for domain setting --- .../terraform/lambda/engine/variables.tf | 4 + .../terraform/lambda/fast-api/main.tf | 9 ++ .../terraform/lambda/fast-api/variables.tf | 6 +- .../terraform/modules/cloudfront-api/main.tf | 82 +++++++++++++++++++ .../modules/cloudfront-api/outputs.tf | 7 ++ .../modules/cloudfront-api/variables.tf | 9 ++ .../lambda_with_api_gateway/outputs.tf | 8 ++ 7 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 infrastructure/terraform/modules/cloudfront-api/main.tf create mode 100644 infrastructure/terraform/modules/cloudfront-api/outputs.tf create mode 100644 infrastructure/terraform/modules/cloudfront-api/variables.tf diff --git a/infrastructure/terraform/lambda/engine/variables.tf b/infrastructure/terraform/lambda/engine/variables.tf index bf0a42a2..585bce2a 100644 --- a/infrastructure/terraform/lambda/engine/variables.tf +++ b/infrastructure/terraform/lambda/engine/variables.tf @@ -81,4 +81,8 @@ locals { output "resolved_image_uri" { value = local.image_uri +} + +variable "domain_name" { + description = "Full domain name for API" } \ No newline at end of file diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index 1d5224ea..ad22a9b6 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -63,6 +63,8 @@ module "fastapi" { artifact_bucket = data.terraform_remote_state.shared.outputs.ara_fast_api_state_bucket requirements_file = "${path.root}/../../../../backend/app/requirements/requirements.txt" + domain_name = var.domain_name + # domain_name = "api.${var.domain_name}" # certificate_arn = data.aws_ssm_parameter.certificate_arn.value # route53_zone_id = data.aws_route53_zone.this.zone_id @@ -131,4 +133,11 @@ resource "aws_iam_role_policy_attachment" "fastapi_sqs_send" { resource "aws_iam_role_policy_attachment" "fastapi_s3_read_and_write" { role = module.fastapi.role_name policy_arn = data.terraform_remote_state.shared.outputs.fast_api_s3_read_and_write_arn +} + +module "fastapi_cdn" { + source = "../../modules/cloudfront-api" + + domain_name = var.domain_name + api_domain_name = module.fastapi.api_endpoint } \ No newline at end of file diff --git a/infrastructure/terraform/lambda/fast-api/variables.tf b/infrastructure/terraform/lambda/fast-api/variables.tf index d329e0ca..a3157590 100644 --- a/infrastructure/terraform/lambda/fast-api/variables.tf +++ b/infrastructure/terraform/lambda/fast-api/variables.tf @@ -29,9 +29,9 @@ variable "secret_key" { sensitive = true } -# variable "domain_name" { -# type = string -# } +variable "domain_name" { + type = string +} variable "epc_auth_token" { type = string diff --git a/infrastructure/terraform/modules/cloudfront-api/main.tf b/infrastructure/terraform/modules/cloudfront-api/main.tf new file mode 100644 index 00000000..00139de3 --- /dev/null +++ b/infrastructure/terraform/modules/cloudfront-api/main.tf @@ -0,0 +1,82 @@ +############################################ +# ACM certificate +############################################ +resource "aws_acm_certificate" "this" { + domain_name = var.domain_name + validation_method = "DNS" + + lifecycle { + create_before_destroy = true + } +} + +############################################ +# CloudFront distribution +############################################ +resource "aws_cloudfront_distribution" "this" { + + enabled = true + + aliases = [var.domain_name] + + origin { + domain_name = var.api_domain_name + origin_id = "api-gateway" + + custom_origin_config { + http_port = 80 + https_port = 443 + origin_protocol_policy = "https-only" + origin_ssl_protocols = ["TLSv1.2"] + } + } + + default_cache_behavior { + + target_origin_id = "api-gateway" + + viewer_protocol_policy = "redirect-to-https" + compress = true + + allowed_methods = [ + "GET", + "HEAD", + "OPTIONS", + "PUT", + "POST", + "PATCH", + "DELETE" + ] + + cached_methods = [ + "GET", + "HEAD" + ] + + forwarded_values { + query_string = true + headers = ["*"] + + cookies { + forward = "all" + } + } + + min_ttl = 0 + default_ttl = 0 + max_ttl = 0 + } + + price_class = "PriceClass_100" + + restrictions { + geo_restriction { + restriction_type = "none" + } + } + + viewer_certificate { + acm_certificate_arn = aws_acm_certificate.this.arn + ssl_support_method = "sni-only" + } +} \ No newline at end of file diff --git a/infrastructure/terraform/modules/cloudfront-api/outputs.tf b/infrastructure/terraform/modules/cloudfront-api/outputs.tf new file mode 100644 index 00000000..f7c7e907 --- /dev/null +++ b/infrastructure/terraform/modules/cloudfront-api/outputs.tf @@ -0,0 +1,7 @@ +output "cloudfront_domain_name" { + value = aws_cloudfront_distribution.this.domain_name +} + +output "certificate_validation_records" { + value = aws_acm_certificate.this.domain_validation_options +} \ No newline at end of file diff --git a/infrastructure/terraform/modules/cloudfront-api/variables.tf b/infrastructure/terraform/modules/cloudfront-api/variables.tf new file mode 100644 index 00000000..b058194f --- /dev/null +++ b/infrastructure/terraform/modules/cloudfront-api/variables.tf @@ -0,0 +1,9 @@ +variable "domain_name" { + description = "Public domain name for the API (e.g. api.dev.domna.homes)" + type = string +} + +variable "api_domain_name" { + description = "API Gateway domain (execute-api)" + type = string +} \ No newline at end of file diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf index 9ced7c8b..2d7af141 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf @@ -6,6 +6,14 @@ output "api_endpoint" { value = aws_apigatewayv2_stage.this.invoke_url } +output "cloudfront_domain" { + value = aws_cloudfront_distribution.api.domain_name +} + +output "certificate_validation_records" { + value = aws_acm_certificate.this.domain_validation_options +} + # output "custom_domain_endpoint" { # value = var.domain_name != null ? "https://${var.domain_name}" : null # } \ No newline at end of file From 6e05bd235250428627d93eab0adbc0269dc96de4 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 12 Mar 2026 17:31:59 +0000 Subject: [PATCH 51/77] delete unused stuff --- .../terraform/lambda/engine/variables.tf | 4 --- .../terraform/lambda/fast-api/main.tf | 6 +--- .../modules/lambda_with_api_gateway/main.tf | 34 ------------------- 3 files changed, 1 insertion(+), 43 deletions(-) diff --git a/infrastructure/terraform/lambda/engine/variables.tf b/infrastructure/terraform/lambda/engine/variables.tf index 585bce2a..bf0a42a2 100644 --- a/infrastructure/terraform/lambda/engine/variables.tf +++ b/infrastructure/terraform/lambda/engine/variables.tf @@ -81,8 +81,4 @@ locals { output "resolved_image_uri" { value = local.image_uri -} - -variable "domain_name" { - description = "Full domain name for API" } \ No newline at end of file diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index ad22a9b6..84880188 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -63,11 +63,7 @@ module "fastapi" { artifact_bucket = data.terraform_remote_state.shared.outputs.ara_fast_api_state_bucket requirements_file = "${path.root}/../../../../backend/app/requirements/requirements.txt" - domain_name = var.domain_name - - # domain_name = "api.${var.domain_name}" - # certificate_arn = data.aws_ssm_parameter.certificate_arn.value - # route53_zone_id = data.aws_route53_zone.this.zone_id + domain_name = "api.${var.domain_name}" environment = { ENVIRONMENT = var.stage diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf index f33f8d5b..b1ee3b75 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf @@ -90,37 +90,3 @@ resource "aws_lambda_permission" "apigw_invoke" { principal = "apigateway.amazonaws.com" source_arn = "${aws_apigatewayv2_api.this.execution_arn}/*/*" } - -############################################ -# Custom domain -############################################ -# resource "aws_apigatewayv2_domain_name" "this" { -# count = var.domain_name != null ? 1 : 0 -# domain_name = var.domain_name - -# domain_name_configuration { -# certificate_arn = var.certificate_arn -# endpoint_type = "REGIONAL" -# security_policy = "TLS_1_2" -# } -# } - -# resource "aws_apigatewayv2_api_mapping" "this" { -# count = var.domain_name != null ? 1 : 0 -# api_id = aws_apigatewayv2_api.this.id -# domain_name = aws_apigatewayv2_domain_name.this[0].id -# stage = aws_apigatewayv2_stage.this.id -# } - -# resource "aws_route53_record" "this" { -# count = var.domain_name != null ? 1 : 0 -# name = aws_apigatewayv2_domain_name.this[0].domain_name -# type = "A" -# zone_id = var.route53_zone_id - -# alias { -# name = aws_apigatewayv2_domain_name.this[0].domain_name_configuration[0].target_domain_name -# zone_id = aws_apigatewayv2_domain_name.this[0].domain_name_configuration[0].hosted_zone_id -# evaluate_target_health = false -# } -# } \ No newline at end of file From aac9b784f38972771d39e92327998672f87aebed Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Thu, 12 Mar 2026 17:37:12 +0000 Subject: [PATCH 52/77] use new domain from github secrets --- .github/workflows/deploy_terraform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index c2ecc399..98fd5324 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -342,7 +342,7 @@ jobs: TF_VAR_db_port: ${{ secrets.DEV_DB_PORT }} TF_VAR_api_key: ${{ secrets.FASTAPI_API_KEY }} TF_VAR_secret_key: ${{ secrets.NEXTAUTH_SECRET }} - TF_VAR_domain_name: ${{ secrets.DEV_DOMAIN_NAME }} + TF_VAR_domain_name: ${{ secrets.ARA_DEV_DOMAIN_NAME }} TF_VAR_epc_auth_token: ${{ secrets.DEV_EPC_AUTH_TOKEN }} TF_VAR_google_solar_api_key: ${{ secrets.DEV_GOOGLE_SOLAR_API_KEY }} From b7bee7486c5611b5b95bafd702018569f88976a5 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 13 Mar 2026 09:51:48 +0000 Subject: [PATCH 53/77] deploy cdn as its own job, depending on fastapi --- .github/workflows/deploy_terraform.yml | 17 +++ infrastructure/terraform/cdn/main.tf | 54 ++++++++ infrastructure/terraform/cdn/outputs.tf | 3 + infrastructure/terraform/cdn/variables.tf | 7 + .../terraform/lambda/fast-api/main.tf | 7 - .../terraform/modules/cloudfront-api/main.tf | 82 ----------- .../modules/cloudfront-api/outputs.tf | 7 - .../modules/cloudfront-api/variables.tf | 9 -- .../terraform/modules/cloudfront/main.tf | 129 ++++++++++++++---- .../terraform/modules/cloudfront/variables.tf | 33 ++--- infrastructure/terraform/shared/main.tf | 28 ++-- 11 files changed, 216 insertions(+), 160 deletions(-) create mode 100644 infrastructure/terraform/cdn/main.tf create mode 100644 infrastructure/terraform/cdn/outputs.tf create mode 100644 infrastructure/terraform/cdn/variables.tf delete mode 100644 infrastructure/terraform/modules/cloudfront-api/main.tf delete mode 100644 infrastructure/terraform/modules/cloudfront-api/outputs.tf delete mode 100644 infrastructure/terraform/modules/cloudfront-api/variables.tf diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index 98fd5324..8e2c484d 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -346,3 +346,20 @@ jobs: TF_VAR_epc_auth_token: ${{ secrets.DEV_EPC_AUTH_TOKEN }} TF_VAR_google_solar_api_key: ${{ secrets.DEV_GOOGLE_SOLAR_API_KEY }} + # ============================================================ + # Deploy Cloudfront CDN + # ============================================================ + cloudfront_cdn: + needs: [determine_stage, fast_api_lambda] + uses: ./.github/workflows/_deploy_lambda.yml + with: + lambda_name: ara_cdn + lambda_path: infrastructure/terraform/cdn + stage: ${{ needs.determine_stage.outputs.stage }} + terraform_apply: ${{ needs.determine_stage.outputs.terraform_apply }} + secrets: + AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.DEV_AWS_REGION }} + TF_VAR_domain_name: ${{ secrets.ARA_DEV_DOMAIN_NAME }} + diff --git a/infrastructure/terraform/cdn/main.tf b/infrastructure/terraform/cdn/main.tf new file mode 100644 index 00000000..29abe6e4 --- /dev/null +++ b/infrastructure/terraform/cdn/main.tf @@ -0,0 +1,54 @@ +############################################ +# Load Shared Terraform State +############################################ +data "terraform_remote_state" "shared" { + backend = "s3" + config = { + bucket = "assessment-model-terraform-state" + key = "env:/${var.stage}/terraform.tfstate" + region = "eu-west-2" + } +} + +############################################ +# Load FastAPI Terraform State +############################################ +data "terraform_remote_state" "fast_api" { + backend = "s3" + config = { + bucket = "assessment-model-terraform-state" + key = "env:/${var.stage}/terraform.tfstate" + region = "eu-west-2" + } +} + +############################################ +# CloudFront for API +############################################ +module "cdn" { + source = "../modules/cloudfront" + + aliases = ["domna.homes", "api.dev.domna.homes"] + + origins = [ + # ---- S3 ---- + { + origin_type = "s3" + origin_domain_name = data.terraform_remote_state.shared.outputs.retrofit_datalake_bucket_domain_name + origin_id = "s3-origin" + bucket_id = data.terraform_remote_state.shared.outputs.retrofit_datalake_bucket_id + bucket_arn = data.terraform_remote_state.shared.outputs.retrofit_datalake_bucket_arn + }, + + # ---- API Gateway ---- + { + origin_type = "api" + origin_domain_name = replace( + data.terraform_remote_state.fast_api.outputs.api_endpoint, + "https://", + "" + ) + origin_id = "api-origin" + } + ] +} \ No newline at end of file diff --git a/infrastructure/terraform/cdn/outputs.tf b/infrastructure/terraform/cdn/outputs.tf new file mode 100644 index 00000000..7c684377 --- /dev/null +++ b/infrastructure/terraform/cdn/outputs.tf @@ -0,0 +1,3 @@ +output "cloudfront_domain_name" { + value = module.api_cdn.cloudfront_domain_name +} \ No newline at end of file diff --git a/infrastructure/terraform/cdn/variables.tf b/infrastructure/terraform/cdn/variables.tf new file mode 100644 index 00000000..6fe0073b --- /dev/null +++ b/infrastructure/terraform/cdn/variables.tf @@ -0,0 +1,7 @@ +variable "stage" { + type = string +} + +variable "domain_name" { + type = string +} \ No newline at end of file diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index 84880188..5e8d2b3b 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -129,11 +129,4 @@ resource "aws_iam_role_policy_attachment" "fastapi_sqs_send" { resource "aws_iam_role_policy_attachment" "fastapi_s3_read_and_write" { role = module.fastapi.role_name policy_arn = data.terraform_remote_state.shared.outputs.fast_api_s3_read_and_write_arn -} - -module "fastapi_cdn" { - source = "../../modules/cloudfront-api" - - domain_name = var.domain_name - api_domain_name = module.fastapi.api_endpoint } \ No newline at end of file diff --git a/infrastructure/terraform/modules/cloudfront-api/main.tf b/infrastructure/terraform/modules/cloudfront-api/main.tf deleted file mode 100644 index 00139de3..00000000 --- a/infrastructure/terraform/modules/cloudfront-api/main.tf +++ /dev/null @@ -1,82 +0,0 @@ -############################################ -# ACM certificate -############################################ -resource "aws_acm_certificate" "this" { - domain_name = var.domain_name - validation_method = "DNS" - - lifecycle { - create_before_destroy = true - } -} - -############################################ -# CloudFront distribution -############################################ -resource "aws_cloudfront_distribution" "this" { - - enabled = true - - aliases = [var.domain_name] - - origin { - domain_name = var.api_domain_name - origin_id = "api-gateway" - - custom_origin_config { - http_port = 80 - https_port = 443 - origin_protocol_policy = "https-only" - origin_ssl_protocols = ["TLSv1.2"] - } - } - - default_cache_behavior { - - target_origin_id = "api-gateway" - - viewer_protocol_policy = "redirect-to-https" - compress = true - - allowed_methods = [ - "GET", - "HEAD", - "OPTIONS", - "PUT", - "POST", - "PATCH", - "DELETE" - ] - - cached_methods = [ - "GET", - "HEAD" - ] - - forwarded_values { - query_string = true - headers = ["*"] - - cookies { - forward = "all" - } - } - - min_ttl = 0 - default_ttl = 0 - max_ttl = 0 - } - - price_class = "PriceClass_100" - - restrictions { - geo_restriction { - restriction_type = "none" - } - } - - viewer_certificate { - acm_certificate_arn = aws_acm_certificate.this.arn - ssl_support_method = "sni-only" - } -} \ No newline at end of file diff --git a/infrastructure/terraform/modules/cloudfront-api/outputs.tf b/infrastructure/terraform/modules/cloudfront-api/outputs.tf deleted file mode 100644 index f7c7e907..00000000 --- a/infrastructure/terraform/modules/cloudfront-api/outputs.tf +++ /dev/null @@ -1,7 +0,0 @@ -output "cloudfront_domain_name" { - value = aws_cloudfront_distribution.this.domain_name -} - -output "certificate_validation_records" { - value = aws_acm_certificate.this.domain_validation_options -} \ No newline at end of file diff --git a/infrastructure/terraform/modules/cloudfront-api/variables.tf b/infrastructure/terraform/modules/cloudfront-api/variables.tf deleted file mode 100644 index b058194f..00000000 --- a/infrastructure/terraform/modules/cloudfront-api/variables.tf +++ /dev/null @@ -1,9 +0,0 @@ -variable "domain_name" { - description = "Public domain name for the API (e.g. api.dev.domna.homes)" - type = string -} - -variable "api_domain_name" { - description = "API Gateway domain (execute-api)" - type = string -} \ No newline at end of file diff --git a/infrastructure/terraform/modules/cloudfront/main.tf b/infrastructure/terraform/modules/cloudfront/main.tf index 281ff09f..6fa1331e 100644 --- a/infrastructure/terraform/modules/cloudfront/main.tf +++ b/infrastructure/terraform/modules/cloudfront/main.tf @@ -1,65 +1,146 @@ -resource "aws_cloudfront_distribution" "s3_distribution" { - origin { - domain_name = var.bucket_domain_name - origin_id = "S3-${var.bucket_name}" +############################################ +# CloudFront Distribution +############################################ - s3_origin_config { - origin_access_identity = aws_cloudfront_origin_access_identity.oai.cloudfront_access_identity_path +resource "aws_cloudfront_distribution" "this" { + + ########################################## + # Origins + ########################################## + + dynamic "origin" { + for_each = { for o in var.origins : o.origin_id => o } + + content { + domain_name = origin.value.origin_domain_name + origin_id = origin.value.origin_id + + ###################################### + # S3 Origin + ###################################### + dynamic "s3_origin_config" { + for_each = origin.value.origin_type == "s3" ? [1] : [] + + content { + origin_access_identity = + aws_cloudfront_origin_access_identity.oai[origin.key] + .cloudfront_access_identity_path + } + } + + ###################################### + # API Gateway Origin + ###################################### + dynamic "custom_origin_config" { + for_each = origin.value.origin_type == "api" ? [1] : [] + + content { + http_port = 80 + https_port = 443 + origin_protocol_policy = "https-only" + origin_ssl_protocols = ["TLSv1.2"] + } + } } } enabled = true + aliases = var.aliases + + ########################################## + # Default Cache Behavior + ########################################## default_cache_behavior { - allowed_methods = ["GET", "HEAD"] - cached_methods = ["GET", "HEAD"] - target_origin_id = "S3-${var.bucket_name}" + target_origin_id = var.origins[0].origin_id + viewer_protocol_policy = "redirect-to-https" - compress = true + + allowed_methods = [ + "GET", + "HEAD" + ] + + cached_methods = [ + "GET", + "HEAD" + ] forwarded_values { - query_string = false + query_string = true + headers = ["*"] + cookies { - forward = "none" + forward = "all" } } - min_ttl = 0 - default_ttl = 86400 - max_ttl = 31536000 + compress = true + min_ttl = 0 + default_ttl = 3600 + max_ttl = 86400 } price_class = "PriceClass_All" + ########################################## + # Geo Restrictions + ########################################## + restrictions { geo_restriction { restriction_type = "none" } } + ########################################## + # SSL Certificate + ########################################## + viewer_certificate { - cloudfront_default_certificate = true + acm_certificate_arn = var.acm_certificate_arn + ssl_support_method = "sni-only" + cloudfront_default_certificate = var.acm_certificate_arn == null } } +############################################ +# Origin Access Identities (S3 only) +############################################ + resource "aws_cloudfront_origin_access_identity" "oai" { - comment = "OAI for ${var.bucket_name}" + for_each = { + for o in var.origins : o.origin_id => o + if o.origin_type == "s3" + } + + comment = "OAI for ${each.key}" } +############################################ +# S3 Bucket Policy (S3 only) +############################################ + resource "aws_s3_bucket_policy" "bucket_policy" { - bucket = var.bucket_id + for_each = { + for o in var.origins : o.origin_id => o + if o.origin_type == "s3" + } + + bucket = each.value.bucket_id policy = jsonencode({ - Version = "2012-10-17" + Version = "2012-10-17" Statement = [ { - Effect = "Allow" + Effect = "Allow" Principal = { - AWS = "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity ${aws_cloudfront_origin_access_identity.oai.id}" + AWS = aws_cloudfront_origin_access_identity.oai[each.key] + .iam_arn } Action = "s3:GetObject" - Resource = "${var.bucket_arn}/*" - }, + Resource = "${each.value.bucket_arn}/*" + } ] }) -} +} \ No newline at end of file diff --git a/infrastructure/terraform/modules/cloudfront/variables.tf b/infrastructure/terraform/modules/cloudfront/variables.tf index 88f770a8..92ba2347 100644 --- a/infrastructure/terraform/modules/cloudfront/variables.tf +++ b/infrastructure/terraform/modules/cloudfront/variables.tf @@ -1,24 +1,19 @@ -variable "bucket_name" { - description = "The name of the bucket" - type = string +variable "origins" { + type = list(object({ + origin_type = string # "s3" or "api" + origin_domain_name = string + origin_id = string + + bucket_id = optional(string) + bucket_arn = optional(string) + })) } -variable "stage" { - description = "The deployment stage" - type = string +variable "aliases" { + type = list(string) } -variable "bucket_id" { - description = "The ID of the S3 bucket" - type = string -} - -variable "bucket_arn" { - description = "The ARN of the S3 bucket" - type = string -} - -variable "bucket_domain_name" { - description = "The regional domain name of the S3 bucket" - type = string +variable "acm_certificate_arn" { + type = string + default = null } \ No newline at end of file diff --git a/infrastructure/terraform/shared/main.tf b/infrastructure/terraform/shared/main.tf index 0a6dfe3f..1de2031f 100644 --- a/infrastructure/terraform/shared/main.tf +++ b/infrastructure/terraform/shared/main.tf @@ -127,6 +127,22 @@ module "s3" { allowed_origins = var.allowed_origins } +output "retrofit_datalake_bucket_id" { + value = module.s3.bucket_id +} + +output "retrofit_datalake_bucket_arn" { + value = module.s3.bucket_arn +} + +output "retrofit_datalake_bucket_domain_name" { + value = module.s3.bucket_domain_name +} + +output "retrofit_datalake_bucket_name" { + value = module.s3.bucket_name +} + module "model_directory" { source = "../modules/s3" bucketname = "retrofit-model-directory-${var.stage}" @@ -311,18 +327,6 @@ module "sap_baseline_ecr" { source = "../modules/ecr" } -############################################## -# CDN - Cloudfront -############################################## -module "cloudfront_distribution" { - source = "../modules/cloudfront" - bucket_name = module.s3.bucket_name - bucket_id = module.s3.bucket_id - bucket_arn = module.s3.bucket_arn - bucket_domain_name = module.s3.bucket_domain_name - stage = var.stage -} - ################################################ # SES - Email sending ################################################ From 33406ff67881d6f406d1c01eb3b2977067abdff6 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 13 Mar 2026 09:53:40 +0000 Subject: [PATCH 54/77] cdn depends on shared --- .github/workflows/deploy_terraform.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index 8e2c484d..506f11f6 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -350,7 +350,7 @@ jobs: # Deploy Cloudfront CDN # ============================================================ cloudfront_cdn: - needs: [determine_stage, fast_api_lambda] + needs: [determine_stage, shared_terraform, fast_api_lambda] uses: ./.github/workflows/_deploy_lambda.yml with: lambda_name: ara_cdn From ddcfa75a03e4283bb51b324c8aeede06ea3874dd Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 13 Mar 2026 11:16:41 +0000 Subject: [PATCH 55/77] address PR --- .github/workflows/deploy_terraform.yml | 1 - infrastructure/terraform/cdn/main.tf | 3 ++- infrastructure/terraform/cdn/variables.tf | 4 ---- infrastructure/terraform/lambda/fast-api/main.tf | 8 -------- infrastructure/terraform/lambda/fast-api/outputs.tf | 7 +++++++ infrastructure/terraform/modules/cloudfront/main.tf | 4 +--- infrastructure/terraform/modules/cloudfront/variables.tf | 7 +------ 7 files changed, 11 insertions(+), 23 deletions(-) create mode 100644 infrastructure/terraform/lambda/fast-api/outputs.tf diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index 506f11f6..1d84505b 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -361,5 +361,4 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} AWS_REGION: ${{ secrets.DEV_AWS_REGION }} - TF_VAR_domain_name: ${{ secrets.ARA_DEV_DOMAIN_NAME }} diff --git a/infrastructure/terraform/cdn/main.tf b/infrastructure/terraform/cdn/main.tf index 29abe6e4..daa3b0f1 100644 --- a/infrastructure/terraform/cdn/main.tf +++ b/infrastructure/terraform/cdn/main.tf @@ -28,7 +28,8 @@ data "terraform_remote_state" "fast_api" { module "cdn" { source = "../modules/cloudfront" - aliases = ["domna.homes", "api.dev.domna.homes"] + # Comment out temporarily just to see what happens + # aliases = [data.terraform_remote_state.fast_api.outputs.domain_name] origins = [ # ---- S3 ---- diff --git a/infrastructure/terraform/cdn/variables.tf b/infrastructure/terraform/cdn/variables.tf index 6fe0073b..423f0b0f 100644 --- a/infrastructure/terraform/cdn/variables.tf +++ b/infrastructure/terraform/cdn/variables.tf @@ -1,7 +1,3 @@ variable "stage" { type = string -} - -variable "domain_name" { - type = string } \ No newline at end of file diff --git a/infrastructure/terraform/lambda/fast-api/main.tf b/infrastructure/terraform/lambda/fast-api/main.tf index 5e8d2b3b..f71b6f60 100644 --- a/infrastructure/terraform/lambda/fast-api/main.tf +++ b/infrastructure/terraform/lambda/fast-api/main.tf @@ -39,14 +39,6 @@ locals { db_credentials = jsondecode(data.aws_secretsmanager_secret_version.db_credentials.secret_string) } -# data "aws_ssm_parameter" "certificate_arn" { -# name = "/ssl_certificate_arn" -# } - -# data "aws_route53_zone" "this" { -# name = var.domain_name -# } - ############################################ # FastAPI Lambda + API Gateway ############################################ diff --git a/infrastructure/terraform/lambda/fast-api/outputs.tf b/infrastructure/terraform/lambda/fast-api/outputs.tf new file mode 100644 index 00000000..d3d9dbaa --- /dev/null +++ b/infrastructure/terraform/lambda/fast-api/outputs.tf @@ -0,0 +1,7 @@ +output "domain_name" { + value = module.fastapi.domain_name +} + +output "api_endpoint" { + value = module.fastapi.api_endpoint +} \ No newline at end of file diff --git a/infrastructure/terraform/modules/cloudfront/main.tf b/infrastructure/terraform/modules/cloudfront/main.tf index 6fa1331e..02b64606 100644 --- a/infrastructure/terraform/modules/cloudfront/main.tf +++ b/infrastructure/terraform/modules/cloudfront/main.tf @@ -98,9 +98,7 @@ resource "aws_cloudfront_distribution" "this" { ########################################## viewer_certificate { - acm_certificate_arn = var.acm_certificate_arn - ssl_support_method = "sni-only" - cloudfront_default_certificate = var.acm_certificate_arn == null + cloudfront_default_certificate = true } } diff --git a/infrastructure/terraform/modules/cloudfront/variables.tf b/infrastructure/terraform/modules/cloudfront/variables.tf index 92ba2347..feff2faa 100644 --- a/infrastructure/terraform/modules/cloudfront/variables.tf +++ b/infrastructure/terraform/modules/cloudfront/variables.tf @@ -3,7 +3,7 @@ variable "origins" { origin_type = string # "s3" or "api" origin_domain_name = string origin_id = string - + bucket_id = optional(string) bucket_arn = optional(string) })) @@ -11,9 +11,4 @@ variable "origins" { variable "aliases" { type = list(string) -} - -variable "acm_certificate_arn" { - type = string - default = null } \ No newline at end of file From b96b71a05a6e22ddb0044f30263bde4f51858aef Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 13 Mar 2026 11:59:59 +0000 Subject: [PATCH 56/77] fix incorrect variables and references --- infrastructure/terraform/cdn/main.tf | 2 +- infrastructure/terraform/cdn/outputs.tf | 3 --- .../modules/lambda_with_api_gateway/outputs.tf | 14 +------------- 3 files changed, 2 insertions(+), 17 deletions(-) delete mode 100644 infrastructure/terraform/cdn/outputs.tf diff --git a/infrastructure/terraform/cdn/main.tf b/infrastructure/terraform/cdn/main.tf index daa3b0f1..839ea28b 100644 --- a/infrastructure/terraform/cdn/main.tf +++ b/infrastructure/terraform/cdn/main.tf @@ -16,7 +16,7 @@ data "terraform_remote_state" "shared" { data "terraform_remote_state" "fast_api" { backend = "s3" config = { - bucket = "assessment-model-terraform-state" + bucket = "ara-fast-api-terraform-state" key = "env:/${var.stage}/terraform.tfstate" region = "eu-west-2" } diff --git a/infrastructure/terraform/cdn/outputs.tf b/infrastructure/terraform/cdn/outputs.tf deleted file mode 100644 index 7c684377..00000000 --- a/infrastructure/terraform/cdn/outputs.tf +++ /dev/null @@ -1,3 +0,0 @@ -output "cloudfront_domain_name" { - value = module.api_cdn.cloudfront_domain_name -} \ No newline at end of file diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf index 2d7af141..c44ddc0d 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf @@ -4,16 +4,4 @@ output "role_name" { output "api_endpoint" { value = aws_apigatewayv2_stage.this.invoke_url -} - -output "cloudfront_domain" { - value = aws_cloudfront_distribution.api.domain_name -} - -output "certificate_validation_records" { - value = aws_acm_certificate.this.domain_validation_options -} - -# output "custom_domain_endpoint" { -# value = var.domain_name != null ? "https://${var.domain_name}" : null -# } \ No newline at end of file +} \ No newline at end of file From 4e6dd546ce8f4aca1c8ad27ce16ecb753fb456a5 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 13 Mar 2026 13:38:11 +0000 Subject: [PATCH 57/77] make sure domain_name is exported from lambda_with_api_gateway for use by fast-api --- .../terraform/modules/lambda_with_api_gateway/outputs.tf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf index c44ddc0d..fad4f66c 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf @@ -4,4 +4,8 @@ output "role_name" { output "api_endpoint" { value = aws_apigatewayv2_stage.this.invoke_url +} + +output "domain_name" { + value = var.domain_name } \ No newline at end of file From 53bbb57a679d46062183e87487841b0411f49946 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 13 Mar 2026 14:01:43 +0000 Subject: [PATCH 58/77] fix incorrect terraform syntax --- infrastructure/terraform/modules/cloudfront/main.tf | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/infrastructure/terraform/modules/cloudfront/main.tf b/infrastructure/terraform/modules/cloudfront/main.tf index 02b64606..1ebe0578 100644 --- a/infrastructure/terraform/modules/cloudfront/main.tf +++ b/infrastructure/terraform/modules/cloudfront/main.tf @@ -22,9 +22,7 @@ resource "aws_cloudfront_distribution" "this" { for_each = origin.value.origin_type == "s3" ? [1] : [] content { - origin_access_identity = - aws_cloudfront_origin_access_identity.oai[origin.key] - .cloudfront_access_identity_path + origin_access_identity = aws_cloudfront_origin_access_identity.oai[origin.key].cloudfront_access_identity_path } } From e78900f0b6ecf3161cdb321cc995f5b5dc2cbd72 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 13 Mar 2026 14:13:14 +0000 Subject: [PATCH 59/77] correct more terraform syntax --- infrastructure/terraform/modules/cloudfront/main.tf | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/infrastructure/terraform/modules/cloudfront/main.tf b/infrastructure/terraform/modules/cloudfront/main.tf index 1ebe0578..d73edadc 100644 --- a/infrastructure/terraform/modules/cloudfront/main.tf +++ b/infrastructure/terraform/modules/cloudfront/main.tf @@ -131,8 +131,7 @@ resource "aws_s3_bucket_policy" "bucket_policy" { { Effect = "Allow" Principal = { - AWS = aws_cloudfront_origin_access_identity.oai[each.key] - .iam_arn + AWS = aws_cloudfront_origin_access_identity.oai[each.key].iam_arn } Action = "s3:GetObject" Resource = "${each.value.bucket_arn}/*" From 3f10af0be5b297109db600a7d200e3ea3605a450 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 13 Mar 2026 14:35:33 +0000 Subject: [PATCH 60/77] dont try to use _deploy_lambda for cdn --- .github/workflows/deploy_terraform.yml | 42 ++++++++++++++++++++------ 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index 1d84505b..ec83479d 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -351,14 +351,36 @@ jobs: # ============================================================ cloudfront_cdn: needs: [determine_stage, shared_terraform, fast_api_lambda] - uses: ./.github/workflows/_deploy_lambda.yml - with: - lambda_name: ara_cdn - lambda_path: infrastructure/terraform/cdn - stage: ${{ needs.determine_stage.outputs.stage }} - terraform_apply: ${{ needs.determine_stage.outputs.terraform_apply }} - secrets: - AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} - AWS_REGION: ${{ secrets.DEV_AWS_REGION }} + runs-on: ubuntu-latest + env: + STAGE: ${{ needs.determine_stage.outputs.stage }} + TERRAFORM_APPLY: ${{ needs.determine_stage.outputs.terraform_apply }} + + steps: + - uses: actions/checkout@v4 + + - uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.DEV_AWS_REGION }} + + - uses: hashicorp/setup-terraform@v3 + + - name: Terraform Init + working-directory: infrastructure/terraform/cdn + run: terraform init -reconfigure + + - name: Terraform Workspace + working-directory: infrastructure/terraform/cdn + run: terraform workspace select ${STAGE} || terraform workspace new ${STAGE} + + - name: Terraform Plan + working-directory: infrastructure/terraform/cdn + run: terraform plan -var-file=${STAGE}.tfvars -out=tfplan + + - name: Terraform Apply + if: env.TERRAFORM_APPLY == 'true' + working-directory: infrastructure/terraform/cdn + run: terraform apply -auto-approve tfplan From 20cc5a3059f5a7b0e29bcb62de08fbbf6cda1376 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 13 Mar 2026 15:00:46 +0000 Subject: [PATCH 61/77] dont try to use shared/.tfvars when deploying cloudfront cdn --- .github/workflows/deploy_terraform.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index ec83479d..501fe30f 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -374,13 +374,18 @@ jobs: - name: Terraform Workspace working-directory: infrastructure/terraform/cdn - run: terraform workspace select ${STAGE} || terraform workspace new ${STAGE} + run: | + terraform workspace select $STAGE \ + || terraform workspace new $STAGE - name: Terraform Plan working-directory: infrastructure/terraform/cdn - run: terraform plan -var-file=${STAGE}.tfvars -out=tfplan + run: | + terraform plan \ + -var="stage=${STAGE}" \ + -out=tfplan - name: Terraform Apply if: env.TERRAFORM_APPLY == 'true' working-directory: infrastructure/terraform/cdn - run: terraform apply -auto-approve tfplan + run: terraform apply -auto-approve tfplan \ No newline at end of file From 1056dda939564430e77cbdd2e517ad071ae0691d Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 13 Mar 2026 15:17:18 +0000 Subject: [PATCH 62/77] set aliases --- infrastructure/terraform/cdn/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/terraform/cdn/main.tf b/infrastructure/terraform/cdn/main.tf index 839ea28b..58824b3a 100644 --- a/infrastructure/terraform/cdn/main.tf +++ b/infrastructure/terraform/cdn/main.tf @@ -29,7 +29,7 @@ module "cdn" { source = "../modules/cloudfront" # Comment out temporarily just to see what happens - # aliases = [data.terraform_remote_state.fast_api.outputs.domain_name] + aliases = [data.terraform_remote_state.fast_api.outputs.domain_name] origins = [ # ---- S3 ---- From 928e417373517dca015b09f30d72adb50df27e8b Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 13 Mar 2026 15:18:35 +0000 Subject: [PATCH 63/77] delete comment --- infrastructure/terraform/cdn/main.tf | 1 - 1 file changed, 1 deletion(-) diff --git a/infrastructure/terraform/cdn/main.tf b/infrastructure/terraform/cdn/main.tf index 58824b3a..d4acb025 100644 --- a/infrastructure/terraform/cdn/main.tf +++ b/infrastructure/terraform/cdn/main.tf @@ -28,7 +28,6 @@ data "terraform_remote_state" "fast_api" { module "cdn" { source = "../modules/cloudfront" - # Comment out temporarily just to see what happens aliases = [data.terraform_remote_state.fast_api.outputs.domain_name] origins = [ From 4921217b78498ab34004de50b0f54c61b2cb29dc Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 13 Mar 2026 15:20:33 +0000 Subject: [PATCH 64/77] empty aliases --- infrastructure/terraform/cdn/main.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/infrastructure/terraform/cdn/main.tf b/infrastructure/terraform/cdn/main.tf index d4acb025..2eca76e7 100644 --- a/infrastructure/terraform/cdn/main.tf +++ b/infrastructure/terraform/cdn/main.tf @@ -28,7 +28,8 @@ data "terraform_remote_state" "fast_api" { module "cdn" { source = "../modules/cloudfront" - aliases = [data.terraform_remote_state.fast_api.outputs.domain_name] + aliases = [] +# aliases = [data.terraform_remote_state.fast_api.outputs.domain_name] origins = [ # ---- S3 ---- From 1e1bd7ead2b8724cec22b682fc12a89dc53c4899 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 13 Mar 2026 15:42:57 +0000 Subject: [PATCH 65/77] use api_endpoint rather than invoke_url in cdn deployment --- infrastructure/terraform/cdn/main.tf | 6 +----- .../terraform/modules/lambda_with_api_gateway/outputs.tf | 8 ++++---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/infrastructure/terraform/cdn/main.tf b/infrastructure/terraform/cdn/main.tf index 2eca76e7..ce1e20e5 100644 --- a/infrastructure/terraform/cdn/main.tf +++ b/infrastructure/terraform/cdn/main.tf @@ -44,11 +44,7 @@ module "cdn" { # ---- API Gateway ---- { origin_type = "api" - origin_domain_name = replace( - data.terraform_remote_state.fast_api.outputs.api_endpoint, - "https://", - "" - ) + origin_domain_name = data.terraform_remote_state.fast_api.outputs.api_endpoint origin_id = "api-origin" } ] diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf index fad4f66c..e3b291a7 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf @@ -2,10 +2,10 @@ output "role_name" { value = module.role.role_name } -output "api_endpoint" { - value = aws_apigatewayv2_stage.this.invoke_url -} - output "domain_name" { value = var.domain_name +} + +output "api_endpoint" { + value = aws_apigatewayv2_stage.this.api_endpoint } \ No newline at end of file From 23df9442c9868789aa9a7cb117eaa1df03de7d23 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 16 Mar 2026 09:39:17 +0000 Subject: [PATCH 66/77] revert to using invoke_url but correct the replace method --- infrastructure/terraform/cdn/main.tf | 2 +- infrastructure/terraform/lambda/fast-api/outputs.tf | 4 ++-- .../terraform/modules/lambda_with_api_gateway/outputs.tf | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/infrastructure/terraform/cdn/main.tf b/infrastructure/terraform/cdn/main.tf index ce1e20e5..7ef58e19 100644 --- a/infrastructure/terraform/cdn/main.tf +++ b/infrastructure/terraform/cdn/main.tf @@ -44,7 +44,7 @@ module "cdn" { # ---- API Gateway ---- { origin_type = "api" - origin_domain_name = data.terraform_remote_state.fast_api.outputs.api_endpoint + origin_domain_name = replace(data.terraform_remote_state.fast_api.outputs.invoke_url, "/^https?://([^/]*).*/", "$1") origin_id = "api-origin" } ] diff --git a/infrastructure/terraform/lambda/fast-api/outputs.tf b/infrastructure/terraform/lambda/fast-api/outputs.tf index d3d9dbaa..c9fc6f86 100644 --- a/infrastructure/terraform/lambda/fast-api/outputs.tf +++ b/infrastructure/terraform/lambda/fast-api/outputs.tf @@ -2,6 +2,6 @@ output "domain_name" { value = module.fastapi.domain_name } -output "api_endpoint" { - value = module.fastapi.api_endpoint +output "invoke_url" { + value = module.fastapi.invoke_url } \ No newline at end of file diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf index e3b291a7..eae0f7d7 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/outputs.tf @@ -6,6 +6,6 @@ output "domain_name" { value = var.domain_name } -output "api_endpoint" { - value = aws_apigatewayv2_stage.this.api_endpoint +output "invoke_url" { + value = aws_apigatewayv2_stage.this.invoke_url } \ No newline at end of file From 976fd0b3767c4701da5c4cccf4d61e8b5a55dea1 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 16 Mar 2026 12:11:15 +0000 Subject: [PATCH 67/77] set caching behaviour based on request origin - api v s3 --- .../terraform/modules/cloudfront/main.tf | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/infrastructure/terraform/modules/cloudfront/main.tf b/infrastructure/terraform/modules/cloudfront/main.tf index d73edadc..1b68b891 100644 --- a/infrastructure/terraform/modules/cloudfront/main.tf +++ b/infrastructure/terraform/modules/cloudfront/main.tf @@ -46,23 +46,47 @@ resource "aws_cloudfront_distribution" "this" { aliases = var.aliases ########################################## - # Default Cache Behavior + # Default Cache Behavior (S3) ########################################## default_cache_behavior { - target_origin_id = var.origins[0].origin_id + target_origin_id = "s3-origin" + + viewer_protocol_policy = "redirect-to-https" + + allowed_methods = ["GET", "HEAD"] + cached_methods = ["GET", "HEAD"] + + forwarded_values { + query_string = false + + cookies { + forward = "none" + } + } + + compress = true + min_ttl = 0 + default_ttl = 3600 + max_ttl = 86400 + } + + ########################################## + # API Behavior + ########################################## + + ordered_cache_behavior { + path_pattern = "/v1/*" + target_origin_id = "api-origin" viewer_protocol_policy = "redirect-to-https" allowed_methods = [ - "GET", - "HEAD" + "GET","HEAD","OPTIONS", + "PUT","POST","PATCH","DELETE" ] - cached_methods = [ - "GET", - "HEAD" - ] + cached_methods = ["GET","HEAD"] forwarded_values { query_string = true @@ -73,10 +97,9 @@ resource "aws_cloudfront_distribution" "this" { } } - compress = true - min_ttl = 0 - default_ttl = 3600 - max_ttl = 86400 + min_ttl = 0 + default_ttl = 0 + max_ttl = 0 } price_class = "PriceClass_All" From ce68470f25195c1d6ed988cc4442e39bfa99bcb4 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 16 Mar 2026 15:11:59 +0000 Subject: [PATCH 68/77] Replace forwarded_values with cache_policy and origin_request_policy for API origin --- .../terraform/modules/cloudfront/main.tf | 56 ++++++++++++++----- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/infrastructure/terraform/modules/cloudfront/main.tf b/infrastructure/terraform/modules/cloudfront/main.tf index 1b68b891..11f086fd 100644 --- a/infrastructure/terraform/modules/cloudfront/main.tf +++ b/infrastructure/terraform/modules/cloudfront/main.tf @@ -1,3 +1,41 @@ +resource "aws_cloudfront_cache_policy" "api" { + name = "api-no-cache" + + default_ttl = 0 + max_ttl = 0 + min_ttl = 0 + + parameters_in_cache_key_and_forwarded_to_origin { + cookies_config { + cookie_behavior = "none" + } + + headers_config { + header_behavior = "none" + } + + query_strings_config { + query_string_behavior = "all" + } + } +} + +resource "aws_cloudfront_origin_request_policy" "api" { + name = "api-forward-all" + + headers_config { + header_behavior = "allViewer" + } + + query_strings_config { + query_string_behavior = "all" + } + + cookies_config { + cookie_behavior = "all" + } +} + ############################################ # CloudFront Distribution ############################################ @@ -81,21 +119,11 @@ resource "aws_cloudfront_distribution" "this" { viewer_protocol_policy = "redirect-to-https" - allowed_methods = [ - "GET","HEAD","OPTIONS", - "PUT","POST","PATCH","DELETE" - ] + allowed_methods = ["GET","HEAD","OPTIONS","PUT","POST","PATCH","DELETE"] + cached_methods = ["GET","HEAD"] - cached_methods = ["GET","HEAD"] - - forwarded_values { - query_string = true - headers = ["*"] - - cookies { - forward = "all" - } - } + cache_policy_id = aws_cloudfront_cache_policy.api.id + origin_request_policy_id = aws_cloudfront_origin_request_policy.api.id min_ttl = 0 default_ttl = 0 From 98ad7d1d7c2d2c27ca1d1efcb63b2f2dfd3af270 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Mon, 16 Mar 2026 16:06:20 +0000 Subject: [PATCH 69/77] disable query string behaviour caching as caching time is set to 0 --- infrastructure/terraform/modules/cloudfront/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/terraform/modules/cloudfront/main.tf b/infrastructure/terraform/modules/cloudfront/main.tf index 11f086fd..3b5fe549 100644 --- a/infrastructure/terraform/modules/cloudfront/main.tf +++ b/infrastructure/terraform/modules/cloudfront/main.tf @@ -15,7 +15,7 @@ resource "aws_cloudfront_cache_policy" "api" { } query_strings_config { - query_string_behavior = "all" + query_string_behavior = "none" } } } From 867cebc9f220ae99834821f73aeb47299553cd66 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 17 Mar 2026 09:16:21 +0000 Subject: [PATCH 70/77] add cloudwatch logging to api gateway deployment --- .../modules/lambda_with_api_gateway/main.tf | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf index b1ee3b75..bef4a16c 100644 --- a/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf +++ b/infrastructure/terraform/modules/lambda_with_api_gateway/main.tf @@ -6,6 +6,14 @@ module "role" { name = "${var.name}-lambda-${var.stage}" } +############################################ +# Cloudwatch log group +############################################ +resource "aws_cloudwatch_log_group" "api_logs" { + name = "/aws/apigateway/${var.name}-${var.stage}" + retention_in_days = 14 +} + ############################################ # Install python packages ############################################ @@ -68,6 +76,19 @@ resource "aws_apigatewayv2_stage" "this" { api_id = aws_apigatewayv2_api.this.id name = "$default" auto_deploy = true + + access_log_settings { + destination_arn = aws_cloudwatch_log_group.api_logs.arn + + format = jsonencode({ + requestId = "$context.requestId" + domainName = "$context.domainName" + path = "$context.path" + status = "$context.status" + sourceIp = "$context.identity.sourceIp" + userAgent = "$context.identity.userAgent" + }) + } } resource "aws_apigatewayv2_integration" "this" { From c91193ed2287f3dfbaafbe97e4ad4e89418addff Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 17 Mar 2026 09:42:16 +0000 Subject: [PATCH 71/77] use managed cloudfront caching and forwarding policies instead of defining our own --- .../terraform/modules/cloudfront/main.tf | 46 +++---------------- 1 file changed, 6 insertions(+), 40 deletions(-) diff --git a/infrastructure/terraform/modules/cloudfront/main.tf b/infrastructure/terraform/modules/cloudfront/main.tf index 3b5fe549..261352a8 100644 --- a/infrastructure/terraform/modules/cloudfront/main.tf +++ b/infrastructure/terraform/modules/cloudfront/main.tf @@ -1,39 +1,9 @@ -resource "aws_cloudfront_cache_policy" "api" { - name = "api-no-cache" - - default_ttl = 0 - max_ttl = 0 - min_ttl = 0 - - parameters_in_cache_key_and_forwarded_to_origin { - cookies_config { - cookie_behavior = "none" - } - - headers_config { - header_behavior = "none" - } - - query_strings_config { - query_string_behavior = "none" - } - } +data "aws_cloudfront_cache_policy" "caching_disabled" { + name = "Managed-CachingDisabled" } -resource "aws_cloudfront_origin_request_policy" "api" { - name = "api-forward-all" - - headers_config { - header_behavior = "allViewer" - } - - query_strings_config { - query_string_behavior = "all" - } - - cookies_config { - cookie_behavior = "all" - } +data "aws_cloudfront_origin_request_policy" "all_viewer_except_host_header" { + name = "Managed-AllViewerExceptHostHeader" } ############################################ @@ -122,12 +92,8 @@ resource "aws_cloudfront_distribution" "this" { allowed_methods = ["GET","HEAD","OPTIONS","PUT","POST","PATCH","DELETE"] cached_methods = ["GET","HEAD"] - cache_policy_id = aws_cloudfront_cache_policy.api.id - origin_request_policy_id = aws_cloudfront_origin_request_policy.api.id - - min_ttl = 0 - default_ttl = 0 - max_ttl = 0 + cache_policy_id = data.aws_cloudfront_cache_policy.caching_disabled.id + origin_request_policy_id = data.aws_cloudfront_origin_request_policy.all_viewer_except_host_header.id } price_class = "PriceClass_All" From e9f37e79597bc81362ade200244203ce619851a5 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 17 Mar 2026 11:37:37 +0000 Subject: [PATCH 72/77] add certificate for cdn --- .github/workflows/deploy_terraform.yml | 47 ++++++++++++++++++- infrastructure/terraform/cdn/main.tf | 19 ++++++-- .../terraform/cdn_certificate/main.tf | 28 +++++++++++ .../terraform/cdn_certificate/outputs.tf | 3 ++ .../terraform/cdn_certificate/provider.tf | 17 +++++++ .../terraform/cdn_certificate/variables.tf | 3 ++ .../terraform/modules/acm_certificate/main.tf | 11 +++++ .../modules/acm_certificate/outputs.tf | 7 +++ .../modules/acm_certificate/variables.tf | 16 +++++++ .../terraform/modules/cloudfront/main.tf | 9 +++- .../terraform/modules/cloudfront/variables.tf | 6 +++ infrastructure/terraform/shared/main.tf | 11 +++++ 12 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 infrastructure/terraform/cdn_certificate/main.tf create mode 100644 infrastructure/terraform/cdn_certificate/outputs.tf create mode 100644 infrastructure/terraform/cdn_certificate/provider.tf create mode 100644 infrastructure/terraform/cdn_certificate/variables.tf create mode 100644 infrastructure/terraform/modules/acm_certificate/main.tf create mode 100644 infrastructure/terraform/modules/acm_certificate/outputs.tf create mode 100644 infrastructure/terraform/modules/acm_certificate/variables.tf diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index 501fe30f..733f7064 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -346,11 +346,56 @@ jobs: TF_VAR_epc_auth_token: ${{ secrets.DEV_EPC_AUTH_TOKEN }} TF_VAR_google_solar_api_key: ${{ secrets.DEV_GOOGLE_SOLAR_API_KEY }} + # ============================================================ + # Deploy ACM Certificate for Cloudfront + # ============================================================ + cloudfront_acm: + needs: [determine_stage, fast_api_lambda] + runs-on: ubuntu-latest + + env: + STAGE: ${{ needs.determine_stage.outputs.stage }} + TERRAFORM_APPLY: ${{ needs.determine_stage.outputs.terraform_apply }} + + steps: + - uses: actions/checkout@v4 + + - uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.DEV_AWS_REGION }} + + - uses: hashicorp/setup-terraform@v3 + + - name: Terraform Init + working-directory: infrastructure/terraform/cdn_certificate + run: terraform init -reconfigure + + - name: Terraform Workspace + working-directory: infrastructure/terraform/cdn_certificate + run: | + terraform workspace select $STAGE \ + || terraform workspace new $STAGE + + - name: Terraform Plan + working-directory: infrastructure/terraform/cdn_certificate + run: | + terraform plan \ + -var="stage=${STAGE}" \ + -out=tfplan + + - name: Terraform Apply + if: env.TERRAFORM_APPLY == 'true' + working-directory: infrastructure/terraform/cdn_certificate + run: terraform apply -auto-approve tfplan + + # ============================================================ # Deploy Cloudfront CDN # ============================================================ cloudfront_cdn: - needs: [determine_stage, shared_terraform, fast_api_lambda] + needs: [determine_stage, shared_terraform, cloudfront_acm] runs-on: ubuntu-latest env: diff --git a/infrastructure/terraform/cdn/main.tf b/infrastructure/terraform/cdn/main.tf index 7ef58e19..7e4c7cd7 100644 --- a/infrastructure/terraform/cdn/main.tf +++ b/infrastructure/terraform/cdn/main.tf @@ -16,7 +16,19 @@ data "terraform_remote_state" "shared" { data "terraform_remote_state" "fast_api" { backend = "s3" config = { - bucket = "ara-fast-api-terraform-state" + bucket = data.terraform_remote_state.shared.ara_fast_api_state_bucket + key = "env:/${var.stage}/terraform.tfstate" + region = "eu-west-2" + } +} + +############################################ +# Load CDN Certificate Terraform State +############################################ +data "terraform_remote_state" "cdn_certificate" { + backend = "s3" + config = { + bucket = data.terraform_remote_state.shared.cdn_certificate_state_bucket key = "env:/${var.stage}/terraform.tfstate" region = "eu-west-2" } @@ -28,8 +40,9 @@ data "terraform_remote_state" "fast_api" { module "cdn" { source = "../modules/cloudfront" - aliases = [] -# aliases = [data.terraform_remote_state.fast_api.outputs.domain_name] + aliases = [data.terraform_remote_state.fast_api.outputs.domain_name] + + acm_certificate_arn = data.terraform_remote_state.cdn_certificate.outputs.certificate_arn origins = [ # ---- S3 ---- diff --git a/infrastructure/terraform/cdn_certificate/main.tf b/infrastructure/terraform/cdn_certificate/main.tf new file mode 100644 index 00000000..5c0c178c --- /dev/null +++ b/infrastructure/terraform/cdn_certificate/main.tf @@ -0,0 +1,28 @@ +############################################ +# Load FastAPI Terraform State +############################################ +data "terraform_remote_state" "fast_api" { + backend = "s3" + config = { + bucket = "ara-fast-api-terraform-state" + key = "env:/${var.stage}/terraform.tfstate" + region = "eu-west-2" + } +} + +############################################ +# Define Certificate +############################################ +module "cdn_certificate" { + source = "../modules/acm_certificate" + + providers = { + aws = aws.us_east_1 + } + + domain_name = data.terraform_remote_state.fast_api.outputs.domain_name + + tags = { + Environment = var.stage + } +} \ No newline at end of file diff --git a/infrastructure/terraform/cdn_certificate/outputs.tf b/infrastructure/terraform/cdn_certificate/outputs.tf new file mode 100644 index 00000000..e72c6e13 --- /dev/null +++ b/infrastructure/terraform/cdn_certificate/outputs.tf @@ -0,0 +1,3 @@ +output "certificate_arn" { + value = module.cdn_certificate.certificate_arn +} \ No newline at end of file diff --git a/infrastructure/terraform/cdn_certificate/provider.tf b/infrastructure/terraform/cdn_certificate/provider.tf new file mode 100644 index 00000000..84d88438 --- /dev/null +++ b/infrastructure/terraform/cdn_certificate/provider.tf @@ -0,0 +1,17 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0" + } + } +} + +provider "aws" { + region = var.region +} + +provider "aws" { + alias = "us_east_1" + region = "us-east-1" +} \ No newline at end of file diff --git a/infrastructure/terraform/cdn_certificate/variables.tf b/infrastructure/terraform/cdn_certificate/variables.tf new file mode 100644 index 00000000..423f0b0f --- /dev/null +++ b/infrastructure/terraform/cdn_certificate/variables.tf @@ -0,0 +1,3 @@ +variable "stage" { + type = string +} \ No newline at end of file diff --git a/infrastructure/terraform/modules/acm_certificate/main.tf b/infrastructure/terraform/modules/acm_certificate/main.tf new file mode 100644 index 00000000..13d39556 --- /dev/null +++ b/infrastructure/terraform/modules/acm_certificate/main.tf @@ -0,0 +1,11 @@ +resource "aws_acm_certificate" "this" { + domain_name = var.domain_name + subject_alternative_names = var.subject_alternative_names + validation_method = "DNS" + + lifecycle { + create_before_destroy = true + } + + tags = var.tags +} \ No newline at end of file diff --git a/infrastructure/terraform/modules/acm_certificate/outputs.tf b/infrastructure/terraform/modules/acm_certificate/outputs.tf new file mode 100644 index 00000000..990a6db9 --- /dev/null +++ b/infrastructure/terraform/modules/acm_certificate/outputs.tf @@ -0,0 +1,7 @@ +output "certificate_arn" { + value = aws_acm_certificate.this.arn +} + +output "domain_validation_options" { + value = aws_acm_certificate.this.domain_validation_options +} \ No newline at end of file diff --git a/infrastructure/terraform/modules/acm_certificate/variables.tf b/infrastructure/terraform/modules/acm_certificate/variables.tf new file mode 100644 index 00000000..bd30501f --- /dev/null +++ b/infrastructure/terraform/modules/acm_certificate/variables.tf @@ -0,0 +1,16 @@ +variable "domain_name" { + description = "Primary domain name for the certificate" + type = string +} + +variable "subject_alternative_names" { + description = "Additional domains for the certificate" + type = list(string) + default = [] +} + +variable "tags" { + description = "Tags to apply to the certificate" + type = map(string) + default = {} +} \ No newline at end of file diff --git a/infrastructure/terraform/modules/cloudfront/main.tf b/infrastructure/terraform/modules/cloudfront/main.tf index 261352a8..03082604 100644 --- a/infrastructure/terraform/modules/cloudfront/main.tf +++ b/infrastructure/terraform/modules/cloudfront/main.tf @@ -1,3 +1,6 @@ +############################################# +# Use Managed Caching and Forwarding Policies +############################################# data "aws_cloudfront_cache_policy" "caching_disabled" { name = "Managed-CachingDisabled" } @@ -113,7 +116,11 @@ resource "aws_cloudfront_distribution" "this" { ########################################## viewer_certificate { - cloudfront_default_certificate = true + acm_certificate_arn = var.acm_certificate_arn + ssl_support_method = var.acm_certificate_arn != null ? "sni-only" : null + minimum_protocol_version = var.acm_certificate_arn != null ? "TLSv1.2_2021" : null + + cloudfront_default_certificate = var.acm_certificate_arn == null } } diff --git a/infrastructure/terraform/modules/cloudfront/variables.tf b/infrastructure/terraform/modules/cloudfront/variables.tf index feff2faa..4721d3d1 100644 --- a/infrastructure/terraform/modules/cloudfront/variables.tf +++ b/infrastructure/terraform/modules/cloudfront/variables.tf @@ -11,4 +11,10 @@ variable "origins" { variable "aliases" { type = list(string) +} + +variable "acm_certificate_arn" { + description = "ACM certificate ARN for custom aliases" + type = string + default = null } \ No newline at end of file diff --git a/infrastructure/terraform/shared/main.tf b/infrastructure/terraform/shared/main.tf index 1de2031f..486f79ca 100644 --- a/infrastructure/terraform/shared/main.tf +++ b/infrastructure/terraform/shared/main.tf @@ -562,4 +562,15 @@ output "fast_api_s3_read_and_write_arn" { value = module.fast_api_s3_read_and_write.policy_arn } +################################################ +# CDN Certificate +################################################ +module "cdn_certificate_state_bucket" { + source = "../modules/tf_state_bucket" + bucket_name = "cdn-certificate-terraform-state" +} + +output "cdn_certificate_state_bucket" { + value = module.cdn_certificate_state_bucket.bucket_name +} From 3d0a37cf22bb17eab3d0352e6cbbe90ee8dbafc1 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 17 Mar 2026 11:47:56 +0000 Subject: [PATCH 73/77] certificate depends on shared because of tfstate bucket --- .github/workflows/deploy_terraform.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index 733f7064..e41534e6 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -350,7 +350,7 @@ jobs: # Deploy ACM Certificate for Cloudfront # ============================================================ cloudfront_acm: - needs: [determine_stage, fast_api_lambda] + needs: [determine_stage, shared_terraform, fast_api_lambda] runs-on: ubuntu-latest env: @@ -395,7 +395,7 @@ jobs: # Deploy Cloudfront CDN # ============================================================ cloudfront_cdn: - needs: [determine_stage, shared_terraform, cloudfront_acm] + needs: [determine_stage, cloudfront_acm] runs-on: ubuntu-latest env: From f84a5cd762b02144e9c31d6e778105556d183030 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 17 Mar 2026 12:00:43 +0000 Subject: [PATCH 74/77] hardcode state bucket names --- infrastructure/terraform/cdn/main.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/terraform/cdn/main.tf b/infrastructure/terraform/cdn/main.tf index 7e4c7cd7..56b2f52b 100644 --- a/infrastructure/terraform/cdn/main.tf +++ b/infrastructure/terraform/cdn/main.tf @@ -16,7 +16,7 @@ data "terraform_remote_state" "shared" { data "terraform_remote_state" "fast_api" { backend = "s3" config = { - bucket = data.terraform_remote_state.shared.ara_fast_api_state_bucket + bucket = "ara-fast-api-terraform-state" key = "env:/${var.stage}/terraform.tfstate" region = "eu-west-2" } @@ -28,7 +28,7 @@ data "terraform_remote_state" "fast_api" { data "terraform_remote_state" "cdn_certificate" { backend = "s3" config = { - bucket = data.terraform_remote_state.shared.cdn_certificate_state_bucket + bucket = "cdn-certificate-terraform-state" key = "env:/${var.stage}/terraform.tfstate" region = "eu-west-2" } From 4b405202886af3308c63c754d2e0088073fe25ad Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 17 Mar 2026 12:23:19 +0000 Subject: [PATCH 75/77] make sure cdn certificate state is stored --- infrastructure/terraform/cdn_certificate/provider.tf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/infrastructure/terraform/cdn_certificate/provider.tf b/infrastructure/terraform/cdn_certificate/provider.tf index 84d88438..38b01d82 100644 --- a/infrastructure/terraform/cdn_certificate/provider.tf +++ b/infrastructure/terraform/cdn_certificate/provider.tf @@ -5,6 +5,12 @@ terraform { version = ">= 5.0" } } + + backend "s3" { + bucket = "cdn-certificate-terraform-state" + key = "terraform.tfstate" + region = "eu-west-2" + } } provider "aws" { From 3aefba7ba7b33e6e312f0ba326cc686d1bfa16c6 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 17 Mar 2026 17:02:52 +0000 Subject: [PATCH 76/77] do not recreate certificate every deploy --- infrastructure/terraform/modules/acm_certificate/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/terraform/modules/acm_certificate/main.tf b/infrastructure/terraform/modules/acm_certificate/main.tf index 13d39556..9f813a77 100644 --- a/infrastructure/terraform/modules/acm_certificate/main.tf +++ b/infrastructure/terraform/modules/acm_certificate/main.tf @@ -4,7 +4,7 @@ resource "aws_acm_certificate" "this" { validation_method = "DNS" lifecycle { - create_before_destroy = true + create_before_destroy = false } tags = var.tags From 63c0f845230bf013921cffde71ba5522e46c7107 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Tue, 17 Mar 2026 17:08:59 +0000 Subject: [PATCH 77/77] update gitignore to ignore terraform outputs --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 68e66052..15050bdd 100644 --- a/.gitignore +++ b/.gitignore @@ -246,7 +246,7 @@ etl/epc/local_data/* /backend/condition/sample_data/peabody/* *.DS_Store -infrastructure/terraform/.terraform* +**/.terraform* # Don't commit packages up serverless packages .serverless