SAMPLE-02: Provisioning Lambda Function, API Gateway and Reaching HTML Page in Python Code From Browser
This sample shows:
- how to create Lambda function with Python code,
- how to create Lambda Role, Policy, Policy-Role attachment, Lambda API-gateway permission, uploading code,
- how to create API-gateway resource and method definition, Lambda-API-gateway connection, deploying API-gateway,
- details on AWS Lambda, API-Gateway, IAM
There are 3 main parts:
- lambda.tf: It includes lambda function, lambda role, policy, policy-role attachment, lambda api gateway permission, zipping code
- api-gateway.tf: It includes api-gateway resource and method definition, lambda - api gateway connection, deploying api gateway, api-gateway deployment URL as output
- code/main.py: It includes basic lambda handler with basic HTML code, and REST API response.
- You should have a look following lab:
- Create lambda.tf:
- Terraform Configuration: Specifies the AWS provider version and Terraform version.
- IAM Role and Policy: Creates an IAM role for the Lambda function and attaches a policy that allows the Lambda function to write logs to CloudWatch.
- Zipping Lambda Code: Prepares the Python code by zipping it into a format that Lambda can execute.
- Lambda Function: Creates a Lambda function using the specified IAM role, code, and runtime.
- API Gateway Integration: Grants API Gateway permission to invoke the Lambda function.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16"
}
}
required_version = ">= 1.2.0"
}
# Create IAM Role for lambda
resource "aws_iam_role" "lambda_role" {
name = "aws_lambda_role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
# IAM policy for the lambda
resource "aws_iam_policy" "iam_policy_for_lambda" {
name = "aws_iam_policy_for_aws_lambda_role"
path = "/"
description = "AWS IAM Policy for managing aws lambda role"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*",
"Effect": "Allow"
}
]
}
EOF
}
# Role - Policy Attachment
resource "aws_iam_role_policy_attachment" "attach_iam_policy_to_iam_role" {
role = aws_iam_role.lambda_role.name
policy_arn = aws_iam_policy.iam_policy_for_lambda.arn
}
# Zipping the code, lambda wants the code as zip file
data "archive_file" "zip_the_python_code" {
type = "zip"
source_dir = "${path.module}/code/"
output_path = "${path.module}/code/main.zip"
}
# Lambda Function, in terraform ${path.module} is the current directory.
resource "aws_lambda_function" "lambda_function" {
filename = "${path.module}/code/main.zip"
function_name = "Lambda-Function"
role = aws_iam_role.lambda_role.arn
handler = "main.lambda_handler"
runtime = "python3.8"
depends_on = [aws_iam_role_policy_attachment.attach_iam_policy_to_iam_role]
}
# With Lambda permission, API Gateway can invoke Lambda
resource "aws_lambda_permission" "apigw" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.lambda_function.function_name
principal = "apigateway.amazonaws.com"
# The "/*/*" portion grants access from any method on any resource within the API Gateway REST API.
source_arn = "${aws_api_gateway_rest_api.example.execution_arn}/*/*"
}
- Create api-gateway.tf:
- Proxy Usage: The {proxy+} path is used to dynamically route all requests to the Lambda function, eliminating the need to define individual paths and methods.
- Flexibility: The ANY method allows all HTTP methods, making the API flexible.
- AWS_PROXY Integration: Simplifies the interaction between API Gateway and Lambda by passing the entire request data to Lambda, allowing it to process dynamically.
# Create API Gateway with Rest API type
resource "aws_api_gateway_rest_api" "example" {
name = "Serverless"
description = "Serverless Application using Terraform"
}
resource "aws_api_gateway_resource" "proxy" {
rest_api_id = aws_api_gateway_rest_api.example.id
parent_id = aws_api_gateway_rest_api.example.root_resource_id
path_part = "{proxy+}" # with proxy, this resource will match any request path
}
resource "aws_api_gateway_method" "proxy" {
rest_api_id = aws_api_gateway_rest_api.example.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = "ANY" # with ANY, it allows any request method to be used, all incoming requests will match this resource
authorization = "NONE"
}
# API Gateway - Lambda Connection
resource "aws_api_gateway_integration" "lambda" {
rest_api_id = aws_api_gateway_rest_api.example.id
resource_id = aws_api_gateway_method.proxy.resource_id
http_method = aws_api_gateway_method.proxy.http_method
integration_http_method = "POST"
type = "AWS_PROXY" # With AWS_PROXY, it causes API gateway to call into the API of another AWS service
uri = aws_lambda_function.lambda_function.invoke_arn
}
# The proxy resource cannot match an empty path at the root of the API.
# To handle that, a similar configuration must be applied to the root resource that is built in to the REST API object
resource "aws_api_gateway_method" "proxy_root" {
rest_api_id = aws_api_gateway_rest_api.example.id
resource_id = aws_api_gateway_rest_api.example.root_resource_id
http_method = "ANY"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "lambda_root" {
rest_api_id = aws_api_gateway_rest_api.example.id
resource_id = aws_api_gateway_method.proxy_root.resource_id
http_method = aws_api_gateway_method.proxy_root.http_method
integration_http_method = "POST"
type = "AWS_PROXY" # With AWS_PROXY, it causes API gateway to call into the API of another AWS service
uri = aws_lambda_function.lambda_function.invoke_arn
}
# Deploy API Gateway
resource "aws_api_gateway_deployment" "example" {
depends_on = [
aws_api_gateway_integration.lambda,
aws_api_gateway_integration.lambda_root,
]
rest_api_id = aws_api_gateway_rest_api.example.id
stage_name = "test"
}
# Output to the URL
output "base_url" {
value = aws_api_gateway_deployment.example.invoke_url
}
- Create main.py under code directory:
def lambda_handler(event, context):
content = """
<html>
<h1> Hello Website running on Lambda! Deployed via Terraform </h1>
</html>
"""
response ={
"statusCode": 200,
"body": content,
"headers": {"Content-Type": "text/html",},
}
return response
- Run init, validate command:
terraform init
terraform validate
- Run plan, apply command:
terraform plan # for dry-run
terraform apply
- On AWS Lambda:
- Create a test by clicking "Test" for lambda function:
- Status code 200, OK is returned successfully:
- Execution Role is created, seen on Lambda
- Role on IAM:
- Policy on IAM:
- On AWS Lambda: With Lambda permission, API Gateway can invoke Lambda
- Lambda is triggered by API-Gateway:
- On AWS API-Gateway:
- By clicking "Test" to test api-gateway
- HTML page that runs on Lambda Function using API-Gateway can be reached. With API-Gateway, lambda function gets DNS and traffic from internet comes to the Lambda function.
- Run destroy command:
terraform destroy
- On AWS Lambda, function is deleted: