diff --git a/.github/workflows/deploy-sandbox.yml b/.github/workflows/deploy-sandbox.yml index 8b8b4760d..fa60722e4 100644 --- a/.github/workflows/deploy-sandbox.yml +++ b/.github/workflows/deploy-sandbox.yml @@ -17,7 +17,7 @@ on: permissions: pull-requests: write id-token: write # This is required for requesting the JWT - contents: read # This is required for actions/checkout + contents: read # This is required for actions/checkout jobs: validate_inputs: diff --git a/infrastructure/lambda-bulk-upload.tf b/infrastructure/lambda-bulk-upload.tf index 45e5b994a..22de62c49 100644 --- a/infrastructure/lambda-bulk-upload.tf +++ b/infrastructure/lambda-bulk-upload.tf @@ -42,10 +42,10 @@ module "bulk-upload-lambda" { APIM_API_URL = data.aws_ssm_parameter.apim_url.value } - is_gateway_integration_needed = false - is_invoked_from_gateway = false - lambda_timeout = 900 - reserved_concurrent_executions = local.bulk_upload_lambda_concurrent_limit + is_gateway_integration_needed = false + is_invoked_from_gateway = false + lambda_timeout = 900 + manage_reserved_concurrency = false depends_on = [ module.ndr-bulk-staging-store, diff --git a/infrastructure/modules/lambda/main.tf b/infrastructure/modules/lambda/main.tf index 5275fd188..28d28475e 100644 --- a/infrastructure/modules/lambda/main.tf +++ b/infrastructure/modules/lambda/main.tf @@ -1,4 +1,13 @@ +# Migrate existing resources to indexed form +moved { + from = aws_lambda_function.lambda + to = aws_lambda_function.lambda[0] +} + +# Lambda with Terraform-managed concurrency (default - keeps existing resource name) resource "aws_lambda_function" "lambda" { + count = var.manage_reserved_concurrency ? 1 : 0 + # If the file is not in the current working directory you will need to include a # path.module in the filename. filename = data.archive_file.lambda.output_path @@ -41,6 +50,59 @@ resource "aws_lambda_function" "lambda" { ] } +# Lambda with externally-managed concurrency (opt-in with new resource name) +resource "aws_lambda_function" "lambda_unmanaged" { + count = var.manage_reserved_concurrency ? 0 : 1 + + # If the file is not in the current working directory you will need to include a + # path.module in the filename. + filename = data.archive_file.lambda.output_path + function_name = "${terraform.workspace}_${var.name}" + role = aws_iam_role.lambda_execution_role.arn + handler = var.handler + source_code_hash = data.archive_file.lambda.output_base64sha256 + runtime = "python3.11" + timeout = var.lambda_timeout + memory_size = var.memory_size + reserved_concurrent_executions = var.reserved_concurrent_executions + kms_key_arn = aws_kms_key.lambda.arn + + ephemeral_storage { + size = var.lambda_ephemeral_storage + } + + environment { + variables = var.lambda_environment_variables + } + + vpc_config { + subnet_ids = var.vpc_subnet_ids + security_group_ids = var.vpc_security_group_ids + } + + layers = local.lambda_layers + + lifecycle { + ignore_changes = [ + # These are required as Lambdas are deployed via the CI/CD pipelines + source_code_hash, + layers, + # Allow external management of concurrency + reserved_concurrent_executions + ] + } + + depends_on = [ + aws_iam_role_policy_attachment.default_policies, + aws_iam_role_policy_attachment.lambda_execution_policy + ] +} + +# Local value to reference the active lambda resource +locals { + lambda = var.manage_reserved_concurrency ? aws_lambda_function.lambda[0] : aws_lambda_function.lambda_unmanaged[0] +} + resource "aws_cloudwatch_log_group" "lambda_logs" { count = contains(var.persistent_workspaces, terraform.workspace) ? 0 : 1 name = "/aws/lambda/${terraform.workspace}_${var.name}" @@ -128,14 +190,14 @@ resource "aws_api_gateway_integration" "lambda_integration" { http_method = each.value integration_http_method = "POST" type = "AWS_PROXY" - uri = aws_lambda_function.lambda.invoke_arn + uri = local.lambda.invoke_arn } resource "aws_lambda_permission" "lambda_permission" { count = var.is_invoked_from_gateway ? 1 : 0 statement_id = "AllowAPIGatewayInvoke" action = "lambda:InvokeFunction" - function_name = aws_lambda_function.lambda.arn + function_name = local.lambda.arn principal = "apigateway.amazonaws.com" # The "/*/*" portion grants access from any method on any resource # within the API Gateway REST API. diff --git a/infrastructure/modules/lambda/outputs.tf b/infrastructure/modules/lambda/outputs.tf index 64697f46f..055bd58e2 100644 --- a/infrastructure/modules/lambda/outputs.tf +++ b/infrastructure/modules/lambda/outputs.tf @@ -1,21 +1,21 @@ output "invoke_arn" { - value = aws_lambda_function.lambda.invoke_arn + value = local.lambda.invoke_arn } output "qualified_arn" { - value = aws_lambda_function.lambda.qualified_arn + value = local.lambda.qualified_arn } output "function_name" { - value = aws_lambda_function.lambda.function_name + value = local.lambda.function_name } output "timeout" { - value = aws_lambda_function.lambda.timeout + value = local.lambda.timeout } output "lambda_arn" { - value = aws_lambda_function.lambda.arn + value = local.lambda.arn } output "lambda_execution_role_name" { @@ -24,4 +24,4 @@ output "lambda_execution_role_name" { output "lambda_execution_role_arn" { value = aws_iam_role.lambda_execution_role.arn -} \ No newline at end of file +} diff --git a/infrastructure/modules/lambda/variable.tf b/infrastructure/modules/lambda/variable.tf index 6a5f9553d..3f540997a 100644 --- a/infrastructure/modules/lambda/variable.tf +++ b/infrastructure/modules/lambda/variable.tf @@ -85,6 +85,18 @@ variable "reserved_concurrent_executions" { default = -1 } +variable "manage_reserved_concurrency" { + description = <<-EOT + Whether Terraform should manage reserved_concurrent_executions. + - true (default): Terraform enforces concurrency value on every deploy. Any external changes will be reset. + - false: Terraform ignores concurrency after creation, allowing external management (EventBridge, controller lambdas). + + Note: When false, reserved_concurrent_executions is set initially but then ignored. External systems can freely modify it. + EOT + type = bool + default = true +} + variable "default_policies" { description = "List of default IAM policy ARNs to attach to the Lambda execution role." type = list(string)