|  | 
|  | 1 | +# Deploying your first module | 
|  | 2 | + | 
|  | 3 | +[Modules](../overview/modules.md) allow you to define an interface to create one or many resources in the cloud or on-premise, similar to how in object oriented programming you can define a class that may have different attribute values across many instances. | 
|  | 4 | + | 
|  | 5 | +This tutorial will teach you how to develop a Terraform module that deploys an AWS Lambda function. We will create the required file structure, define an AWS Lambda function and AWS IAM role as code, then plan and apply the resource in an AWS account. Then, we’ll verify the deployment by invoking the Lambda using the AWS CLI. Finally, we'll clean up the resources we create to avoid unexpected costs. | 
|  | 6 | + | 
|  | 7 | +## Prerequisites | 
|  | 8 | +- An AWS account with permissions to create the necessary resources | 
|  | 9 | +- An [AWS Identity and Access Management](https://aws.amazon.com/iam/) (IAM) user or role with permissions to create AWS IAM roles and Lambda functions | 
|  | 10 | +- [AWS Command Line Interface](https://aws.amazon.com/cli/) (AWS CLI) installed on your local machine | 
|  | 11 | +- [Terraform](https://www.terraform.io) installed on your local machine | 
|  | 12 | + | 
|  | 13 | +## Create the module | 
|  | 14 | + | 
|  | 15 | +In this section you’ll create a Terraform module that can create an AWS Lambda function and IAM role. This module will include three files — `main.tf` which will contain the resource definitions, `variables.tf`, which specifies the possible inputs to the module, and `outputs.tf`, which specifies the values that can be used to pass references to attributes from the resources in the module. | 
|  | 16 | + | 
|  | 17 | +This module could be referenced many times to create any number of AWS Lambda functions and IAM roles. | 
|  | 18 | + | 
|  | 19 | + | 
|  | 20 | +### Create a basic file structure | 
|  | 21 | +First, create the directories and files that will contain the Terraform configuration. | 
|  | 22 | + | 
|  | 23 | +```bash | 
|  | 24 | +mkdir -p terraform-aws-gw-lambda-tutorial/modules/lambda | 
|  | 25 | +touch terraform-aws-gw-lambda-tutorial/modules/lambda/main.tf | 
|  | 26 | +touch terraform-aws-gw-lambda-tutorial/modules/lambda/variables.tf | 
|  | 27 | +touch terraform-aws-gw-lambda-tutorial/modules/lambda/outputs.tf | 
|  | 28 | +``` | 
|  | 29 | + | 
|  | 30 | +### Define the module resources | 
|  | 31 | + | 
|  | 32 | +First, define the resources that should be created by the module. This is where you define resource level blocks provided by Terraform. For this module, we need an AWS Lambda function and an IAM role that will be used by the Lambda function. | 
|  | 33 | + | 
|  | 34 | +Paste the following snippet in `terraform-aws-gw-lambda/modules/lambda/main.tf`. | 
|  | 35 | +```hcl title="terraform-aws-gw-lambda/modules/lambda/main.tf" | 
|  | 36 | +resource "aws_iam_role" "lambda_role" { | 
|  | 37 | +  name = "${var.lambda_name}-role" | 
|  | 38 | +
 | 
|  | 39 | +  assume_role_policy = <<EOF | 
|  | 40 | +{ | 
|  | 41 | +  "Version": "2012-10-17", | 
|  | 42 | +  "Statement": [ | 
|  | 43 | +    { | 
|  | 44 | +      "Action": "sts:AssumeRole", | 
|  | 45 | +      "Principal": { | 
|  | 46 | +        "Service": "lambda.amazonaws.com" | 
|  | 47 | +      }, | 
|  | 48 | +      "Effect": "Allow", | 
|  | 49 | +      "Sid": "" | 
|  | 50 | +    } | 
|  | 51 | +  ] | 
|  | 52 | +} | 
|  | 53 | +EOF | 
|  | 54 | +} | 
|  | 55 | +
 | 
|  | 56 | +data "archive_file" "zip" { | 
|  | 57 | +  type = "zip" | 
|  | 58 | +
 | 
|  | 59 | +  source_file = var.source_file | 
|  | 60 | +  output_path = "${path.module}/${var.lambda_name}.zip" | 
|  | 61 | +} | 
|  | 62 | +
 | 
|  | 63 | +resource "aws_lambda_function" "lambda" { | 
|  | 64 | +  function_name = var.lambda_name | 
|  | 65 | +  handler       = var.handler | 
|  | 66 | +  filename      = data.archive_file.zip.output_path | 
|  | 67 | +  runtime       = var.runtime | 
|  | 68 | +  memory_size   = var.memory_size | 
|  | 69 | +  timeout       = var.timeout | 
|  | 70 | +
 | 
|  | 71 | +  role = aws_iam_role.lambda_role.arn | 
|  | 72 | +} | 
|  | 73 | +``` | 
|  | 74 | + | 
|  | 75 | +### Specify the variables for the module | 
|  | 76 | + | 
|  | 77 | +Now that you’ve defined the resources you want to create, you need to list out all of the variables that you want to allow users to pass into the module. You can reference these values in the module using the `var` syntax, as visible in `terraform-aws-gw-lambda/modules/lambda/main.tf`. | 
|  | 78 | + | 
|  | 79 | +Copy the following snippet into `terraform-aws-gw-lambda-tutorial/modules/lambda/variables.tf`. | 
|  | 80 | + | 
|  | 81 | +```hcl title="terraform-aws-gw-lambda-tutorial/modules/lambda/variables.tf" | 
|  | 82 | +variable "lambda_name" { | 
|  | 83 | +  type        = string | 
|  | 84 | +  description = "Name that will be used for the AWS Lambda function" | 
|  | 85 | +} | 
|  | 86 | +
 | 
|  | 87 | +variable "handler" { | 
|  | 88 | +  type        = string | 
|  | 89 | +  description = "The name of the handler function that will be called as the entrypoint of the lambda" | 
|  | 90 | +} | 
|  | 91 | +
 | 
|  | 92 | +variable "source_file" { | 
|  | 93 | +  type        = string | 
|  | 94 | +  description = "The path to the source file to be deployed to lambda" | 
|  | 95 | +} | 
|  | 96 | +
 | 
|  | 97 | +variable "runtime" { | 
|  | 98 | +  type        = string | 
|  | 99 | +  description = "The runtime of the Lambda. Options include go, python, ruby, etc." | 
|  | 100 | +} | 
|  | 101 | +
 | 
|  | 102 | +variable "memory_size" { | 
|  | 103 | +  type        = number | 
|  | 104 | +  description = "The amount of memory, in MB, to give to the Lambda. Defaults to 128." | 
|  | 105 | +  default     = 128 | 
|  | 106 | +} | 
|  | 107 | +
 | 
|  | 108 | +variable "timeout" { | 
|  | 109 | +  type        = number | 
|  | 110 | +  description = "The amount of time, in seconds, that a lambda can execute before timing out. Defaults to 30." | 
|  | 111 | +  default     = 30 | 
|  | 112 | +} | 
|  | 113 | +``` | 
|  | 114 | + | 
|  | 115 | +### Specify the outputs | 
|  | 116 | + | 
|  | 117 | +Terraform allows you to specify values that the module will output. Outputs are convenient ways to pass values between modules when composing a service comprised of many modules. | 
|  | 118 | + | 
|  | 119 | +Copy the following snippet into `terraform-aws-gw-lambda-tutorial/modules/lambda/outputs.tf`. | 
|  | 120 | +```hcl title="terraform-aws-gw-lambda-tutorial/modules/lambda/outputs.tf" | 
|  | 121 | +output "function_name" { | 
|  | 122 | +  value = aws_lambda_function.lambda.function_name | 
|  | 123 | +} | 
|  | 124 | +``` | 
|  | 125 | + | 
|  | 126 | +## Reference the module | 
|  | 127 | + | 
|  | 128 | +Now that you have defined a module that creates an AWS Lambda function and IAM role, you can use the module to create the resources in AWS. | 
|  | 129 | + | 
|  | 130 | +### Create the basic file structure | 
|  | 131 | + | 
|  | 132 | +Now that you have the module defined, you need to create files which will reference the module. Typically, you would create a module in one repository, then reference it in a different repository. For this tutorial, we’ll just create the reference in the top level directory for the sake of simplicity. | 
|  | 133 | + | 
|  | 134 | +Create a file called `main.tf`, which will contain a reference to the module, and a file called `main.py`, which will contain the Lambda function code. | 
|  | 135 | +```bash | 
|  | 136 | +touch terraform-aws-gw-lambda-tutorial/main.tf | 
|  | 137 | +touch terraform-aws-gw-lambda-tutorial/main.py | 
|  | 138 | +``` | 
|  | 139 | + | 
|  | 140 | +### Write the function code | 
|  | 141 | + | 
|  | 142 | +Next, we’ll write a simple Python function that returns a string that will be used as the entrypoint of the AWS Lambda function. Terraform will create a zip file containing this file that will be uploaded to the Lambda function. | 
|  | 143 | + | 
|  | 144 | +Copy the following to `terraform-aws-gw-lambda-tutorial/main.py`. | 
|  | 145 | + | 
|  | 146 | +```py title="terraform-aws-gw-lambda-tutorial/main.py" | 
|  | 147 | +def lambda_handler(event, context): | 
|  | 148 | +    return "Hello from Gruntwork!" | 
|  | 149 | +``` | 
|  | 150 | + | 
|  | 151 | +### Reference the module | 
|  | 152 | + | 
|  | 153 | +Next, create a reference to the module you just created in `/modules/lambda/main.tf`. This code uses the `module` block from Terraform, which references the `/modules/lambda` directory using the `source` attribute. You can then specify values for the required variables specified in `/modules/lambdas/variables.tf`. Finally, we specify an output using the value of the `module.lambda.function_name` output created in `/modules/lambdas/outputs.tf` | 
|  | 154 | + | 
|  | 155 | +```hcl title="terraform-aws-gw-lambda-tutorial/main.tf" | 
|  | 156 | +terraform { | 
|  | 157 | +  required_providers { | 
|  | 158 | +    aws = { | 
|  | 159 | +      source  = "hashicorp/aws" | 
|  | 160 | +      version = ">= 4.0.0" | 
|  | 161 | +    } | 
|  | 162 | +  } | 
|  | 163 | +} | 
|  | 164 | +
 | 
|  | 165 | +module "lambda" { | 
|  | 166 | +  source = "./modules/lambda" | 
|  | 167 | +
 | 
|  | 168 | +  lambda_name      = "gruntwork-lambda-tutorial" | 
|  | 169 | +  handler          = "main.lambda_handler" | 
|  | 170 | +  source_file      = "${path.module}/main.py" | 
|  | 171 | +  runtime          = "python3.9" | 
|  | 172 | +} | 
|  | 173 | +
 | 
|  | 174 | +
 | 
|  | 175 | +output "function_name" { | 
|  | 176 | +  value = module.lambda.function_name | 
|  | 177 | +} | 
|  | 178 | +``` | 
|  | 179 | + | 
|  | 180 | +## Plan and apply the module | 
|  | 181 | + | 
|  | 182 | +### Run Terraform plan | 
|  | 183 | + | 
|  | 184 | +Terraform will generate an execution plan using the `plan` action. The plan will show what resources Terraform determines need to be created or modified. | 
|  | 185 | + | 
|  | 186 | +Running `terraform plan` is helpful when developing modules, to confirm that the Terraform code you are writing are using the correct syntax, and to confirm what resources will be created or modified when applying the module in your AWS account. | 
|  | 187 | + | 
|  | 188 | + | 
|  | 189 | +From the `terraform-aws-gw-lambda-tutorial` directory, run a plan to see what resources will be created. | 
|  | 190 | +```bash | 
|  | 191 | +terraform plan | 
|  | 192 | +``` | 
|  | 193 | + | 
|  | 194 | +Review the output of `terraform plan`, it should contain two resources — an AWS Lambda function and an AWS IAM role. | 
|  | 195 | + | 
|  | 196 | + | 
|  | 197 | +### Run Terraform apply | 
|  | 198 | + | 
|  | 199 | +Terraform creates resources when using the `apply` action in a directory containing Terraform configuration files. Like with the `plan` command, Terraform will determine which resources need to be created or modified. You should expect the same resources to be created when running `apply` that are shown when running `plan`. | 
|  | 200 | + | 
|  | 201 | +From the `terraform-aws-gw-lambda-tutorial` directory, run `terraform apply`. Terraform will pause to show you the resources it will create and prompt you to confirm resource creation. | 
|  | 202 | + | 
|  | 203 | +```bash | 
|  | 204 | +terraform apply | 
|  | 205 | +``` | 
|  | 206 | + | 
|  | 207 | +Review the output to confirm it will only create an AWS Lambda function and IAM role. Then, enter `yes` to confirm resource creation. Terraform will create the resources in your AWS account. Once complete, you can invoke the AWS Lambda function following the steps in the next section. | 
|  | 208 | + | 
|  | 209 | +## Invoke the created resource | 
|  | 210 | + | 
|  | 211 | +Next, invoke the AWS Lambda function to verify it was created and is successfully executing the application code. | 
|  | 212 | + | 
|  | 213 | +Use `terraform output` to retrieve the name of the AWS Lambda function you provisioned. This uses the outputs we added to the module in [create a module](./deploying-a-module.md#create-a-module) to retrieve the name of the Lambda function. Then, invoke the Lambda function directly using the AWS CLI, writing the response of the Lambda to a file called `lambda_output`. | 
|  | 214 | +```bash | 
|  | 215 | +#!/bin/bash | 
|  | 216 | +export FUNCTION_NAME=$(terraform output -raw function_name) | 
|  | 217 | +aws lambda invoke --function-name $FUNCTION_NAME --output json lambda_output | 
|  | 218 | +``` | 
|  | 219 | + | 
|  | 220 | +The lambda `invoke` command should return a JSON blob in response with the StatusCode of 200 and the ExecutedVersion of `$LATEST`. | 
|  | 221 | +```json | 
|  | 222 | +{ | 
|  | 223 | +    "StatusCode": 200, | 
|  | 224 | +    "ExecutedVersion": "$LATEST" | 
|  | 225 | +} | 
|  | 226 | +``` | 
|  | 227 | + | 
|  | 228 | +Inspect the contents of the `lambda_output` file, you should see a string stating `Hello from Gruntwork!`. | 
|  | 229 | + | 
|  | 230 | +## Clean up | 
|  | 231 | + | 
|  | 232 | +When you’ve completed the tutorial, clean up the resources you created to avoid incurring unexpected costs. | 
|  | 233 | + | 
|  | 234 | +First, execute the `terraform plan -destroy` command to show the AWS resources that will be destroyed. | 
|  | 235 | +```bash | 
|  | 236 | +terraform plan -destroy | 
|  | 237 | +``` | 
|  | 238 | + | 
|  | 239 | +Review the output, it should show two resources to be destroyed — an AWS Lambda function and IAM role. | 
|  | 240 | + | 
|  | 241 | +Next, execute the `destroy` command. | 
|  | 242 | + | 
|  | 243 | +```bash | 
|  | 244 | +terraform destroy | 
|  | 245 | +``` | 
|  | 246 | + | 
|  | 247 | +Finally, when prompted, enter `yes` to confirm the resource deletion. Terraform will begin destroying the resources created as part of this tutorial. | 
|  | 248 | + | 
|  | 249 | + | 
|  | 250 | +## What’s next | 
|  | 251 | + | 
|  | 252 | +Now that you’ve developed and deployed your first Terraform module, try creating another module that leverages the module you just created. For example, make your Lambda function available via a URL using an [AWS API Gateway HTTP API](../../reference/modules/terraform-aws-lambda/api-gateway-proxy/) with an AWS Lambda integration. Then, write a test using [Terratest](https://terratest.gruntwork.io/) that confirms your module creates resources as you’d expect. | 
|  | 253 | + | 
|  | 254 | +Finally, consider what other resources you would create to make your modules ready to use in production. For example, you would likely need to add [metrics](../../reference/modules/terraform-aws-monitoring/metrics/metrics.md) and [alerting](../../reference/modules/terraform-aws-monitoring/alarms/alarms.md). | 
0 commit comments