8. Cloud Resume Challenge: Creating Lambda Functions

I am a Sr. Windows Administrator looking to sharpen my skills within the cloud space. This blog serves not only as a guide for others to use but also to document my cloud journey.
Search for a command to run...

I am a Sr. Windows Administrator looking to sharpen my skills within the cloud space. This blog serves not only as a guide for others to use but also to document my cloud journey.
No comments yet. Be the first to comment.
In this series, I will outline the steps to integrate a fully functional serverless website hosted on CloudFront using Amazon S3. The implementation deployment will be through Terraform.
In this module, you will be creating your API Gateway and a couple of resources that will interact with your Lambda Functions from the previous module. You will create API resources that will utilize POST HTTP Methods. Once your API Gateway is deploy...
Deploying my portfolio site with Terraform was an enriching experience. It allowed me to craft the architecture precisely as I envisioned it. Each resource deployment deepened my understanding of the services I employed, many of which were new to me....

In this final module, you will create an ACM certificate and Route53 entries for CloudFront and API Gateway using your custom domain name. This will present a more profession website that can be shared with others. Terraform ACM Reference Terraform...

In this module, you will be updating your HTML and JavaScript files to integrate your API Gateway resources. You will also make modifications to your terraform-apply_cloudfront-invalidation.yml to invalidate your CloudFront Distribution. The invalida...

In this module, you will be creating your API Gateway and a couple of resources that will interact with your Lambda Functions from the previous module. You will create API resources that will utilize POST HTTP Methods. Once your API Gateway is deploy...

In this module, I will walk you through each of the Lambda functions and how they work, upload those functions to AWS, and test their functionality with their respective resources.

Login to GitHub.
Select the Issues tab > Select New Issue.
Add the a title, e.g. Create Lambda Functions
Select Submit new issue.

On the right pane, under Development, Select Create a branch.
Leave the defaults > Select Create branch.

Open your IDE Terminal.
Input the following:
git fetch origin
git checkout YOUR_BRANCH_NAME
./lambda/Update_Table

This lambda_function utilizes Boto3, the AWS SDK for Python. At the start of the function, boto3 is imported for use.
client is defined as a low-level representation of Amazon DynamoDB
import boto3
client = boto3.client('dynamodb')
Using the default lambda_handler function provided by AWS Lambda, it returns a JSON representation of { 'views': updateTable()}
updateTable is executed and upon completion sends the value returned
For example, since the function has not been executed yet; upon it's first execution the return value for the lambda_handler will be {'views': 1}

At the start of this function, it defines a viewer_count variable.
The viewer_count calls the getViewerCount function and increments by 1.
By default the table values, even though it is defined as a Number ('N'), are stored as strings. So the value from getViewerCount is converted into an integer so that basic arithmetic can be accomplished.
The update_item method for the client (boto3.client('dynamodb')) is used to update the DynamoDB table by using the arguments passed for TableName, Key, and AttributeUpdates.
In this case, it is updating the value table item to the viewer_count variable.
Once the table is updated, it calls the getViewerCount function again and returns the value from the DynamoDB table to the lambda_handler.

In this function a response variable is defined.
get_item method for the client (boto3.client('dynamodb')) is used to retrieve the value from the defined table.The return value of the getViewerCount is the actual string value extrapolated using the Item, value, and N keys. I chose this approach because I only wanted the value of value passed, and not the full table values.
For visualization of what the return response would look like adding keys each time:
return response
return response['Item']
return response['Item']['value']
return response['Item']['value']['N']
Update the getViewerCount and updateTable functions
INPUT_TABLE_NAME with your actual table name
./lambda/Send_Mail

