From 9f9930cabcd4aaeadacab3aa46c085c120c59f9e Mon Sep 17 00:00:00 2001 From: Niranjan Rajendran Date: Mon, 7 Dec 2020 17:51:11 +0530 Subject: [PATCH] feat: Add support for creating lambdas that use Container Images (#80) --- .gitignore | 1 + .pre-commit-config.yaml | 2 +- README.md | 24 +++++- examples/alias/versions.tf | 2 +- examples/async/versions.tf | 2 +- examples/build-package/versions.tf | 2 +- examples/container-image/README.md | 61 ++++++++++++++ examples/container-image/context/Dockerfile | 2 + examples/container-image/context/empty | 1 + examples/container-image/main.tf | 65 +++++++++++++++ examples/container-image/outputs.tf | 88 +++++++++++++++++++++ examples/container-image/variables.tf | 0 examples/container-image/versions.tf | 13 +++ examples/deploy/versions.tf | 2 +- examples/multiple-regions/versions.tf | 2 +- examples/simple/versions.tf | 2 +- examples/with-efs/README.md | 7 +- examples/with-efs/versions.tf | 2 +- examples/with-vpc/versions.tf | 2 +- main.tf | 15 +++- modules/alias/versions.tf | 2 +- modules/deploy/versions.tf | 2 +- variables.tf | 30 +++++++ versions.tf | 2 +- 24 files changed, 313 insertions(+), 18 deletions(-) create mode 100644 examples/container-image/README.md create mode 100644 examples/container-image/context/Dockerfile create mode 100644 examples/container-image/context/empty create mode 100644 examples/container-image/main.tf create mode 100644 examples/container-image/outputs.tf create mode 100644 examples/container-image/variables.tf create mode 100644 examples/container-image/versions.tf diff --git a/.gitignore b/.gitignore index 0308fbf4..b95fccd1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.tfstate *.tfvars *.tfplan +.terraform.lock.hcl builds/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8677870d..94f7541a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: git://github.com/antonbabenko/pre-commit-terraform - rev: v1.44.0 + rev: v1.45.0 hooks: - id: terraform_fmt - id: terraform_validate diff --git a/README.md b/README.md index 6986ec1c..983dcba9 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ This Terraform module is the part of [serverless.tf framework](https://github.co - [x] Build dependencies for your Lambda Function and Layer. - [x] Support builds locally and in Docker (with or without SSH agent support for private builds). -- [x] Create deployment package or deploy existing (previously built package) from local, from S3, from URL. +- [x] Create deployment package or deploy existing (previously built package) from local, from S3, from URL, or from AWS ECR repository. - [x] Store deployment packages locally or in the S3 bucket. - [x] Support almost all features of Lambda resources (function, layer, alias, etc.) - [x] Lambda@Edge @@ -153,6 +153,22 @@ module "lambda_function_existing_package_s3" { } ``` +### Lambda Functions from Container Image stored on AWS ECR + +```hcl +module "lambda_function_container_image" { + source = "terraform-aws-modules/lambda/aws" + + function_name = "my-lambda-existing-package-local" + description = "My awesome lambda function" + + create_package = false + + image_uri = "132367819851.dkr.ecr.eu-west-1.amazonaws.com/complete-cow:1.0" + package_type = "Image" +} +``` + ### Lambda Layers (store packages locally and on S3) ```hcl @@ -543,6 +559,7 @@ Q4: What does this error mean - `"We currently do not support adding policies fo ## Examples * [Complete](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/complete) - Create Lambda resources in various combinations with all supported features. +* [Container Image](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/container-image) - Create Docker image (using [docker provider](https://registry.terraform.io/providers/kreuzwerker/docker)), push it to AWS ECR, and create Lambda function from it. * [Build and Package](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/build-package) - Build and create deployment packages in various ways. * [Alias](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/alias) - Create static and dynamic aliases in various ways. * [Deploy](https://github.com/terraform-aws-modules/terraform-aws-lambda/tree/master/examples/deploy) - Complete end-to-end build/update/deploy process using AWS CodeDeploy. @@ -619,6 +636,11 @@ Q4: What does this error mean - `"We currently do not support adding policies fo | function\_name | A unique name for your Lambda Function | `string` | `""` | no | | handler | Lambda Function entrypoint in your code | `string` | `""` | no | | hash\_extra | The string to add into hashing function. Useful when building same source path for different functions. | `string` | `""` | no | +| image\_uri | The ECR image URI containing the function's deployment package. | `string` | `null` | no | +| image\_config\_entry_point | The ENTRYPOINT for the docker image. | `string` | `null` | no | +| image\_config\_command | The CMD for the docker image. | `string` | `null` | no | +| image\_config\_working_directory | The working directory for the docker image. | `string` | `null` | no | +| package\_type | The Lambda deployment package type. | `string` | `Zip` | no | | kms\_key\_arn | The ARN of KMS key to use by your Lambda Function | `string` | `null` | no | | lambda\_at\_edge | Set this to true if using Lambda@Edge, to enable publishing, limit the timeout, and allow edgelambda.amazonaws.com to invoke the function | `bool` | `false` | no | | lambda\_role | IAM role attached to the Lambda Function. This governs both who / what can invoke your Lambda Function, as well as what resources our Lambda Function has access to. See Lambda Permission Model for more details. | `string` | `""` | no | diff --git a/examples/alias/versions.tf b/examples/alias/versions.tf index 16983974..957cf18c 100644 --- a/examples/alias/versions.tf +++ b/examples/alias/versions.tf @@ -2,7 +2,7 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.67" + aws = ">= 3.19" random = ">= 2" } } diff --git a/examples/async/versions.tf b/examples/async/versions.tf index 16983974..957cf18c 100644 --- a/examples/async/versions.tf +++ b/examples/async/versions.tf @@ -2,7 +2,7 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.67" + aws = ">= 3.19" random = ">= 2" } } diff --git a/examples/build-package/versions.tf b/examples/build-package/versions.tf index 16983974..957cf18c 100644 --- a/examples/build-package/versions.tf +++ b/examples/build-package/versions.tf @@ -2,7 +2,7 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.67" + aws = ">= 3.19" random = ">= 2" } } diff --git a/examples/container-image/README.md b/examples/container-image/README.md new file mode 100644 index 00000000..b4d7f3f8 --- /dev/null +++ b/examples/container-image/README.md @@ -0,0 +1,61 @@ +# AWS Lambda launched from Docker Container Image example + +Configuration in this directory creates AWS Lambda Function deployed with a Container Image. + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which cost money. Run `terraform destroy` when you don't need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| terraform | >= 0.12.6 | +| aws | >= 2.67 | +| random | >= 2 | + +## Providers + +| Name | Version | +|------|---------| +| aws | >= 2.67 | +| random | >= 2 | + +## Inputs + +No input. + +## Outputs + +| Name | Description | +|------|-------------| +| lambda\_cloudwatch\_log\_group\_arn | The ARN of the Cloudwatch Log Group | +| lambda\_role\_arn | The ARN of the IAM role created for the Lambda Function | +| lambda\_role\_name | The name of the IAM role created for the Lambda Function | +| local\_filename | The filename of zip archive deployed (if deployment was from local) | +| s3\_object | The map with S3 object data of zip archive deployed (if deployment was from S3) | +| this\_lambda\_function\_arn | The ARN of the Lambda Function | +| this\_lambda\_function\_invoke\_arn | The Invoke ARN of the Lambda Function | +| this\_lambda\_function\_kms\_key\_arn | The ARN for the KMS encryption key of Lambda Function | +| this\_lambda\_function\_last\_modified | The date Lambda Function resource was last modified | +| this\_lambda\_function\_name | The name of the Lambda Function | +| this\_lambda\_function\_qualified\_arn | The ARN identifying your Lambda Function Version | +| this\_lambda\_function\_source\_code\_hash | Base64-encoded representation of raw SHA-256 sum of the zip file | +| this\_lambda\_function\_source\_code\_size | The size in bytes of the function .zip file | +| this\_lambda\_function\_version | Latest published version of Lambda Function | +| this\_lambda\_layer\_arn | The ARN of the Lambda Layer with version | +| this\_lambda\_layer\_created\_date | The date Lambda Layer resource was created | +| this\_lambda\_layer\_layer\_arn | The ARN of the Lambda Layer without version | +| this\_lambda\_layer\_source\_code\_size | The size in bytes of the Lambda Layer .zip file | +| this\_lambda\_layer\_version | The Lambda Layer version | + + diff --git a/examples/container-image/context/Dockerfile b/examples/container-image/context/Dockerfile new file mode 100644 index 00000000..5c7f1077 --- /dev/null +++ b/examples/container-image/context/Dockerfile @@ -0,0 +1,2 @@ +FROM scratch +COPY empty /empty diff --git a/examples/container-image/context/empty b/examples/container-image/context/empty new file mode 100644 index 00000000..3f99b56c --- /dev/null +++ b/examples/container-image/context/empty @@ -0,0 +1 @@ +# empty file :) diff --git a/examples/container-image/main.tf b/examples/container-image/main.tf new file mode 100644 index 00000000..118ea181 --- /dev/null +++ b/examples/container-image/main.tf @@ -0,0 +1,65 @@ +provider "aws" { + region = "eu-west-1" + + # Make it faster by skipping something + skip_get_ec2_platforms = true + skip_metadata_api_check = true + skip_region_validation = true + skip_credentials_validation = true + skip_requesting_account_id = true +} + +resource "random_pet" "this" { + length = 2 +} + +module "lambda_function_from_container_image" { + source = "../../" + + function_name = "${random_pet.this.id}-lambda-from-container-image" + description = "My awesome lambda function from container image" + + create_package = false + + ################## + # Container Image + ################## + image_uri = docker_registry_image.app.name + package_type = "Image" +} + +################# +# ECR Repository +################# +resource "aws_ecr_repository" "this" { + name = random_pet.this.id +} + +############################################### +# Create Docker Image and push to ECR registry +############################################### + +data "aws_caller_identity" "this" {} +data "aws_region" "current" {} +data "aws_ecr_authorization_token" "token" {} + +locals { + ecr_address = format("%v.dkr.ecr.%v.amazonaws.com", data.aws_caller_identity.this.account_id, data.aws_region.current.name) + ecr_image = format("%v/%v:%v", local.ecr_address, aws_ecr_repository.this.id, "1.0") +} + +provider "docker" { + registry_auth { + address = local.ecr_address + username = data.aws_ecr_authorization_token.token.user_name + password = data.aws_ecr_authorization_token.token.password + } +} + +resource "docker_registry_image" "app" { + name = local.ecr_image + + build { + context = "context" + } +} diff --git a/examples/container-image/outputs.tf b/examples/container-image/outputs.tf new file mode 100644 index 00000000..ae487707 --- /dev/null +++ b/examples/container-image/outputs.tf @@ -0,0 +1,88 @@ +# Lambda Function +output "this_lambda_function_arn" { + description = "The ARN of the Lambda Function" + value = module.lambda_function_from_container_image.this_lambda_function_arn +} + +output "this_lambda_function_invoke_arn" { + description = "The Invoke ARN of the Lambda Function" + value = module.lambda_function_from_container_image.this_lambda_function_invoke_arn +} + +output "this_lambda_function_name" { + description = "The name of the Lambda Function" + value = module.lambda_function_from_container_image.this_lambda_function_name +} + +output "this_lambda_function_qualified_arn" { + description = "The ARN identifying your Lambda Function Version" + value = module.lambda_function_from_container_image.this_lambda_function_qualified_arn +} + +output "this_lambda_function_version" { + description = "Latest published version of Lambda Function" + value = module.lambda_function_from_container_image.this_lambda_function_version +} + +output "this_lambda_function_last_modified" { + description = "The date Lambda Function resource was last modified" + value = module.lambda_function_from_container_image.this_lambda_function_last_modified +} + +output "this_lambda_function_kms_key_arn" { + description = "The ARN for the KMS encryption key of Lambda Function" + value = module.lambda_function_from_container_image.this_lambda_function_kms_key_arn +} + +output "this_lambda_function_source_code_hash" { + description = "Base64-encoded representation of raw SHA-256 sum of the zip file" + value = module.lambda_function_from_container_image.this_lambda_function_source_code_hash +} + +output "this_lambda_function_source_code_size" { + description = "The size in bytes of the function .zip file" + value = module.lambda_function_from_container_image.this_lambda_function_source_code_size +} + +# Lambda Layer +output "this_lambda_layer_arn" { + description = "The ARN of the Lambda Layer with version" + value = module.lambda_function_from_container_image.this_lambda_layer_arn +} + +output "this_lambda_layer_layer_arn" { + description = "The ARN of the Lambda Layer without version" + value = module.lambda_function_from_container_image.this_lambda_layer_layer_arn +} + +output "this_lambda_layer_created_date" { + description = "The date Lambda Layer resource was created" + value = module.lambda_function_from_container_image.this_lambda_layer_created_date +} + +output "this_lambda_layer_source_code_size" { + description = "The size in bytes of the Lambda Layer .zip file" + value = module.lambda_function_from_container_image.this_lambda_layer_source_code_size +} + +output "this_lambda_layer_version" { + description = "The Lambda Layer version" + value = module.lambda_function_from_container_image.this_lambda_layer_version +} + +# IAM Role +output "lambda_role_arn" { + description = "The ARN of the IAM role created for the Lambda Function" + value = module.lambda_function_from_container_image.lambda_role_arn +} + +output "lambda_role_name" { + description = "The name of the IAM role created for the Lambda Function" + value = module.lambda_function_from_container_image.lambda_role_name +} + +# CloudWatch Log Group +output "lambda_cloudwatch_log_group_arn" { + description = "The ARN of the Cloudwatch Log Group" + value = module.lambda_function_from_container_image.lambda_cloudwatch_log_group_arn +} diff --git a/examples/container-image/variables.tf b/examples/container-image/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/container-image/versions.tf b/examples/container-image/versions.tf new file mode 100644 index 00000000..7980d563 --- /dev/null +++ b/examples/container-image/versions.tf @@ -0,0 +1,13 @@ +terraform { + required_version = ">= 0.12.6" + + required_providers { + aws = ">= 3.19" + random = ">= 2" + + docker = { + source = "kreuzwerker/docker" + version = ">= 2.8.0" + } + } +} diff --git a/examples/deploy/versions.tf b/examples/deploy/versions.tf index 16983974..957cf18c 100644 --- a/examples/deploy/versions.tf +++ b/examples/deploy/versions.tf @@ -2,7 +2,7 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.67" + aws = ">= 3.19" random = ">= 2" } } diff --git a/examples/multiple-regions/versions.tf b/examples/multiple-regions/versions.tf index 16983974..957cf18c 100644 --- a/examples/multiple-regions/versions.tf +++ b/examples/multiple-regions/versions.tf @@ -2,7 +2,7 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.67" + aws = ">= 3.19" random = ">= 2" } } diff --git a/examples/simple/versions.tf b/examples/simple/versions.tf index 16983974..957cf18c 100644 --- a/examples/simple/versions.tf +++ b/examples/simple/versions.tf @@ -2,7 +2,7 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.67" + aws = ">= 3.19" random = ">= 2" } } diff --git a/examples/with-efs/README.md b/examples/with-efs/README.md index ef2955d2..e280726e 100644 --- a/examples/with-efs/README.md +++ b/examples/with-efs/README.md @@ -1,7 +1,8 @@ -# AWS Lambda with EFS example +# AWS Lambda with EFS Example Configuration in this directory creates AWS Lambda Function deployed with Elastic File System (EFS) attached. + ## Usage To run this example you need to execute: @@ -20,14 +21,14 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | terraform | >= 0.12.6 | -| aws | >= 2.67 | +| aws | >= 3.19.0 | | random | >= 2 | ## Providers | Name | Version | |------|---------| -| aws | >= 2.67 | +| aws | >= 3.19.0 | | random | >= 2 | ## Inputs diff --git a/examples/with-efs/versions.tf b/examples/with-efs/versions.tf index 16983974..957cf18c 100644 --- a/examples/with-efs/versions.tf +++ b/examples/with-efs/versions.tf @@ -2,7 +2,7 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.67" + aws = ">= 3.19" random = ">= 2" } } diff --git a/examples/with-vpc/versions.tf b/examples/with-vpc/versions.tf index 16983974..957cf18c 100644 --- a/examples/with-vpc/versions.tf +++ b/examples/with-vpc/versions.tf @@ -2,7 +2,7 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.67" + aws = ">= 3.19" random = ">= 2" } } diff --git a/main.tf b/main.tf index 71e39d65..e8b44e5b 100644 --- a/main.tf +++ b/main.tf @@ -17,14 +17,16 @@ resource "aws_lambda_function" "this" { function_name = var.function_name description = var.description role = var.create_role ? aws_iam_role.lambda[0].arn : var.lambda_role - handler = var.handler + handler = var.package_type != "Zip" ? null : var.handler memory_size = var.memory_size reserved_concurrent_executions = var.reserved_concurrent_executions - runtime = var.runtime + runtime = var.package_type != "Zip" ? null : var.runtime layers = var.layers timeout = var.lambda_at_edge ? min(var.timeout, 5) : var.timeout publish = var.lambda_at_edge ? true : var.publish kms_key_arn = var.kms_key_arn + image_uri = var.image_uri + package_type = var.package_type filename = local.filename source_code_hash = (local.filename == null ? false : fileexists(local.filename)) && ! local.was_missing ? filebase64sha256(local.filename) : null @@ -33,6 +35,15 @@ resource "aws_lambda_function" "this" { s3_key = local.s3_key s3_object_version = local.s3_object_version + dynamic "image_config" { + for_each = length(var.image_config_entry_point) > 0 || length(var.image_config_command) > 0 || var.image_config_working_directory != null ? [true] : [] + content { + entry_point = var.image_config_entry_point + command = var.image_config_command + working_directory = var.image_config_working_directory + } + } + dynamic "environment" { for_each = length(keys(var.environment_variables)) == 0 ? [] : [true] content { diff --git a/modules/alias/versions.tf b/modules/alias/versions.tf index 0d661015..9d71257e 100644 --- a/modules/alias/versions.tf +++ b/modules/alias/versions.tf @@ -2,6 +2,6 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.67" + aws = ">= 3.19" } } diff --git a/modules/deploy/versions.tf b/modules/deploy/versions.tf index aca38083..c1227787 100644 --- a/modules/deploy/versions.tf +++ b/modules/deploy/versions.tf @@ -2,7 +2,7 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.67" + aws = ">= 3.19" local = ">= 1" null = ">= 2" } diff --git a/variables.tf b/variables.tf index 9c4b97a2..c6844627 100644 --- a/variables.tf +++ b/variables.tf @@ -151,6 +151,36 @@ variable "s3_object_tags" { default = {} } +variable "package_type" { + description = "The Lambda deployment package type. Valid options: Zip or Image" + type = string + default = "Zip" +} + +variable "image_uri" { + description = "The ECR image URI containing the function's deployment package." + type = string + default = null +} + +variable "image_config_entry_point" { + description = "The ENTRYPOINT for the docker image" + type = list(string) + default = [] + +} +variable "image_config_command" { + description = "The CMD for the docker image" + type = list(string) + default = [] +} + +variable "image_config_working_directory" { + description = "The working directory for the docker image" + type = string + default = null +} + ######## # Layer ######## diff --git a/versions.tf b/versions.tf index 9e316b17..07306751 100644 --- a/versions.tf +++ b/versions.tf @@ -2,7 +2,7 @@ terraform { required_version = ">= 0.12.6" required_providers { - aws = ">= 2.67" + aws = ">= 3.19" external = ">= 1" local = ">= 1" random = ">= 2"