Skip to content

Commit 63fed05

Browse files
authored
terraform module for lambda function (#1)
1 parent 84b6675 commit 63fed05

File tree

8 files changed

+479
-0
lines changed

8 files changed

+479
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
terraform.tfstate*
2+
.terraform*
3+
tfplan

EXAMPLE.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Lambda
2+
Below are the examples of calling this module.
3+
4+
## Create lambda resource with security group
5+
Lambda function will be created from the zip file named lambda.zip uploaded in s3 bucket where key is path for zip file in the bucket. Function url is used for security and access control which can be set to false also. Environment variables can also be passed from parameter store if required. Secuirty group and subnet ids are passed as list if required.
6+
```
7+
module "lambda_test" {
8+
source = "./lambda"
9+
function_name = "${var.prefix}-test-lambda"
10+
handler = "lambda.handler"
11+
lambda_runtime = "python3.x"
12+
s3_bucket = "${var.prefix}-test-lambda"
13+
s3_key = "lambda.zip"
14+
description = "Lambda resource with security group"
15+
security_group_ids = ["sg-1234567"]
16+
subnets = ["subnet-1", "subnet-2"]
17+
function_url = true
18+
function_url_cors = {
19+
allow_credentials = false
20+
allow_headers = ["header-1", "header-2"]
21+
allow_methods = ["GET", "POST"]
22+
allow_origins = [
23+
"*"
24+
]
25+
}
26+
environment_variables = {
27+
LOG_LEVEL = "info"
28+
BASE_URL = "https://example.com/v1/info"
29+
}
30+
env_vars_from_parameter_store = {
31+
DB_HOST = "/${var.prefix}/DB_HOST"
32+
DB_NAME = "/${var.prefix}/DB_NAME"
33+
DB_PORT = "/${var.prefix}/DB_PORT"
34+
}
35+
logs_retention = 14
36+
}
37+
```
38+
39+
## Allow apigw to invoke lambda
40+
Api gateway will invoke the lambda function where function is created from zip file named lambda.zip uploaded in s3 bucket where key is path for zip file in the bucket.
41+
```
42+
module "lambda_test" {
43+
source = "./lambda"
44+
function_name = "${var.prefix}-test-lambda"
45+
handler = "lambda.handler"
46+
lambda_runtime = "python3.x"
47+
s3_bucket = "${var.prefix}-test-lambda"
48+
s3_key = "lambda.zip"
49+
description = "Allow apigw to invoke lambda"
50+
apigw_execution_arn = "arn:aws:apigateway:region::resource-path-specifier"
51+
logs_retention = 14
52+
}
53+
```
54+
55+
## Create Lambda function from zip file when source file is passed
56+
Here a file named test.py is present in same directory and in output_path, we define the name for zip file which is created by test.py. Output_path will create zip file with name lambda.zip
57+
```
58+
module "lambda_test" {
59+
source = ".lambda"
60+
function_name = "${var.prefix}-test-lambda"
61+
handler = "lambda.handler"
62+
lambda_runtime = "python3.x"
63+
source_file = "test.py"
64+
output_path = "lambda.zip"
65+
lambda_artifacts_bucket = "${var.prefix}-test-lambda"
66+
description = "Test lambda"
67+
logs_retention = 14
68+
}
69+
```

README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
## Requirements
2+
3+
| Name | Version |
4+
|------|---------|
5+
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0 |
6+
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 5.0 |
7+
8+
## Providers
9+
10+
| Name | Version |
11+
|------|---------|
12+
| <a name="provider_archive"></a> [archive](#provider\_archive) | n/a |
13+
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 5.0 |
14+
15+
## Modules
16+
17+
No modules.
18+
19+
## Resources
20+
21+
| Name | Type |
22+
|------|------|
23+
| [aws_cloudwatch_log_group.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
24+
| [aws_iam_role.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
25+
| [aws_iam_role_policy_attachment.lambda_basic_execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
26+
| [aws_iam_role_policy_attachment.lambda_vpc_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
27+
| [aws_lambda_function.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource |
28+
| [aws_lambda_function_url.function_url](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function_url) | resource |
29+
| [aws_lambda_permission.api](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource |
30+
| [aws_lambda_permission.cognito](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource |
31+
| [aws_lambda_permission.eventbridge](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource |
32+
| [aws_lambda_permission.sqs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource |
33+
| [archive_file.lambda](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source |
34+
| [aws_iam_policy_document.lambda_service_trust_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
35+
| [aws_s3_object.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/s3_object) | data source |
36+
| [aws_ssm_parameter.env_vars_from_parameter_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source |
37+
38+
## Inputs
39+
40+
| Name | Description | Type | Default | Required |
41+
|------|-------------|------|---------|:--------:|
42+
| <a name="input_apigw_execution_arn"></a> [apigw\_execution\_arn](#input\_apigw\_execution\_arn) | Apigw execution arn | `list` | `[]` | no |
43+
| <a name="input_cognito_pool_arn"></a> [cognito\_pool\_arn](#input\_cognito\_pool\_arn) | Cognito pool arn | `string` | `""` | no |
44+
| <a name="input_description"></a> [description](#input\_description) | Lambda function description | `any` | n/a | yes |
45+
| <a name="input_env_vars_from_parameter_store"></a> [env\_vars\_from\_parameter\_store](#input\_env\_vars\_from\_parameter\_store) | Lambda environment variables from SSM parameter store | `map(any)` | `{}` | no |
46+
| <a name="input_environment_variables"></a> [environment\_variables](#input\_environment\_variables) | Environment Variables for Lambda Functions | `map(any)` | `{}` | no |
47+
| <a name="input_eventbridge_rule_arn"></a> [eventbridge\_rule\_arn](#input\_eventbridge\_rule\_arn) | Eventbridge rule arn | `string` | `""` | no |
48+
| <a name="input_function_name"></a> [function\_name](#input\_function\_name) | Lambda function name | `any` | n/a | yes |
49+
| <a name="input_function_url"></a> [function\_url](#input\_function\_url) | Create lambda function url | `bool` | `false` | no |
50+
| <a name="input_function_url_cors"></a> [function\_url\_cors](#input\_function\_url\_cors) | Function url cors | `any` | `[]` | no |
51+
| <a name="input_handler"></a> [handler](#input\_handler) | Name of Handler | `any` | n/a | yes |
52+
| <a name="input_lambda_memory"></a> [lambda\_memory](#input\_lambda\_memory) | Required Memory for Lambda function | `number` | `128` | no |
53+
| <a name="input_lambda_runtime"></a> [lambda\_runtime](#input\_lambda\_runtime) | Lambda language | `any` | n/a | yes |
54+
| <a name="input_lambda_timeout"></a> [lambda\_timeout](#input\_lambda\_timeout) | Required Timeout for Lambda function | `number` | `5` | no |
55+
| <a name="input_layers_arn"></a> [layers\_arn](#input\_layers\_arn) | Lambda layer arn | `list(string)` | `null` | no |
56+
| <a name="input_logs_retention"></a> [logs\_retention](#input\_logs\_retention) | Specifies the number of days you want to retain log events in the specified log group | `number` | `null` | no |
57+
| <a name="input_output_path"></a> [output\_path](#input\_output\_path) | The name for the zip file created with the file described in source\_file | `string` | `""` | no |
58+
| <a name="input_prefix"></a> [prefix](#input\_prefix) | Prefix for resources | `string` | `""` | no |
59+
| <a name="input_publish"></a> [publish](#input\_publish) | Publish lambda function version | `bool` | `false` | no |
60+
| <a name="input_s3_bucket"></a> [s3\_bucket](#input\_s3\_bucket) | Lambda artifacts bucket | `string` | `""` | no |
61+
| <a name="input_s3_key"></a> [s3\_key](#input\_s3\_key) | Path of the zip file which is present in s3 bucket | `string` | `""` | no |
62+
| <a name="input_security_group_ids"></a> [security\_group\_ids](#input\_security\_group\_ids) | Security geoup id | `list(any)` | `null` | no |
63+
| <a name="input_source_file"></a> [source\_file](#input\_source\_file) | Lambda source file | `string` | `""` | no |
64+
| <a name="input_sqs_queue_arn"></a> [sqs\_queue\_arn](#input\_sqs\_queue\_arn) | SQS queue arn | `string` | `""` | no |
65+
| <a name="input_subnets"></a> [subnets](#input\_subnets) | Subnets | `list(any)` | `null` | no |
66+
67+
## Outputs
68+
69+
| Name | Description |
70+
|------|-------------|
71+
| <a name="output_arn"></a> [arn](#output\_arn) | Lambda ARN |
72+
| <a name="output_function_name"></a> [function\_name](#output\_function\_name) | Name of the Lambda function. |
73+
| <a name="output_function_url"></a> [function\_url](#output\_function\_url) | Function url |
74+
| <a name="output_invoke_arn"></a> [invoke\_arn](#output\_invoke\_arn) | Lambda Invoke ARN |
75+
| <a name="output_role_arn"></a> [role\_arn](#output\_role\_arn) | Role arn |
76+
| <a name="output_role_name"></a> [role\_name](#output\_role\_name) | Role name |

iam.tf

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# --- lambda/iam.tf ---
2+
resource "aws_iam_role" "lambda" {
3+
name = "${var.function_name}-lambda"
4+
assume_role_policy = data.aws_iam_policy_document.lambda_service_trust_policy.json
5+
}
6+
7+
data "aws_iam_policy_document" "lambda_service_trust_policy" {
8+
statement {
9+
effect = "Allow"
10+
actions = ["sts:AssumeRole"]
11+
12+
principals {
13+
type = "Service"
14+
identifiers = ["lambda.amazonaws.com"]
15+
}
16+
}
17+
}
18+
19+
resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
20+
count = var.subnets != null && var.security_group_ids != null ? 1 : 0
21+
role = aws_iam_role.lambda.name
22+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
23+
}
24+
25+
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
26+
role = aws_iam_role.lambda.id
27+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
28+
}

main.tf

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# ------------------------------------------------------------------------------
2+
# CREATE EITHER S3 OBJECT OR ARCHIVE FILE
3+
# ------------------------------------------------------------------------------
4+
5+
# Here lambda function will be created from zip file uploaded in s3 bucket where key is path for zip file in the bucket.
6+
data "aws_s3_object" "lambda" {
7+
count = var.s3_bucket != "" && var.s3_key != "" ? 1 : 0
8+
bucket = var.s3_bucket
9+
key = var.s3_key
10+
}
11+
12+
# Here a zip file will be created when source_file is passed and lambda function will be created from that zip file
13+
data "archive_file" "lambda" {
14+
count = var.source_file != "" && var.output_path != "" ? 1 : 0
15+
type = "zip"
16+
source_file = var.source_file
17+
output_path = var.output_path
18+
}
19+
20+
data "aws_ssm_parameter" "env_vars_from_parameter_store" {
21+
for_each = var.env_vars_from_parameter_store
22+
name = each.value
23+
}
24+
25+
locals {
26+
env_vars_from_parameter_store = length(keys(var.env_vars_from_parameter_store)) == 0 ? {} : zipmap(
27+
[for key, value in var.env_vars_from_parameter_store : key],
28+
[for parameter in data.aws_ssm_parameter.env_vars_from_parameter_store : parameter.value]
29+
)
30+
environment_variables = length(keys(var.environment_variables)) == 0 && length(keys(local.env_vars_from_parameter_store)) == 0 ? [] : [merge(var.environment_variables, local.env_vars_from_parameter_store)]
31+
}
32+
33+
# ------------------------------------------------------------------------------
34+
# CREATE LAMBDA FUNCTION
35+
# ------------------------------------------------------------------------------
36+
#tfsec:ignore:aws-lambda-enable-tracing
37+
resource "aws_lambda_function" "lambda" {
38+
function_name = var.function_name
39+
description = var.description
40+
handler = var.handler
41+
memory_size = var.lambda_memory
42+
role = aws_iam_role.lambda.arn
43+
runtime = var.lambda_runtime
44+
timeout = var.lambda_timeout
45+
s3_bucket = try(data.aws_s3_object.lambda[0].bucket, null)
46+
s3_key = try(data.aws_s3_object.lambda[0].key, null)
47+
s3_object_version = try(data.aws_s3_object.lambda[0].version_id, null)
48+
filename = try(data.archive_file.lambda[0].output_path, null)
49+
layers = var.layers_arn
50+
publish = var.publish
51+
52+
dynamic "environment" {
53+
for_each = length(local.environment_variables) == 0 ? [] : local.environment_variables
54+
content {
55+
variables = environment.value
56+
}
57+
}
58+
59+
dynamic "vpc_config" {
60+
for_each = var.subnets != null && var.security_group_ids != null ? [true] : []
61+
content {
62+
security_group_ids = var.security_group_ids
63+
subnet_ids = var.subnets
64+
}
65+
}
66+
}
67+
68+
# ------------------------------------------------------------------------------
69+
# ASSIGN PERMISSION TO API GATEWAY, COGNITO, SQS AND EVENTBRIDGE
70+
# ------------------------------------------------------------------------------
71+
72+
resource "aws_lambda_permission" "api" {
73+
count = length(var.apigw_execution_arn) > 0 ? 1 : 0
74+
statement_id = "AllowAPIGWLambdaInvoke"
75+
action = "lambda:InvokeFunction"
76+
function_name = aws_lambda_function.lambda.function_name
77+
principal = "apigateway.amazonaws.com"
78+
source_arn = "${var.apigw_execution_arn}/*/*/*"
79+
}
80+
81+
resource "aws_lambda_permission" "cognito" {
82+
count = length(var.cognito_pool_arn) > 0 ? 1 : 0
83+
statement_id = "AllowCognitoPoolLambdaInvoke"
84+
action = "lambda:InvokeFunction"
85+
function_name = aws_lambda_function.lambda.function_name
86+
principal = "cognito-idp.amazonaws.com"
87+
source_arn = var.cognito_pool_arn
88+
}
89+
90+
resource "aws_lambda_permission" "sqs" {
91+
count = length(var.sqs_queue_arn) > 0 ? 1 : 0
92+
statement_id = "AllowExecutionFromSQS"
93+
action = "lambda:InvokeFunction"
94+
function_name = aws_lambda_function.lambda.function_name
95+
principal = "sqs.amazonaws.com"
96+
source_arn = var.sqs_queue_arn
97+
}
98+
99+
resource "aws_lambda_permission" "eventbridge" {
100+
count = length(var.eventbridge_rule_arn) > 0 ? 1 : 0
101+
statement_id = "AllowExecutionFromEventBridge"
102+
action = "lambda:InvokeFunction"
103+
function_name = aws_lambda_function.lambda.function_name
104+
principal = "events.amazonaws.com"
105+
source_arn = var.eventbridge_rule_arn
106+
}
107+
108+
# ------------------------------------------------------------------------------
109+
# LAMBDA LOG RETENTION
110+
# ------------------------------------------------------------------------------
111+
#tfsec:ignore:aws-cloudwatch-log-group-customer-key
112+
resource "aws_cloudwatch_log_group" "lambda" {
113+
name = "/aws/lambda/${aws_lambda_function.lambda.function_name}"
114+
retention_in_days = var.logs_retention
115+
}
116+
117+
# ------------------------------------------------------------------------------
118+
# CREATE LAMBDA FUNCTION URL
119+
# ------------------------------------------------------------------------------
120+
resource "aws_lambda_function_url" "function_url" {
121+
count = var.function_url ? 1 : 0
122+
function_name = aws_lambda_function.lambda.function_name
123+
authorization_type = "NONE"
124+
125+
dynamic "cors" {
126+
for_each = length(keys(var.function_url_cors)) == 0 ? [] : [var.function_url_cors]
127+
128+
content {
129+
allow_credentials = try(cors.value.allow_credentials, null)
130+
allow_headers = try(cors.value.allow_headers, null)
131+
allow_methods = try(cors.value.allow_methods, null)
132+
allow_origins = try(cors.value.allow_origins, null)
133+
expose_headers = try(cors.value.expose_headers, null)
134+
max_age = try(cors.value.max_age, null)
135+
}
136+
}
137+
}

outputs.tf

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# --- lambda/outputs.tf ---
2+
output "function_name" {
3+
description = "Name of the Lambda function."
4+
value = aws_lambda_function.lambda.function_name
5+
}
6+
7+
output "invoke_arn" {
8+
description = "Lambda Invoke ARN"
9+
value = aws_lambda_function.lambda.invoke_arn
10+
}
11+
12+
output "arn" {
13+
description = "Lambda ARN"
14+
value = aws_lambda_function.lambda.arn
15+
}
16+
17+
output "function_url" {
18+
description = "Function url"
19+
value = var.function_url ? aws_lambda_function_url.function_url[0].function_url : null
20+
}
21+
22+
output "role_name" {
23+
description = "Role name"
24+
value = aws_iam_role.lambda.arn
25+
}
26+
27+
output "role_arn" {
28+
description = "Role arn"
29+
value = aws_iam_role.lambda.name
30+
}

provider.tf

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
terraform {
2+
required_version = ">= 1.0"
3+
4+
required_providers {
5+
aws = {
6+
source = "hashicorp/aws"
7+
version = ">= 5.0"
8+
}
9+
}
10+
}

0 commit comments

Comments
 (0)