This lambda_function utilizes Boto3, the AWS SDK for Python. At the start of the function, boto3 is imported for use.
client is defined as a low-level representation of Amazon SNS
import boto3
client = boto3.client('sns')
Using the default lambda_handler function provided by AWS Lambda, it calls the sendMail function and passes the event JSON data.
The event JSON payload consists of name, email, phone, subject, and body.
Example:
{
"name": "John Doe",
"email": " john.doe@example.com",
"phone": 0123456,
"subject": "Call Me!",
"body": "Hi Anonymous, We'd like to hire you!"
}

This function starts by defining the body as a formatted string.
This is what you could expect to see from an email that has the example event JSON data.
Subject: Call Me!
Message from John Doe:
Hi Anonymous, We'd like to hire you!
Name: John Doe
Phone: 0123456
Email: john.doe@example.com
The function then publishes the formatted string as the Message argument.
The TopicARN calls the get_topic function so it publishes the message to that topic/subscription
The subject is defined by the event JSON data; in this case "Call Me!"

This function defines topic_arns by calling the list_topics method and using the Topics key to get the list of Topic ARNs
The function then uses a for loop to find the Topic ARN to use to publish the message

In this section you will be modifying the main, variables and outputs Terraform files located in ./infra/modules/aws/lambda/.

Input the following to create the Lambda function
resource "aws_lambda_function" "lambda" {
description = var.description
filename = var.zip_file_name
function_name = var.function_name
role = var.role
timeout = var.timeout
handler = var.handler
source_code_hash = data.archive_file.lambda.output_base64sha256
runtime = var.runtime
}
This will create the Lambda function using the defined variables
The Lambda function requires a zip file to be created for the file_name argument
Input the following to create a zip file of the lambda_function.py
data "archive_file" "lambda" {
type = "zip"
source_file = var.file_name
output_path = var.zip_file_name
}
For the Lambda function to interact with DynamoDB and SNS, you'll have to give it the proper permissions.
Input the following to grant Lambda the sts:AssumeRole and Allow actions policies
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
actions = [
"sts:AssumeRole",
]
}
}
data "aws_iam_policy_document" "policy" {
statement {
effect = "Allow"
actions = var.actions
resources = var.resource
}
}

Save the file
Input the following to define the input variables for the lambda module:
variable "function_name" {
description = "(Required) Unique name for your Lambda Function."
type = string
nullable = false
}
variable "role" {
description = "(Required) Amazon Resource Name (ARN) of the function's execution role. The role provides the function's identity and access to AWS services and resources."
type = string
nullable = false
}
variable "file_name" {
description = "(Optional) Path to the function's deployment package within the local filesystem. Exactly one of filename, image_uri, or s3_bucket must be specified."
type = string
nullable = true
}
variable "zip_file_name" {
description = "(Optional) Path to the function's deployment package within the local filesystem. Exactly one of filename, image_uri, or s3_bucket must be specified."
type = string
nullable = true
}
variable "description" {
description = "(Optional) Description of what your Lambda Function does."
type = string
nullable = true
}
variable "handler" {
description = "(Optional) Function entrypoint in your code."
type = string
default = "lambda_function.lambda_handler" # Python handler
}
variable "runtime" {
description = "(Optional) Identifier of the function's runtime. See Runtimes for valid values."
type = string
default = "python3.12"
}
variable "timeout" {
description = "Optional) Amount of time your Lambda Function has to run in seconds. Defaults to 3. See Limits."
type = number
default = 600
}
variable "actions" {
description = "Include a list of actions that the policy allows or denies."
type = list(string)
}
variable "resource" {
description = "(Required in only some circumstances) – If you create an IAM permissions policy, you must specify a list of resources to which the actions apply. If you create a resource-based policy, this element is optional. If you do not include this element, then the resource to which the action applies is the resource to which the policy is attached."
type = list(string)
}

Save the file
Input the following to define the outputs for the Lambda module
output "arn" {
description = "Amazon Resource Name (ARN) identifying your Lambda Function."
value = aws_lambda_function.lambda.arn
}
output "function_name" {
description = "Unique name for your Lambda Function."
value = aws_lambda_function.lambda.function_name
}
output "invoke_arn" {
description = "ARN to be used for invoking Lambda Function from API Gateway - to be used in aws_api_gateway_integration's uri"
value = aws_lambda_function.lambda.invoke_arn
}
output "last_modified" {
description = "Date this resource was last modified."
value = aws_lambda_function.lambda.last_modified
}
output "qualified_arn" {
description = "Qualified ARN (ARN with lambda version number) to be used for invoking Lambda Function from API Gateway - to be used in aws_api_gateway_integration's uri."
value = aws_lambda_function.lambda.qualified_arn
}
output "signing_job_arn" {
description = "ARN of the signing job."
value = aws_lambda_function.lambda.signing_job_arn
}
output "signing_profile_version_arn" {
description = "ARN of the signing profile version."
value = aws_lambda_function.lambda.signing_profile_version_arn
}
output "source_code_size" {
description = "Size in bytes of the function .zip file."
value = aws_lambda_function.lambda.source_code_size
}
output "tags_all" {
description = "A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block."
value = aws_lambda_function.lambda.tags_all
}
output "version" {
description = "Latest published version of your Lambda Function."
value = aws_lambda_function.lambda.version
}
output "assume_role" {
description = "Lambda AWS sts:AssumeRole"
value = data.aws_iam_policy_document.assume_role.json
}
output "policy" {
description = "IAM Policy for Lambda function"
value = data.aws_iam_policy_document.policy.json
}

Save the file
In this section you will be modifying the main, variables and outputs Terraform files located in ./infra/modules/aws/iam/. This will define the IAM role for the Lambda functions.
Input the following to create the IAM role and policy
resource "aws_iam_role" "role" {
name = "ROL_${var.policy_name}"
assume_role_policy = var.assume_role
}
resource "aws_iam_role_policy" "policy" {
name = var.policy_name
role = aws_iam_role.role.id
policy = var.policy
}

This creates an IAM Role and IAM policy.
Save the file
Input the following to create the input variable definitions
variable "assume_role" {
description = "Allow sts:AssumeRole"
type = string
}
variable "policy_name" {
description = "(Optional) The name of the role policy. If omitted, Terraform will assign a random, unique name."
type = string
}
variable "policy" {
description = "(Required) The inline policy document. This is a JSON formatted string. For more information about building IAM policy documents with Terraform, see the AWS IAM Policy Document Guide"
type = string
}

Save the file
Input the following to define the return values for the IAM module
output "arn" {
description = "Amazon Resource Name (ARN) specifying the role."
value = aws_iam_role.role.arn
}
output "create_date" {
description = "Creation date of the IAM role."
value = aws_iam_role.role.create_date
}
output "id" {
description = "Name of the role."
value = aws_iam_role.role.id
}
output "name" {
description = "Name of the role."
value = aws_iam_role.role.name
}
output "tags_all" {
description = " A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block."
value = aws_iam_role.role.tags_all
}
output "unique_id" {
description = "Stable and unique string identifying the role."
value = aws_iam_role.role.unique_id
}

Save the file
Input the following to create the Lambda function module with the IAM role and policy defined.
module "iam_role_lambda" {
source = "../aws/iam"
assume_role = module.lambda_function.assume_role
policy_name = var.policy_name
policy = module.lambda_function.policy
}
module "lambda_function" {
source = "../aws/lambda"
description = var.description
function_name = var.function_name
file_name = var.file_name
zip_file_name = var.zip_file_name
role = module.iam_role_lambda.arn
actions = var.actions
resource = var.resource
}

Input the following to create the input variables for the lamdba_function module
variable "function_name" {
description = "(Required) Unique name for your Lambda Function."
type = string
nullable = false
}
variable "file_name" {
description = "(Optional) Path to the function's deployment package within the local filesystem. Exactly one of filename, image_uri, or s3_bucket must be specified."
type = string
nullable = true
}
variable "zip_file_name" {
description = "(Optional) Path to the function's deployment package within the local filesystem. Exactly one of filename, image_uri, or s3_bucket must be specified."
type = string
nullable = true
}
variable "description" {
description = "(Optional) Description of what your Lambda Function does."
type = string
nullable = true
}
variable "actions" {
description = "Include a list of actions that the policy allows or denies."
type = list(string)
}
variable "resource" {
description = "(Required in only some circumstances) – If you create an IAM permissions policy, you must specify a list of resources to which the actions apply. If you create a resource-based policy, this element is optional. If you do not include this element, then the resource to which the action applies is the resource to which the policy is attached."
type = list(string)
}
variable "policy_name" {
description = "(Optional) The name of the role policy. If omitted, Terraform will assign a random, unique name."
type = string
}

Save the file
Input the following to define the return values
output "lambda_arn" {
description = "Amazon Resource Name (ARN) identifying your Lambda Function."
value = module.lambda_function.arn
}
output "function_name" {
description = "Unique name for your Lambda Function."
value = module.lambda_function.function_name
}
output "invoke_arn" {
description = "ARN to be used for invoking Lambda Function from API Gateway - to be used in aws_api_gateway_integration's uri"
value = module.lambda_function.invoke_arn
}
output "last_modified" {
description = "Date this resource was last modified."
value = module.lambda_function.last_modified
}
output "qualified_arn" {
description = "Qualified ARN (ARN with lambda version number) to be used for invoking Lambda Function from API Gateway - to be used in aws_api_gateway_integration's uri."
value = module.lambda_function.qualified_arn
}
output "signing_job_arn" {
description = "ARN of the signing job."
value = module.lambda_function.signing_job_arn
}
output "signing_profile_version_arn" {
description = "ARN of the signing profile version."
value = module.lambda_function.signing_profile_version_arn
}
output "source_code_size" {
description = "Size in bytes of the function .zip file."
value = module.lambda_function.source_code_size
}
output "version" {
description = "Latest published version of your Lambda Function."
value = module.lambda_function.version
}
output "assume_role" {
description = "Lambda AWS sts:AssumeRole"
value = module.lambda_function.assume_role
}
output "policy" {
description = "IAM Policy for Lambda function"
value = module.lambda_function.policy
}
output "iam_role_arn" {
description = "Amazon Resource Name (ARN) specifying the role."
value = module.iam_role_lambda.arn
}
output "create_date" {
description = "Creation date of the IAM role."
value = module.iam_role_lambda.create_date
}
output "id" {
description = "Name of the role."
value = module.iam_role_lambda.id
}
output "name" {
description = "Name of the role."
value = module.iam_role_lambda.name
}
output "unique_id" {
description = "Stable and unique string identifying the role."
value = module.iam_role_lambda.unique_id
}

Save the file
In this section you will update my_portfolio module to include the creation of the Lambda function with the IAM Role and Policy attached.
module "lambda_dynamodb" {
source = "../lambda_function"
description = var.dynamodb_description
function_name = var.dynamodb_function_name
file_name = var.dynamodb_function_file
zip_file_name = var.dynamodb_function_zip
policy_name = var.dynamodb_policy_name
api_gateway_arn = "${module.api.execution_arn}/*/POST${module.api_resource_dynamodb.path}"
actions = var.dynamodb_actions
resource = [module.dynamodb.arn]
}
module "lambda_sns" {
source = "../lambda_function"
description = var.sns_description
function_name = var.sns_function_name
file_name = var.sns_function_file
zip_file_name = var.sns_function_zip
policy_name = var.sns_policy_name
api_gateway_arn = "${module.api.execution_arn}/*/POST${module.api_resource_sns.path}"
actions = var.sns_actions
resource = ["*"]
}

