Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
- uses: actions/checkout@master
- uses: hashicorp/setup-terraform@v1
with:
terraform_version: 1.3.0
terraform_version: 1.7.4
- id: Init
run: terraform init -no-color
- id: Fmt
Expand Down
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform 1.7.4
62 changes: 29 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,52 +32,48 @@ No requirements.

## Modules

No modules.
| Name | Source | Version |
|------|--------|---------|
| <a name="module_lambda_function"></a> [lambda\_function](#module\_lambda\_function) | terraform-aws-modules/lambda/aws | 7.2.6 |

## Resources

| Name | Type |
|------|------|
| [aws_cloudwatch_log_group.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
| [aws_cloudwatch_metric_alarm.lambda_errors](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_metric_alarm) | resource |
| [aws_iam_role_policy_attachment.lambda_basic_execution](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_iam_role_policy_attachment.lambda_insights](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_iam_role_policy_attachment.lambda_networking](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_iam_role_policy_attachment.lambda_xray](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource |
| [aws_lambda_function.lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource |
| [aws_s3_bucket_object.lambda_deploy_object](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_object) | resource |
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
| [aws_sns_topic.notifications](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/sns_topic) | data source |
| [aws_ssm_parameter.account_name](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source |
| [aws_ssm_parameter.deployment_bucket_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source |
| [aws_ssm_parameter.global_account_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source |
| [aws_ssm_parameter.kms_key_arn](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_deployment_bucket_id"></a> [deployment\_bucket\_id](#input\_deployment\_bucket\_id) | ID of S3 bucket that should store our deployment artifacts. Will use the /account/DEPLOYMENT\_BUCKET\_ID value from SSM unless specified otherwise. | `string` | `null` | no |
| <a name="input_description"></a> [description](#input\_description) | Description of the Lambda Function | `string` | `null` | no |
| <a name="input_environment"></a> [environment](#input\_environment) | Environment variables to be passed to the function | `map(string)` | `{}` | no |
| <a name="input_error_rate_alarm_threshold"></a> [error\_rate\_alarm\_threshold](#input\_error\_rate\_alarm\_threshold) | Error rate (in percent, 1-100) at which to trigger an alarm notification | `number` | `25` | no |
| <a name="input_git_sha"></a> [git\_sha](#input\_git\_sha) | Hash generated by `git hash-object` in source repo and used to determine whether a lambda needs to be updated | `string` | `null` | no |
| <a name="input_handler"></a> [handler](#input\_handler) | Name of the handler function inside the artifact (https://docs.aws.amazon.com/lambda/latest/dg/configuration-console.html) | `string` | n/a | yes |
| <a name="input_layer_arns"></a> [layer\_arns](#input\_layer\_arns) | List of ARNs for layers to use with the function | `list(string)` | `[]` | no |
| <a name="input_log_retention_in_days"></a> [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | Number of days to keep function logs in Cloudwatch | `number` | `365` | no |
| <a name="input_memory_size"></a> [memory\_size](#input\_memory\_size) | Amount of memory (in MB) to allocate to the function | `number` | `128` | no |
| <a name="input_name"></a> [name](#input\_name) | Name for the function | `string` | n/a | yes |
| <a name="input_notifications_topic_arn"></a> [notifications\_topic\_arn](#input\_notifications\_topic\_arn) | SNS topic to send error notifications | `string` | n/a | yes |
| <a name="input_path"></a> [path](#input\_path) | Local path to a zipped artifact containing the function code | `string` | n/a | yes |
| <a name="input_reserved_concurrent_executions"></a> [reserved\_concurrent\_executions](#input\_reserved\_concurrent\_executions) | Reserved concurrent executions (none by default) | `number` | `null` | no |
| <a name="input_role_name"></a> [role\_name](#input\_role\_name) | Name of the execution role for the function. It does not need to include logging/networking permissions - those policies will be added automatically. | `string` | n/a | yes |
| <a name="input_runtime"></a> [runtime](#input\_runtime) | Language runtime for the function (https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html) | `string` | n/a | yes |
| <a name="input_security_group_ids"></a> [security\_group\_ids](#input\_security\_group\_ids) | Security groups for the function (if run in a VPC) | `list(string)` | `[]` | no |
| <a name="input_subnet_ids"></a> [subnet\_ids](#input\_subnet\_ids) | Subnets for the function (if run in a VPC) | `list(string)` | `[]` | no |
| <a name="input_tags"></a> [tags](#input\_tags) | Tags to apply to created resources | `map(any)` | `{}` | no |
| <a name="input_timeout"></a> [timeout](#input\_timeout) | Function timeout in seconds | `number` | `15` | no |
| <a name="input_allowed_triggers"></a> [allowed\_triggers](#input\_allowed\_triggers) | n/a | `map(any)` | `{}` | no |
| <a name="input_description"></a> [description](#input\_description) | n/a | `string` | `null` | no |
| <a name="input_ecr_address"></a> [ecr\_address](#input\_ecr\_address) | n/a | `string` | n/a | yes |
| <a name="input_ecr_arn"></a> [ecr\_arn](#input\_ecr\_arn) | n/a | `string` | n/a | yes |
| <a name="input_engine_name"></a> [engine\_name](#input\_engine\_name) | n/a | `string` | n/a | yes |
| <a name="input_environment_variables"></a> [environment\_variables](#input\_environment\_variables) | n/a | `map(any)` | `{}` | no |
| <a name="input_event_source_mapping"></a> [event\_source\_mapping](#input\_event\_source\_mapping) | Map of event source mappings | `map(any)` | `{}` | no |
| <a name="input_function_name"></a> [function\_name](#input\_function\_name) | n/a | `string` | n/a | yes |
| <a name="input_handler"></a> [handler](#input\_handler) | n/a | `string` | `null` | no |
| <a name="input_image_tag"></a> [image\_tag](#input\_image\_tag) | n/a | `string` | `"latest"` | no |
| <a name="input_memory_size"></a> [memory\_size](#input\_memory\_size) | n/a | `number` | `1024` | no |
| <a name="input_policy_statements"></a> [policy\_statements](#input\_policy\_statements) | n/a | `map(any)` | `{}` | no |
| <a name="input_security_group_ids"></a> [security\_group\_ids](#input\_security\_group\_ids) | n/a | `list(string)` | `[]` | no |
| <a name="input_subnet_ids"></a> [subnet\_ids](#input\_subnet\_ids) | n/a | `list(string)` | `[]` | no |
| <a name="input_timeout"></a> [timeout](#input\_timeout) | n/a | `string` | `"60"` | no |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_function_arn"></a> [function\_arn](#output\_function\_arn) | ARN of the created/updated Lambda function |
| <a name="output_function_invoke_arn"></a> [function\_invoke\_arn](#output\_function\_invoke\_arn) | ARN for invoking the created Lambda function |
| <a name="output_function_name"></a> [function\_name](#output\_function\_name) | Name of the created Lambda function |
| <a name="output_function_version"></a> [function\_version](#output\_function\_version) | Version of the created/updated Lambda function |
| <a name="output_function_arn"></a> [function\_arn](#output\_function\_arn) | n/a |
| <a name="output_function_name"></a> [function\_name](#output\_function\_name) | n/a |
| <a name="output_function_version"></a> [function\_version](#output\_function\_version) | n/a |
| <a name="output_lambda_role_arn"></a> [lambda\_role\_arn](#output\_lambda\_role\_arn) | n/a |
| <a name="output_lambda_role_name"></a> [lambda\_role\_name](#output\_lambda\_role\_name) | n/a |
| <a name="output_qualified_function_arn"></a> [qualified\_function\_arn](#output\_qualified\_function\_arn) | n/a |
18 changes: 17 additions & 1 deletion data.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

data "aws_ssm_parameter" "account_name" {
name = "/account/name"
}

data "aws_sns_topic" "notifications" {
name = "account-notifications"
}

data "aws_ssm_parameter" "deployment_bucket_id" {
name = "/account/DEPLOYMENT_BUCKET_ID"
}

data "aws_ssm_parameter" "kms_key_arn" {
name = "/account/KMS_KEY_ARN"
}

data "aws_ssm_parameter" "global_account_id" {
name = "/global/account_id"
}
215 changes: 61 additions & 154 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -23,166 +23,73 @@
*/

locals {
deploy_artifact_key = "deploy.zip"
deployment_bucket_id = coalesce(var.deployment_bucket_id, data.aws_ssm_parameter.deployment_bucket_id.value)
source_hash = coalesce(var.git_sha, try(filebase64sha256(var.path), null))
role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.role_name}"
description = coalesce(var.description, "Highwing Engine - ${title(var.engine_name)}")
handler = coalesce(var.handler, "engines/${var.engine_name}/lambda.handler")

environment = data.aws_ssm_parameter.account_name.value
environment_variables = (
startswith(local.environment, "production")
? var.environment_variables
: merge(var.environment_variables, {
DEV_MODE_ENABLED = true
})
)
}

# Configure default role permissions
resource "aws_iam_role_policy_attachment" "lambda_basic_execution" {
role = var.role_name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

resource "aws_iam_role_policy_attachment" "lambda_networking" {
role = var.role_name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
}

resource "aws_iam_role_policy_attachment" "lambda_xray" {
role = var.role_name
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
}

resource "aws_iam_role_policy_attachment" "lambda_insights" {
role = var.role_name
policy_arn = "arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy"
}

# S3 object to hold the deployed artifact
resource "aws_s3_bucket_object" "lambda_deploy_object" {
count = var.image_uri == null ? 1 : 0
bucket = local.deployment_bucket_id
key = "${var.name}/${local.deploy_artifact_key}"
source = var.path
source_hash = md5(local.source_hash)
tags = merge(var.tags, {
GitSHA = var.git_sha
})
}

# The Lambda function itself
resource "aws_lambda_function" "lambda" {
function_name = var.name
description = var.description
handler = var.handler
layers = var.image_uri == null ? concat(
var.layer_arns,
["arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:14"] # ARN for us-east-1
) : null
memory_size = var.memory_size
publish = true
reserved_concurrent_executions = var.reserved_concurrent_executions
role = local.role_arn
runtime = var.runtime
package_type = var.image_uri == null ? "Zip" : "Image"
s3_bucket = var.image_uri == null ? local.deployment_bucket_id : null
s3_key = var.image_uri == null ? aws_s3_bucket_object.lambda_deploy_object[0].key : null
s3_object_version = var.image_uri == null ? aws_s3_bucket_object.lambda_deploy_object[0].version_id : null
image_uri = var.image_uri
tags = var.tags
timeout = var.timeout

dynamic "vpc_config" {
for_each = length(var.subnet_ids) > 0 ? [1] : []
content {
subnet_ids = var.subnet_ids
security_group_ids = var.security_group_ids
}
}

dynamic "environment" {
for_each = length(var.environment) > 0 ? [1] : []
content {
variables = var.environment
}
}

tracing_config {
mode = "Active"
}

lifecycle {
precondition {
condition = (var.image_uri != null && var.path == null) || (var.image_uri == null && var.path != null)
error_message = "Cannot specify image_uri AND path"
module "lambda_function" {
source = "terraform-aws-modules/lambda/aws"
version = "7.2.6"

function_name = var.function_name
description = local.description
handler = local.handler
memory_size = var.memory_size
publish = true
timeout = var.timeout

vpc_subnet_ids = var.subnet_ids
vpc_security_group_ids = var.security_group_ids

attach_cloudwatch_logs_policy = true
attach_dead_letter_policy = true
attach_network_policy = true
attach_tracing_policy = true

allowed_triggers = var.allowed_triggers

number_of_policies = 2
policies = [
"arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy",
"arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole"
]
attach_policies = true

attach_policy_statements = length(var.policy_statements) > 0 ? true : false
policy_statements = merge(var.policy_statements, {
ecr = {
effect = "Allow"
actions = [
"ecr:BatchGetImage",
"ecr:GetDownloadUrlForLayer"
]
resources = [var.ecr_arn]
}
precondition {
condition = (var.image_uri != null && var.handler == null) || (var.image_uri == null && var.handler != null)
error_message = "Cannot specify image_uri AND handler"
}
precondition {
condition = (var.image_uri != null && length(var.layer_arns) == 0) || (var.image_uri == null)
error_message = "Cannot specify image_uri AND layer_arns"
}
precondition {
condition = (var.image_uri != null && var.path == null) || (var.image_uri == null && var.path != null)
error_message = "Cannot specify image_uri AND path"
}
precondition {
condition = (var.image_uri != null && var.runtime == null) || (var.image_uri == null && var.path != null)
error_message = "Cannot specify image_uri AND runtime"
}
}

}

# An alarm to notify of function errors
resource "aws_cloudwatch_metric_alarm" "lambda_errors" {
alarm_actions = [var.notifications_topic_arn]
alarm_description = "This metric monitors the error rate on the ${var.name} lambda"
alarm_name = "${var.name} - High Error Rate"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = "2"
threshold = var.error_rate_alarm_threshold
treat_missing_data = "notBreaching"

metric_query {
id = "error_rate"
expression = "errors/invocations*100"
label = "Error Rate"
return_data = "true"
}

metric_query {
id = "errors"

metric {
metric_name = "Errors"
namespace = "AWS/Lambda"
period = "600"
stat = "Sum"
unit = "Count"

dimensions = {
FunctionName = aws_lambda_function.lambda.function_name
}
}
}
})

metric_query {
id = "invocations"
cloudwatch_logs_retention_in_days = 1096
dead_letter_target_arn = data.aws_sns_topic.notifications.arn
kms_key_arn = data.aws_ssm_parameter.kms_key_arn.value
tracing_mode = "Active"

metric {
metric_name = "Invocations"
namespace = "AWS/Lambda"
period = "600"
stat = "Sum"
unit = "Count"
create_package = false
image_uri = "${var.ecr_address}:lambda_${var.image_tag}"
package_type = "Image"
architectures = ["x86_64"]

dimensions = {
FunctionName = aws_lambda_function.lambda.function_name
}
}
}
event_source_mapping = var.event_source_mapping

tags = var.tags
environment_variables = merge(local.environment_variables, {
DD_SERVICE = replace(var.function_name, "-", "_")
})
}

# Configure logging in Cloudwatch
resource "aws_cloudwatch_log_group" "lambda" {
name = "/aws/lambda/${var.name}"
retention_in_days = var.log_retention_in_days
tags = var.tags
}
26 changes: 15 additions & 11 deletions outputs.tf
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
output "function_name" {
description = "Name of the created Lambda function"
value = aws_lambda_function.lambda.function_name
output "function_arn" {
value = module.lambda_function.lambda_function_arn
}

output "function_arn" {
description = "ARN of the created/updated Lambda function"
value = aws_lambda_function.lambda.arn
output "qualified_function_arn" {
value = module.lambda_function.lambda_function_qualified_arn
}

output "function_name" {
value = module.lambda_function.lambda_function_name
}

output "function_version" {
description = "Version of the created/updated Lambda function"
value = aws_lambda_function.lambda.version
value = module.lambda_function.lambda_function_version
}

output "lambda_role_arn" {
value = module.lambda_function.lambda_role_arn
}

output "function_invoke_arn" {
description = "ARN for invoking the created Lambda function"
value = aws_lambda_function.lambda.invoke_arn
output "lambda_role_name" {
value = module.lambda_function.lambda_role_name
}
Loading
Loading