Skip to content

Commit c0d351d

Browse files
Merge pull request #148 from NHSDigital/feature/CCM-14149_Support_Container_Based_Lambdas
CCM-14149: Support Container Based Lambdas
2 parents 98605e0 + b2b512b commit c0d351d

8 files changed

Lines changed: 126 additions & 13 deletions

File tree

infrastructure/modules/lambda/README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,17 @@
2222
| <a name="input_filter_pattern"></a> [filter\_pattern](#input\_filter\_pattern) | Filter pattern to use for the log subscription filter | `string` | `""` | no |
2323
| <a name="input_force_lambda_code_deploy"></a> [force\_lambda\_code\_deploy](#input\_force\_lambda\_code\_deploy) | If the lambda package in s3 has the same commit id tag as the terraform build branch, the lambda will not update automatically. Set to True if making changes to Lambda code from on the same commit for example during development | `bool` | `false` | no |
2424
| <a name="input_function_code_base_path"></a> [function\_code\_base\_path](#input\_function\_code\_base\_path) | The base path to the sourcecode directories needed for this lambda | `string` | `"./"` | no |
25-
| <a name="input_function_code_dir"></a> [function\_code\_dir](#input\_function\_code\_dir) | The directory for this lambda | `string` | n/a | yes |
25+
| <a name="input_function_code_dir"></a> [function\_code\_dir](#input\_function\_code\_dir) | The directory for this lambda | `string` | `null` | no |
2626
| <a name="input_function_include_common"></a> [function\_include\_common](#input\_function\_include\_common) | Include the 'common' lambda module with this lambda | `bool` | `true` | no |
2727
| <a name="input_function_module_name"></a> [function\_module\_name](#input\_function\_module\_name) | The name of the function module as used by the lambda handler, e.g. index or exports | `string` | `"index"` | no |
2828
| <a name="input_function_name"></a> [function\_name](#input\_function\_name) | Base name of this lambda | `string` | n/a | yes |
29-
| <a name="input_function_s3_bucket"></a> [function\_s3\_bucket](#input\_function\_s3\_bucket) | The bucket to upload Lambda packages to | `string` | n/a | yes |
29+
| <a name="input_function_s3_bucket"></a> [function\_s3\_bucket](#input\_function\_s3\_bucket) | The bucket to upload Lambda packages to | `string` | `null` | no |
3030
| <a name="input_group"></a> [group](#input\_group) | The name of the tfscaffold group | `string` | `null` | no |
3131
| <a name="input_handler_function_name"></a> [handler\_function\_name](#input\_handler\_function\_name) | The name of the lambda handler function (passed directly to the Lambda's handler option) | `string` | `"handler"` | no |
3232
| <a name="input_iam_policy_document"></a> [iam\_policy\_document](#input\_iam\_policy\_document) | n/a | <pre>object({<br/> body = string<br/> })</pre> | `null` | no |
33+
| <a name="input_image_config"></a> [image\_config](#input\_image\_config) | Optional image configuration for Image-based Lambda | <pre>object({<br/> entry_point = optional(list(string))<br/> command = optional(list(string))<br/> working_directory = optional(string)<br/> })</pre> | `null` | no |
34+
| <a name="input_image_repository_names"></a> [image\_repository\_names](#input\_image\_repository\_names) | ECR repository names allowed for Image-based Lambda | `list(string)` | `[]` | no |
35+
| <a name="input_image_uri"></a> [image\_uri](#input\_image\_uri) | ECR image URI for Image-based Lambda | `string` | `null` | no |
3336
| <a name="input_kms_key_arn"></a> [kms\_key\_arn](#input\_kms\_key\_arn) | KMS key arn to use for this function | `string` | n/a | yes |
3437
| <a name="input_lambda_at_edge"></a> [lambda\_at\_edge](#input\_lambda\_at\_edge) | Whether this Lambda is a Lambda@Edge function | `bool` | `false` | no |
3538
| <a name="input_lambda_dlq_message_retention_seconds"></a> [lambda\_dlq\_message\_retention\_seconds](#input\_lambda\_dlq\_message\_retention\_seconds) | The number of seconds to retain messages in the Lambda DLQ SQS queue | `number` | `1209600` | no |
@@ -41,10 +44,11 @@
4144
| <a name="input_log_subscription_lambda_create_permission"></a> [log\_subscription\_lambda\_create\_permission](#input\_log\_subscription\_lambda\_create\_permission) | Whether to create a permission for the log forwarder. Set to false if using a generic one. | `bool` | `true` | no |
4245
| <a name="input_log_subscription_role_arn"></a> [log\_subscription\_role\_arn](#input\_log\_subscription\_role\_arn) | The ARN of the IAM role to use for the log subscription filter | `string` | `""` | no |
4346
| <a name="input_memory"></a> [memory](#input\_memory) | The amount of memory to apply to the created Lambda | `number` | n/a | yes |
47+
| <a name="input_package_type"></a> [package\_type](#input\_package\_type) | Lambda package type: Zip or Image | `string` | `"Zip"` | no |
4448
| <a name="input_permission_statements"></a> [permission\_statements](#input\_permission\_statements) | Statements giving an external source permission to invoke the Lambda function | <pre>list(object({<br/> action = optional(string)<br/> principal = string<br/> source_arn = optional(string)<br/> source_account = optional(string)<br/> statement_id = string<br/> }))</pre> | `[]` | no |
4549
| <a name="input_project"></a> [project](#input\_project) | The name of the tfscaffold project | `string` | n/a | yes |
4650
| <a name="input_region"></a> [region](#input\_region) | The AWS Region | `string` | n/a | yes |
47-
| <a name="input_runtime"></a> [runtime](#input\_runtime) | The runtime to use for the lambda function | `string` | n/a | yes |
51+
| <a name="input_runtime"></a> [runtime](#input\_runtime) | The runtime to use for the lambda function | `string` | `null` | no |
4852
| <a name="input_schedule"></a> [schedule](#input\_schedule) | The fully qualified Cloudwatch Events schedule for when to run the lambda function, e.g. rate(1 day) or a cron() expression. Default disables all events resources | `string` | `""` | no |
4953
| <a name="input_send_to_firehose"></a> [send\_to\_firehose](#input\_send\_to\_firehose) | Enable sending logs to firehose | `bool` | `true` | no |
5054
| <a name="input_sns_destination"></a> [sns\_destination](#input\_sns\_destination) | SNS Topic ARN to be used for on-failure Lambda invocation records | `string` | `null` | no |

infrastructure/modules/lambda/data_archive_file_lambda.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
data "archive_file" "lambda" {
2+
count = local.package_type == "zip" ? 1 : 0
23
type = "zip"
34
source_dir = "${path.root}/${var.function_code_base_path}/${var.function_code_dir}"
45

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
data "aws_iam_policy_document" "ecr" {
2+
statement {
3+
effect = "Allow"
4+
5+
actions = [
6+
"ecr:GetAuthorizationToken",
7+
]
8+
9+
resources = ["*"]
10+
}
11+
12+
statement {
13+
effect = "Allow"
14+
15+
actions = [
16+
"ecr:BatchGetImage",
17+
"ecr:GetDownloadUrlForLayer",
18+
"ecr:BatchCheckLayerAvailability",
19+
]
20+
21+
resources = [
22+
for repo_name in var.image_repository_names :
23+
"arn:aws:ecr:${var.region}:${var.aws_account_id}:repository/${repo_name}"
24+
]
25+
}
26+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
resource "aws_iam_role_policy" "ecr" {
2+
count = local.package_type == "image" ? 1 : 0
3+
name = "${local.csi}-ecr"
4+
role = aws_iam_role.main.id
5+
policy = data.aws_iam_policy_document.ecr.json
6+
}

infrastructure/modules/lambda/lambda_function.tf

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,27 @@ resource "aws_lambda_function" "main" {
22
description = var.description
33
function_name = local.csi
44
role = aws_iam_role.main.arn
5-
handler = "${var.function_module_name}.${var.handler_function_name}"
6-
runtime = var.runtime
5+
handler = local.package_type == "zip" ? "${var.function_module_name}.${var.handler_function_name}" : null
6+
runtime = local.package_type == "zip" ? var.runtime : null
7+
package_type = title(local.package_type)
78
publish = true
89
memory_size = var.memory
910
timeout = var.timeout
1011

11-
s3_bucket = aws_s3_object.lambda.bucket
12-
s3_key = aws_s3_object.lambda.key
13-
s3_object_version = aws_s3_object.lambda.version_id
12+
s3_bucket = local.package_type == "zip" ? aws_s3_object.lambda[0].bucket : null
13+
s3_key = local.package_type == "zip" ? aws_s3_object.lambda[0].key : null
14+
s3_object_version = local.package_type == "zip" ? aws_s3_object.lambda[0].version_id : null
15+
16+
image_uri = local.package_type == "image" ? var.image_uri : null
17+
18+
dynamic "image_config" {
19+
for_each = local.package_type == "image" && var.image_config != null ? [1] : []
20+
content {
21+
entry_point = try(var.image_config.entry_point, null)
22+
command = try(var.image_config.command, null)
23+
working_directory = try(var.image_config.working_directory, null)
24+
}
25+
}
1426

1527
logging_config {
1628
application_log_level = var.application_log_level
@@ -19,12 +31,12 @@ resource "aws_lambda_function" "main" {
1931
system_log_level = var.system_log_level
2032
}
2133

22-
layers = compact(concat(
34+
layers = local.package_type == "zip" ? compact(concat(
2335
var.layers,
2436
[
2537
var.enable_lambda_insights && var.lambda_at_edge == false ? "arn:aws:lambda:${var.region}:580247275435:layer:LambdaInsightsExtension:53" : null
2638
]
27-
))
39+
)) : []
2840

2941
environment {
3042
variables = var.lambda_env_vars

infrastructure/modules/lambda/locals.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
locals {
22
module = "lambda"
33

4+
package_type = lower(var.package_type)
5+
46
# Compound Scope Identifier
57
csi = replace(
68
format(

infrastructure/modules/lambda/s3_object_lambda.tf

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
resource "aws_s3_object" "lambda" {
2+
count = local.package_type == "zip" ? 1 : 0
23
bucket = var.function_s3_bucket
34
key = "${local.csi}.zip"
4-
source = data.archive_file.lambda.output_path
5+
source = data.archive_file.lambda[0].output_path
56

6-
source_hash = var.force_lambda_code_deploy ? data.archive_file.lambda.output_base64sha256 : null
7+
source_hash = var.force_lambda_code_deploy ? data.archive_file.lambda[0].output_base64sha256 : null
78

89
metadata = {
9-
hash = data.archive_file.lambda.output_base64sha256
10+
hash = data.archive_file.lambda[0].output_base64sha256
1011
function = local.csi
1112
commit = try(data.external.git_commit.result["sha"], "null")
1213
}

infrastructure/modules/lambda/variables.tf

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,55 @@ variable "log_retention_in_days" {
9292
variable "runtime" {
9393
type = string
9494
description = "The runtime to use for the lambda function"
95+
default = null
96+
97+
validation {
98+
condition = lower(var.package_type) != "zip" || (var.runtime != null && var.runtime != "")
99+
error_message = "runtime must be set when package_type is Zip."
100+
}
101+
}
102+
103+
variable "package_type" {
104+
type = string
105+
description = "Lambda package type: Zip or Image"
106+
default = "Zip"
107+
108+
validation {
109+
condition = contains(["zip", "image"], lower(var.package_type))
110+
error_message = "package_type must be either Zip or Image."
111+
}
112+
}
113+
114+
variable "image_uri" {
115+
type = string
116+
description = "ECR image URI for Image-based Lambda"
117+
default = null
118+
119+
validation {
120+
condition = lower(var.package_type) != "image" || (var.image_uri != null && var.image_uri != "")
121+
error_message = "image_uri must be set when package_type is Image."
122+
}
123+
}
124+
125+
variable "image_config" {
126+
type = object({
127+
entry_point = optional(list(string))
128+
command = optional(list(string))
129+
working_directory = optional(string)
130+
})
131+
description = "Optional image configuration for Image-based Lambda"
132+
default = null
133+
}
134+
135+
variable "image_repository_names" {
136+
type = list(string)
137+
description = "ECR repository names allowed for Image-based Lambda"
138+
default = []
139+
140+
validation {
141+
condition = lower(var.package_type) != "image" || length(var.image_repository_names) > 0
142+
error_message = "image_repository_names must include at least one repository name when package_type is Image."
143+
}
95144
}
96145

97146
variable "schedule" {
@@ -122,11 +171,23 @@ variable "function_code_base_path" {
122171
variable "function_code_dir" {
123172
type = string
124173
description = "The directory for this lambda"
174+
default = null
175+
176+
validation {
177+
condition = lower(var.package_type) != "zip" || (var.function_code_dir != null && var.function_code_dir != "")
178+
error_message = "function_code_dir must be set when package_type is Zip."
179+
}
125180
}
126181

127182
variable "function_s3_bucket" {
128183
type = string
129184
description = "The bucket to upload Lambda packages to"
185+
default = null
186+
187+
validation {
188+
condition = lower(var.package_type) != "zip" || (var.function_s3_bucket != null && var.function_s3_bucket != "")
189+
error_message = "function_s3_bucket must be set when package_type is Zip."
190+
}
130191
}
131192

132193
variable "function_include_common" {

0 commit comments

Comments
 (0)