The lambda_function module is called for each of the Lambda functions to be created, and assign the permissions associated with the Lambda function.
Input the following to define the input variables for the two module blocks:
variable "dynamodb_policy_name" {
description = "Name for DynamoDB IAM Policy"
type = string
}
variable "dynamodb_description" {
description = "Description for DynamoDB Lambda function"
type = string
}
variable "dynamodb_function_name" {
description = "Function name for DynamoDB Lambda function"
type = string
}
variable "dynamodb_function_file" {
description = "Path for DynamoDB Lambda Function file"
type = string
}
variable "dynamodb_function_zip" {
description = "Path for DynamoDB Lambda Function zip file"
type = string
}
variable "sns_policy_name" {
description = "Name for SNS IAM Policy"
type = string
}
variable "sns_description" {
description = "Description for SNS Lambda function"
type = string
}
variable "sns_function_name" {
description = "Function name for SNS Lambda function"
type = string
}
variable "sns_function_file" {
description = "Path for SNS Lambda Function file"
type = string
}
variable "sns_function_zip" {
description = "Path for SNS Lambda Function zip file"
type = string
}
variable "dynamodb_actions" {
description = "Allowable actions for Lambda on DynamoDB resource"
type = list(string)
}
variable "sns_actions" {
description = "Allowable actions for Lambda on SNS resource"
type = list(string)
}

Save the file
There is no requirement to define the outputs for lambda function, but you can include the outputs for each of the module blocks.
# IAM
dynamodb_policy_name = "DynamoDB_Policy"
sns_policy_name = "SNS_Policy"
# Lambda
dynamodb_description = "Lambda function for update DynamoDB table"
dynamodb_function_name = "Update_DynamoDB_Table"
dynamodb_function_file = "../lambda/Update_Table/lambda_function.py"
dynamodb_function_zip = "../lambda/Update_Table/lambda_function_payload.zip"
sns_description = "Lambda function for sending Emails with SNS"
sns_function_name = "SNS_Email"
sns_function_file = "../lambda/Send_Mail/lambda_function.py"
sns_function_zip = "../lambda/Send_Mail/lambda_function_payload.zip"
dynamodb_actions = [
"dynamodb:GetItem",
"dynamodb:UpdateItem"
]
sns_actions = [
"sns:Publish",
"sns:ListTopics"
]

Since the file_path has been hard coded, you do not have to modify your variables.tf
Optionally, you can include the outputs for these entries.
Ensure your files are saved.
In your IDE Terminal, type the following:
git add .
Add all files that were changed.
git commit -m "Create lambda functions for DynamoDB and SNS"
Commit the changes with a comment.
git push
Push to GitHub.
Login to GitHub.
You should see the push on your repository.
Select Compare and pull request.
Validate the changes that were made to be pushed to main
Select Create pull request.
Your Terraform Plan should run before you can merge to main.


If you are using the same site files from the original template, the plan to add 6 should match.
Select Merge pull request > Confirm merge.
Delete branch.
In your IDE Terminal, type the following:
git checkout main
git pull
AWS Management Console:
In the Search bar, search for "lambda" > Select Functions
You should see your two Lambda Functions

Select the Update_DynamoDB_Table function
Select the BLUE Test button

Leave the defaults and select the Invoke button at the bottom

You should see the Execution results like below

This shows the return value for your DynamoDB table after iterating by 1.
Select the SNS_Email function
Select the BLUE Test button

{
"name": "Spiderman",
"email": "spideyNYC@marvel.com",
"subject": "Spidey Sense!",
"phone": 123456789,
"body": "With great power comes great responsibility..."
}

Select the Invoke button at the bottom
You should see the return value like below


You have successfully created Lambda functions to interact with DynamoDB and SNS. You tested your functions to ensure they work as expected. Your my_site_visitors table has been incremented by one, and you sent yourself an email via Lambda and SNS. In the next module, you will create an API Gateway. This will tie all your services together to make your site a little better.