From b7bee7486c5611b5b95bafd702018569f88976a5 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Fri, 13 Mar 2026 09:51:48 +0000 Subject: [PATCH] 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 ################################################