diff --git a/README.md b/README.md index 2db5bf2..b939f5a 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,12 @@ Currently below AWS services & resources are supported: ## Usage -## Examples +### Basic example + +```bash +terraform init + +terraform apply + +awscurl --service lambda --region us-east-1 --header 'Content-Type: application/json' --header 'Accept: application/json' --data '{"message": "example_post", "key": "118", "transport": "mail"}' "$(terraform output -raw lambda_url_producer)" +``` diff --git a/examples/basic/README.md b/examples/basic/README.md index 3f3de6d..e3a50dd 100644 --- a/examples/basic/README.md +++ b/examples/basic/README.md @@ -6,29 +6,42 @@ | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.5.0 | +| [archive](#requirement\_archive) | ~> 2.0 | ## Providers -No providers. +| Name | Version | +|------|---------| +| [archive](#provider\_archive) | ~> 2.0 | ## Modules | Name | Source | Version | |------|--------|---------| -| [dynamodb](#module\_dynamodb) | sebastianczech/free-serverless-modules/aws//modules/dynamodb | n/a | -| [lambda](#module\_lambda) | sebastianczech/free-serverless-modules/aws//modules/lambda | n/a | -| [sns](#module\_sns) | sebastianczech/free-serverless-modules/aws//modules/sns | n/a | -| [sqs](#module\_sqs) | sebastianczech/free-serverless-modules/aws//modules/sqs | n/a | +| [dynamodb](#module\_dynamodb) | ../../modules/dynamodb | n/a | +| [lambda\_consumer](#module\_lambda\_consumer) | ../../modules/lambda | n/a | +| [lambda\_producer](#module\_lambda\_producer) | ../../modules/lambda | n/a | +| [sns](#module\_sns) | ../../modules/sns | n/a | +| [sqs](#module\_sqs) | ../../modules/sqs | n/a | ## Resources -No resources. +| Name | Type | +|------|------| +| [archive_file.consumer](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source | +| [archive_file.producer](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source | ## Inputs -No inputs. +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [iam\_username](#input\_iam\_username) | The name of the IAM user | `string` | n/a | yes | +| [mail](#input\_mail) | The email address used for notifications | `string` | n/a | yes | +| [prefix](#input\_prefix) | The prefix for the resources | `string` | n/a | yes | ## Outputs -No outputs. +| Name | Description | +|------|-------------| +| [lambda\_url\_producer](#output\_lambda\_url\_producer) | The URL of the Lambda producer | diff --git a/examples/basic/files/code.py b/examples/basic/files/code.py deleted file mode 100644 index e69de29..0000000 diff --git a/examples/basic/files/code.zip b/examples/basic/files/code.zip deleted file mode 100644 index a14d805..0000000 Binary files a/examples/basic/files/code.zip and /dev/null differ diff --git a/examples/basic/files/consumer.py b/examples/basic/files/consumer.py new file mode 100644 index 0000000..81a53cc --- /dev/null +++ b/examples/basic/files/consumer.py @@ -0,0 +1,42 @@ +import json +import boto3 +import ast + + +print('Loading function') +sns = boto3.client('sns') +dynamodb = boto3.client('dynamodb') +topic_url = "${topic_url}" +table_url = "${table_url}" + + +def lambda_handler(event, context): + for record in event['Records']: + payload = record["body"].replace("'",'"') + print("Received SQS message: " + payload) + data = json.loads(payload) + print("Converted data: " + str(data)) + + if "message" in data and "key" in data: + item = dynamodb.put_item( + TableName=table_url, + Item= + { + 'ID': { + 'S': str(data["key"]), + }, + 'message': { + 'S': str(data["message"]), + } + } + ) + print("Insert item into DynamoDB: " + item['ResponseMetadata']['RequestId']) + + if "transport" in data and data["transport"] == "mail": + subject = "Message from SQS" + response = sns.publish( + TopicArn=topic_url, + Message=str(payload), + Subject=subject, + ) + print("Send SNS event: " + response['MessageId']) diff --git a/examples/basic/files/consumer.zip b/examples/basic/files/consumer.zip new file mode 100644 index 0000000..d2e4d9e Binary files /dev/null and b/examples/basic/files/consumer.zip differ diff --git a/examples/basic/files/producer.py b/examples/basic/files/producer.py new file mode 100644 index 0000000..2cd3299 --- /dev/null +++ b/examples/basic/files/producer.py @@ -0,0 +1,70 @@ +import json +import urllib.parse +import boto3 + + +# Function based on documentation: +# https://boto3.amazonaws.com/v1/documentation/api/latest/guide/sqs-example-sending-receiving-msgs.html + + +print('Loading function') +sqs = boto3.client('sqs') +queue_url = "${queue_url}" + + +def lambda_handler(event, context): + print("Received event: " + json.dumps(event, indent=2)) + + if 'body' in event: + message = str(json.loads(str(event['body']))) + elif 'queryStringParameters' in event: + message = str(event['queryStringParameters']) + else: + message = "empty message" + + response = sqs.send_message( + QueueUrl=queue_url, + DelaySeconds=10, + MessageAttributes={ + 'Deployment': { + 'DataType': 'String', + 'StringValue': 'Terraform' + }, + 'Language': { + 'DataType': 'String', + 'StringValue': 'Python' + }, + 'Version': { + 'DataType': 'Number', + 'StringValue': '1' + }, + 'ARN': { + 'DataType': 'String', + 'StringValue': context.invoked_function_arn + }, + 'RequestID': { + 'DataType': 'String', + 'StringValue': context.aws_request_id + }, + 'Message': { + 'DataType': 'String', + 'StringValue': message + }, + }, + MessageBody=( + message + ) + ) + + print("Send SQS message: " + response['MessageId']) + + return { + "statusCode": 200, + "headers": { + "Content-Type": "application/json" + }, + "body": json.dumps({ + "RequestID ": context.aws_request_id, + "ReceivedMessage": message + }) + } diff --git a/examples/basic/files/producer.zip b/examples/basic/files/producer.zip new file mode 100644 index 0000000..91db3dc Binary files /dev/null and b/examples/basic/files/producer.zip differ diff --git a/examples/basic/main.tf b/examples/basic/main.tf index 855f57e..be01e3d 100644 --- a/examples/basic/main.tf +++ b/examples/basic/main.tf @@ -1,32 +1,93 @@ -module "lambda" { - source = "sebastianczech/free-serverless-modules/aws//modules/lambda" +# https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file +data "archive_file" "producer" { + type = "zip" + source { + content = templatefile("files/producer.py", { + queue_url = module.sqs.id + }) + filename = "producer.py" + } + output_path = "files/producer.zip" +} + +# https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file +data "archive_file" "consumer" { + type = "zip" + source { + content = templatefile("files/consumer.py", { + topic_url = module.sns.id, + table_url = module.dynamodb.id + }) + filename = "consumer.py" + } + output_path = "files/consumer.zip" +} + +module "lambda_producer" { + source = "../../modules/lambda" + + name = "${var.prefix}-lambda-producer" + iam_user_name = var.iam_username + + filename = data.archive_file.producer.output_path + handler = "producer.lambda_handler" - name = "my-lambda" - iam_user_name = "my-iam-user" sqs = { - arn = "arn:aws:sqs:us-east-1:123456789012:my-sqs" - url = "https://sqs.us-east-1.amazonaws.com/123456789012/my-sqs" + enabled = true + arn = module.sqs.arn + } + + sns = { + enabled = false + } + + dynamodb = { + enabled = false + } +} + +module "lambda_consumer" { + source = "../../modules/lambda" + + name = "${var.prefix}-lambda-consumer" + iam_user_name = var.iam_username + + filename = data.archive_file.consumer.output_path + handler = "consumer.lambda_handler" + + sqs = { + enabled = true + trigger_lambda = true + arn = module.sqs.arn + } + + sns = { + enabled = true + arn = module.sns.arn + } + + dynamodb = { + enabled = true + arn = module.dynamodb.arn } } module "dynamodb" { - source = "sebastianczech/free-serverless-modules/aws//modules/dynamodb" + source = "../../modules/dynamodb" - name = "my-dynamodb" - read_capacity = 5 - write_capacity = 5 + name = "${var.prefix}-dynamodb" } module "sns" { - source = "sebastianczech/free-serverless-modules/aws//modules/sns" + source = "../../modules/sns" - name = "my-sns" + name = "${var.prefix}-sns" protocol = "email" - endpoint = "example@example.com" + endpoint = var.mail } module "sqs" { - source = "sebastianczech/free-serverless-modules/aws//modules/sqs" + source = "../../modules/sqs" - name = "my-sqs" + name = "${var.prefix}-sqs" } diff --git a/examples/basic/outputs.tf b/examples/basic/outputs.tf index e69de29..ea01b4d 100644 --- a/examples/basic/outputs.tf +++ b/examples/basic/outputs.tf @@ -0,0 +1,4 @@ +output "lambda_url_producer" { + description = "The URL of the Lambda producer" + value = module.lambda_producer.url +} diff --git a/examples/basic/variables.tf b/examples/basic/variables.tf index e69de29..c1e0286 100644 --- a/examples/basic/variables.tf +++ b/examples/basic/variables.tf @@ -0,0 +1,14 @@ +variable "prefix" { + description = "The prefix for the resources" + type = string +} + +variable "iam_username" { + description = "The name of the IAM user" + type = string +} + +variable "mail" { + description = "The email address used for notifications" + type = string +} diff --git a/examples/basic/versions.tf b/examples/basic/versions.tf index fd126d1..9d8eb55 100644 --- a/examples/basic/versions.tf +++ b/examples/basic/versions.tf @@ -1,3 +1,10 @@ terraform { + required_providers { + archive = { + source = "hashicorp/archive" + version = "~> 2.0" + } + } + required_version = ">= 1.5.0" } diff --git a/modules/dynamodb/outputs.tf b/modules/dynamodb/outputs.tf index d02e658..55e1caa 100644 --- a/modules/dynamodb/outputs.tf +++ b/modules/dynamodb/outputs.tf @@ -1,6 +1,6 @@ output "id" { description = "The ID of the DynamoDB table" - value = aws_dynamodb_table.this + value = aws_dynamodb_table.this.id } output "arn" { diff --git a/modules/lambda/.terraform.lock.hcl b/modules/lambda/.terraform.lock.hcl index af5fe6b..b926602 100644 --- a/modules/lambda/.terraform.lock.hcl +++ b/modules/lambda/.terraform.lock.hcl @@ -1,30 +1,6 @@ # This file is maintained automatically by "terraform init". # Manual edits may be lost in future updates. -provider "registry.terraform.io/hashicorp/archive" { - version = "2.5.0" - constraints = "~> 2.0" - hashes = [ - "h1:GyV//bFbFWEll/6XafMvSUCmJL+r7wDDz222dMEmV3c=", - "h1:HXf8h8Z4JYEkBND/JiqC+CjluKqifKoDGrL1IsRo15M=", - "h1:L6/LxzpXg4KYGfyLdisrvHoIsuXyzx/6Kav78NX7k+g=", - "h1:OTk41JfiDc1TVFTcRZ//4+jwPBIcWHXOwN29mjdOyug=", - "h1:REw/wlMOsFDGxWef6AE4gi8GWxKjY2i4yQkWLM/GwdA=", - "zh:3b5774d20e87058d6d67d9ad4ce3fc4a5f7ea7748d345fa6721e24a0cbb0a3d4", - "zh:3b94e706ac0f5151880ccc9e63d33c4113361f27e64224a942caa04a5a19cd44", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:7d7201858fa9376029818c9d017b4b53a933cea75480306b1122663d1e8eea2b", - "zh:8c8c7537978adf12271fe143f93b3587bb5dbabf8202ff49d0e3955b7bddc24b", - "zh:a5942584665a2689e73f3a3c43296adeaeb7e8698631d157419aa931ff856907", - "zh:a63673abdba624d60c84b819184fe86422bdbdf6bc73f68d903a7191aed32c00", - "zh:bcd1586cc32b263265e09e78f56dba3a6b6b19f5371c099a9d7a1bfe0b0667cc", - "zh:cc9e70e186e4dcef60208b4a64b42e6813b197e21ea106a96bb4eb23b54c3e44", - "zh:d4c8a0f69412892507a2c9ec0e334bcc2812a54b81212420d4f2c96ef58f713a", - "zh:e91e6d90bbc15252310eca6400d4188b29260aab0539480a3fc7b45e4d19c446", - "zh:fc468449c0dbda56aae6cb924e4a67578d18504b5b06e8989783182c6b4a5f73", - ] -} - provider "registry.terraform.io/hashicorp/aws" { version = "5.61.0" constraints = "~> 5.61" diff --git a/modules/lambda/README.md b/modules/lambda/README.md index 69a6ad1..16e0f4c 100644 --- a/modules/lambda/README.md +++ b/modules/lambda/README.md @@ -6,7 +6,6 @@ | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.5.0 | -| [archive](#requirement\_archive) | ~> 2.0 | | [aws](#requirement\_aws) | ~> 5.61 | | [external](#requirement\_external) | ~> 2.0 | @@ -14,7 +13,6 @@ | Name | Version | |------|---------| -| [archive](#provider\_archive) | ~> 2.0 | | [aws](#provider\_aws) | ~> 5.61 | ## Modules @@ -35,10 +33,10 @@ No modules. | [aws_iam_role_policy_attachment.lambda_logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.lambda_sns](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.lambda_sqs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_lambda_event_source_mapping.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_event_source_mapping) | resource | | [aws_lambda_function.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function) | resource | | [aws_lambda_function_url.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function_url) | resource | | [aws_lambda_permission.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource | -| [archive_file.this](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source | | [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_user.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_user) | data source | @@ -47,10 +45,12 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [dynamodb](#input\_dynamodb) | The DynamoDB table to put items |
object({| n/a | yes | +| [filename](#input\_filename) | The filename of the Lambda function | `string` | n/a | yes | +| [handler](#input\_handler) | The handler of the Lambda function | `string` | n/a | yes | | [iam\_user\_name](#input\_iam\_user\_name) | The name of the IAM user | `string` | n/a | yes | | [name](#input\_name) | The name of the Lambda function | `string` | n/a | yes | | [sns](#input\_sns) | The SNS topic to publish events |
enabled = optional(bool, false)
arn = optional(string)
})
object({| n/a | yes | -| [sqs](#input\_sqs) | The SQS queue to subscribe to the SNS topic |
enabled = optional(bool, false)
arn = optional(string)
})
object({| n/a | yes | +| [sqs](#input\_sqs) | The SQS queue to subscribe to the SNS topic |
enabled = optional(bool, false)
arn = optional(string)
url = optional(string)
})
object({| n/a | yes | ## Outputs diff --git a/modules/lambda/main.tf b/modules/lambda/main.tf index 44b0d9b..71f6d3b 100644 --- a/modules/lambda/main.tf +++ b/modules/lambda/main.tf @@ -16,27 +16,15 @@ resource "aws_iam_role" "this" { assume_role_policy = data.aws_iam_policy_document.this.json } -# https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file -data "archive_file" "this" { - type = "zip" - source { - content = templatefile("files/code.py", { - queue_url = var.sqs.url - }) - filename = "coder.py" - } - output_path = "files/code.zip" -} - # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_function resource "aws_lambda_function" "this" { - filename = data.archive_file.this.output_path + filename = var.filename function_name = var.name role = aws_iam_role.this.arn - source_code_hash = filebase64sha256(data.archive_file.this.output_path) + source_code_hash = filebase64sha256(var.filename) runtime = "python3.12" - handler = "code.lambda_handler" + handler = var.handler timeout = 10 environment { @@ -105,7 +93,7 @@ resource "aws_iam_policy" "lambda_sqs" { { "Sid": "LambdaStatement", "Action": [ - "sqs:SendMessage" + "sqs:SendMessage", "sqs:ReceiveMessage", "sqs:DeleteMessage", "sqs:GetQueueAttributes" @@ -159,6 +147,13 @@ resource "aws_iam_role_policy_attachment" "lambda_sns" { policy_arn = aws_iam_policy.lambda_sns[0].arn } +# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_event_source_mapping +resource "aws_lambda_event_source_mapping" "this" { + count = var.sqs.trigger_lambda ? 1 : 0 + event_source_arn = var.sqs.arn + function_name = aws_lambda_function.this.arn +} + # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy resource "aws_iam_policy" "lambda_dynamodb" { count = var.dynamodb.enabled ? 1 : 0 diff --git a/modules/lambda/variables.tf b/modules/lambda/variables.tf index 6dfc67c..4e4a8df 100644 --- a/modules/lambda/variables.tf +++ b/modules/lambda/variables.tf @@ -3,12 +3,23 @@ variable "name" { type = string } +variable "filename" { + description = "The filename of the Lambda function" + type = string +} + +variable "handler" { + description = "The handler of the Lambda function" + type = string +} + variable "sqs" { description = "The SQS queue to subscribe to the SNS topic" type = object({ - enabled = optional(bool, false) - arn = optional(string) - url = optional(string) + enabled = optional(bool, false) + trigger_lambda = optional(bool, false) + arn = optional(string) + url = optional(string) }) } diff --git a/modules/lambda/versions.tf b/modules/lambda/versions.tf index f84d251..61f39f9 100644 --- a/modules/lambda/versions.tf +++ b/modules/lambda/versions.tf @@ -5,11 +5,6 @@ terraform { version = "~> 5.61" } - archive = { - source = "hashicorp/archive" - version = "~> 2.0" - } - external = { source = "hashicorp/external" version = "~> 2.0"
enabled = optional(bool, false)
trigger_lambda = optional(bool, false)
arn = optional(string)
url = optional(string)
})