Have the following lambda function and dynamodb defined via terraform:
provider "aws" {
region = "us-east-1" # Change to your desired region
access_key = "test" # Access key for LocalStack
secret_key = "test" # Secret key for LocalStack
skip_credentials_validation = true
skip_requesting_account_id = true
endpoints {
dynamodb = "http://localhost:4566" # LocalStack DynamoDB endpoint
lambda = "http://localhost:4566"
iam = "http://localhost:4566"
}
}
resource "aws_iam_role" "lambda_execution_role" {
name = "lambda_execution_role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = "sts:AssumeRole",
Effect = "Allow",
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "lambda_dynamodb_access" {
policy_arn = "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess"
role = aws_iam_role.lambda_execution_role.name
}
resource "aws_iam_role_policy" "lambda_execution_policy" {
name = "lambda_execution_policy"
role = aws_iam_role.lambda_execution_role.id
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
Effect = "Allow",
Resource = "*"
},
{
Action = "lambda:InvokeFunction",
Effect = "Allow",
Resource = aws_lambda_function.add_user_post.arn
}
# Add more permissions as needed
]
})
}
resource "aws_lambda_function" "add_user_post" {
function_name = "AddUserPost"
handler = "addUserPostFunction.lambda_handler"
runtime = "python3.8"
filename = data.archive_file.lambda_function_add_user_post.output_path # ZIP file containing your Python code
role = aws_iam_role.lambda_execution_role.arn
source_code_hash = filebase64sha256(data.archive_file.lambda_function_add_user_post.output_path)
environment {
variables = {
TABLE_NAME = "UserProfileTable"
}
}
}
data "archive_file" "lambda_function_add_user_post" {
type = "zip"
source_dir = "${path.module}/Source"
output_path = "${path.module}/lambda_function_add_user_post.zip"
}
DynamoDB:
provider "aws" {
region = "us-east-1" # Change to your desired region
access_key = "test" # Access key for LocalStack
secret_key = "test" # Secret key for LocalStack
skip_credentials_validation = true
skip_requesting_account_id = true
endpoints {
dynamodb = "http://localhost:4566" # LocalStack DynamoDB endpoint
lambda = "http://localhost:4566"
}
}
resource "aws_dynamodb_table" "users_table" {
name = "UserProfileTable"
billing_mode = "PROVISIONED" # Or use "PAY_PER_REQUEST" for on-demand capacity
read_capacity = 5
write_capacity = 5
hash_key = "UserID"
range_key = "PostID"
attribute {
name = "UserID"
type = "N"
}
attribute {
name = "PostID"
type = "N"
}
tags = {
Name = "dynamodb-table-1"
Environment = "production"
}
}
And then the .py function that the lambda executes:
import boto3
import os
dynamodb = boto3.resource('dynamodb', region_name='us-east-1', endpoint_url='http://localhost:4566')
def lambda_handler(event, context):
try:
# Your DynamoDB table name
table_name = os.environ["TABLE_NAME"]
# Sample data to be added to DynamoDB
item = {
'userid': '1',
'postid': '1',
'content': 'Yay!',
}
# DynamoDB put operation
table = dynamodb.Table(table_name)
result = table.put_item(Item=item)
print('Item added to DynamoDB:', result)
return {
'statusCode': 200,
'body': 'Item added to DynamoDB successfully',
}
except Exception as e:
print('Error adding item to DynamoDB:', e)
return {
'statusCode': 500,
'body': 'Error adding item to DynamoDB',
}
Terraform apply
ing the two infrastructure components yields no errors, and I can check their existence via awslocal dynamodb list-tables
or awslocal lambda list-functions
and they return the expected values.
However, when running: awslocal --endpoint-url=http://localhost:4566 lambda invoke --function-name AddUserPost --payload '{}' output.json
I get
{
"StatusCode": 200,
"FunctionError": "Unhandled",
"ExecutedVersion": "$LATEST"
}
The output.json
yields:
{"errorMessage":"2024-02-10T16:42:23Z cd163c6c-75d4-4be2-9539-cec447c3624a Task timed out after 3.00 seconds"}
The unhandled
functionerror made me think the problem was with the handler defined in the lambda function, but I have tripled checked the syntax/spelling of this and I am pretty sure it correct. Not sure how to proceed.
Edit: From Helder's below answer, modifying the type of data in the insert was necessary. This removed one error, but then led to the invoke
causing a failure to connect to the endpoint. This stack question has an answer which states that the boto3 runtime should set the endpoint_url
= 'http://host.docker.internal:4566`. Unclear to me why this is necessary, but it fixed the problem fully.
It seems the error is in the data you are trying to put in DynamoDB
In your table attribute declaration we can see:
attribute {
name = "UserID"
type = "N"
}
attribute {
name = "PostID"
type = "N"
}
You have to keep that same case when we call the table.put_item(Item=item)
I got it to work like this:
item = {
'UserID': 1,
'PostID': 1,
'content': 'Yay!',
}
I can see the item created correctly in the table:
Full disclosure I tested directly in AWS not using localstack, from the comments it seems that localstack might be swallowing the error and letting the lambda timeout