├── .gitignore ├── LICENSE ├── README.md ├── main.tf ├── modules ├── global │ └── iam │ │ ├── apigw-role.json │ │ ├── dynamodb-policy.json │ │ ├── lambda-role.json │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf └── services │ ├── api-gateway │ ├── main.tf │ ├── outputs.tf │ └── variables.tf │ ├── dynamodb │ ├── main.tf │ ├── outputs.tf │ └── variables.tf │ └── lambda │ ├── main.tf │ ├── outputs.tf │ └── variables.tf ├── output.tf └── variables.tf /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 crisboarna 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform AWS API Gateway Lambda DynamoDB 2 | 3 | ### Terraform module for AWS API Gateway Lambda DynamoDB infrastructure 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT) 5 | ![stability-stable](https://img.shields.io/badge/stability-stable-brightgreen.svg) 6 | ![Commitizen-friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg) 7 | 8 | ## Table of Contents 9 | * [Features](#features) 10 | * [Usage](#usage) 11 | * [Deployment](#deployment) 12 | * [Example](#example) 13 | 14 | ## Features 15 | Terraform module which deploys a serverless HTTP endpoint backed by AWS API Gateway, Lambda & DynamoDB 16 | 17 | ***Attention*** 18 | 19 | Starting from version 1.15.0, this module targets Terraform 0.12+. If you are using Terraform <=v0.11 you must use up to version 1.14.0. 20 | 21 | ### API Gateway 22 | 23 | This module is created with a single stage that is given as parameter. 24 | The default path that is created is `/api/messages`. This can be expanded upon as the API GW ID, resources and methods are exposed. 25 | If you do not wish to have the default values, you can specify `api_gw_disable_resource_creation = true` and you can create the paths desired. 26 | Allows specification of Endpoint Configuration Type via variable `api_gw_endpoint_configuration_type` with `EDGE`, `REGIONAL` or `PRIVATE`. Defaults to `EDGE` 27 | 28 | 29 | **Note** 30 | 31 | This results in having to create the final `aws_api_gateway_deployment` as well. 32 | 33 | 34 | ### Lambda 35 | 36 | This module is created with full customization by user. 37 | - Can use either local filename path `lambda_file_name` or remote S3 bucket configuration. 38 | - Supports Lambda Layers 39 | - Supports VPC 40 | 41 | **Must** use either the local filename or S3 option as they are mutually exclusive. 42 | Exports S3 bucket to allow usage by multiple Lambda's but given `lambda_code_s3_bucket_use_existing=true` it will use existing S3 bucket provided in `lambda_code_s3_bucket_existing`. 43 | - This module by default, if created allows accompanying Lambda access to `dynamodb:PutItem`, `dynamodb:DescribeTable`, `dynamodb:DeleteItem`, `dynamodb:GetItem`, `dynamodb:Scan`, `dynamodb:Query` all DynamoDB tables. 44 | 45 | 46 | ### DynamoDB 47 | 48 | This module is optional. Lambda is created with R/W permission for DynamoDB to allow Lambda creation of tables or optionally to create them before-hand with this script. 49 | - This module by default, if created allows accompanying Lambda access to `dynamodb:PutItem`, `dynamodb:DescribeTable`, `dynamodb:DeleteItem`, `dynamodb:GetItem`, `dynamodb:Scan`, `dynamodb:Query` all DynamoDB tables. 50 | 51 | **NOTE** 52 | 53 | The attributes and table properties are in separate lists due to current HCL language parser limitations. Will update to single cohesive object once situation improves. 54 | ## Usage 55 | ```hcl-terraform 56 | module "api-gateway-lambda-dynamodb" { 57 | source = "crisboarna/api-gateway-lambda-dynamodb/aws" 58 | version = "1.16.0" 59 | 60 | # insert the 10 required variables here 61 | } 62 | ``` 63 | 64 | ## Deployment 65 | 1. Run build process to generate Lambda ZIP file locally to match `lambda_zip_path` variable path 66 | 2. Provide all needed variables from `variables.tf` file or copy paste and change example below 67 | 3. Create/Select Terraform workspace before deployment 68 | 4. Run `terraform plan -var-file="<.tfvars file>` to check for any errors and see what will be built 69 | 5. Run `terraform apply -var-file="<.tfvars file>` to deploy infrastructure 70 | 71 | **Example Deployment Script** 72 | ```sh 73 | #!/usr/bin/env bash 74 | 75 | if [[ ! -d .terraform ]]; then 76 | terraform init 77 | fi 78 | if ! terraform workspace list 2>&1 | grep -qi "$ENVIRONMENT"; then 79 | terraform workspace new "$ENVIRONMENT" 80 | fi 81 | terraform workspace select "$ENVIRONMENT" 82 | terraform get 83 | terraform plan -var-file=$1 84 | terraform apply -var-file=$1 85 | ``` 86 | 87 | ## Example 88 | ```hcl-terraform 89 | module "api_lambda_dynamodb" { 90 | source = "crisboarna/terraform-aws-api-gateway-lambda-dynamodb" 91 | version = "1.16.0" 92 | 93 | #Global 94 | region = "eu-west-1" 95 | project = "Awesome Project" 96 | 97 | #API Gateway 98 | api_gw_method = "POST" 99 | 100 | #Lambda 101 | lambda_function_name = "Awesome Endpoint" 102 | lambda_description = "Awesome HTTP Endpoint Lambda" 103 | lambda_runtime = "nodejs8.10" 104 | lambda_handler = "dist/bin/lambda.handler" 105 | lambda_timeout = 30 106 | lambda_code_s3_bucket = "awesome-project-bucket" 107 | lambda_code_s3_key = "awesome-project.zip" 108 | lambda_code_s3_storage_class = "ONEZONE_IA" 109 | lambda_code_s3_bucket_visibility = "private" 110 | lambda_zip_path = "../../awesome-project.zip" 111 | lambda_memory_size = 256 112 | lambda_vpc_security_group_ids = [aws_security_group.vpc_security_group.id] 113 | lambda_vpc_subnet_ids = [aws_subnet.vpc_subnet_a.id] 114 | lambda_layers = [data.aws_lambda_layer_version.layer.arn] 115 | 116 | #DynamoDB 117 | dynamodb_table_properties = [ 118 | { 119 | name = "Awesome Project Table 1" 120 | }, 121 | { 122 | name = "Awesome Project Table 2", 123 | read_capacity = 2, 124 | write_capacity = 3, 125 | hash_key = "KEY" 126 | range_key = "" 127 | stream_enabled = "true" 128 | stream_view_type = "NEW_IMAGE" 129 | } 130 | ] 131 | 132 | dynamodb_table_attributes = [[ 133 | { 134 | name = "KEY" 135 | type = "S" 136 | }],[ 137 | { 138 | name = "PRIMARY_KEY" 139 | type = "N" 140 | }, { 141 | name = "SECONDARY_KEY" 142 | type = "S" 143 | } 144 | ]] 145 | 146 | dynamodb_table_secondary_index = [[ 147 | { 148 | name = "GameTitleIndex" 149 | hash_key = "GameTitle" 150 | range_key = "TopScore" 151 | write_capacity = 10 152 | read_capacity = 10 153 | projection_type = "INCLUDE" 154 | non_key_attributes = ["UserId"] 155 | } 156 | ]] 157 | 158 | dynamodb_policy_action_list = ["dynamodb:PutItem", "dynamodb:DescribeTable", "dynamodb:DeleteItem", "dynamodb:GetItem", "dynamodb:Scan", "dynamodb:Query"] 159 | 160 | #Tags 161 | tags = { 162 | project = "Awesome Project" 163 | managedby = "Terraform" 164 | } 165 | 166 | #Lambda Environment variables 167 | environment_variables = { 168 | NODE_ENV = "production" 169 | } 170 | } 171 | ``` -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | #required otherwise circular dependency between IAM and Lambda 2 | locals { 3 | lambda_function_name = "${var.project}-${var.lambda_function_name}-${terraform.workspace}" 4 | dynamodb_tables_count = length(var.dynamodb_table_properties) 5 | } 6 | 7 | module "apigw" { 8 | source = "./modules/services/api-gateway" 9 | 10 | #Setup 11 | api_gw_name = "${var.project}-API-Gateway-${terraform.workspace}" 12 | api_gw_disable_resource_creation = var.api_gw_disable_resource_creation 13 | api_gw_endpoint_configuration_type = var.api_gw_endpoint_configuration_type 14 | stage_name = terraform.workspace 15 | method = var.api_gw_method 16 | lambda_arn = module.lambda.lambda_arn 17 | region = var.region 18 | lambda_name = module.lambda.lambda_name 19 | dependency_list = var.api_gw_dependency_list 20 | } 21 | 22 | module "lambda" { 23 | source = "./modules/services/lambda" 24 | 25 | #Setup 26 | region = var.region 27 | lambda_function_name = local.lambda_function_name 28 | lambda_description = var.lambda_description 29 | lambda_runtime = var.lambda_runtime 30 | lambda_handler = var.lambda_handler 31 | lambda_timeout = var.lambda_timeout 32 | lambda_file_name = var.lambda_file_name 33 | lambda_code_s3_bucket_existing = var.lambda_code_s3_bucket_existing 34 | lambda_code_s3_bucket_new = var.lambda_code_s3_bucket_new 35 | lambda_code_s3_bucket_use_existing = var.lambda_code_s3_bucket_use_existing 36 | lambda_code_s3_key = var.lambda_code_s3_key 37 | lambda_code_s3_storage_class = var.lambda_code_s3_storage_class 38 | lambda_code_s3_bucket_visibility = var.lambda_code_s3_bucket_visibility 39 | lambda_zip_path = var.lambda_zip_path 40 | lambda_memory_size = var.lambda_memory_size 41 | lambda_vpc_security_group_ids = var.lambda_vpc_security_group_ids 42 | lambda_vpc_subnet_ids = var.lambda_vpc_subnet_ids 43 | lambda_layers = var.lambda_layers 44 | 45 | #Internal 46 | lambda_role = module.iam.lambda_role_arn 47 | 48 | #Environment variables 49 | environment_variables = var.environment_variables 50 | 51 | #Tags 52 | tags = var.tags 53 | } 54 | 55 | module "dynamodb" { 56 | source = "./modules/services/dynamodb" 57 | 58 | #Setup 59 | dynamodb_table_properties = var.dynamodb_table_properties 60 | dynamodb_table_attributes = var.dynamodb_table_attributes 61 | dynamodb_table_local_secondary_index = var.dynamodb_table_local_secondary_index 62 | dynamodb_table_secondary_index = var.dynamodb_table_secondary_index 63 | dynamodb_table_ttl = var.dynamodb_table_ttl 64 | 65 | #Tags 66 | tags = var.tags 67 | } 68 | 69 | module "iam" { 70 | source = "./modules/global/iam" 71 | 72 | #Setup 73 | lambda_name = local.lambda_function_name 74 | lambda_layers = var.lambda_layers 75 | api_gw_name = module.apigw.api_gw_name 76 | api_gw_id = module.apigw.api_gw_id 77 | dynamodb_arn_list = module.dynamodb.dynamodb_table_arns 78 | dynamodb_policy_action_list = var.dynamodb_policy_action_list 79 | dynamodb_tables_count = local.dynamodb_tables_count 80 | } -------------------------------------------------------------------------------- /modules/global/iam/apigw-role.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "", 6 | "Effect": "Allow", 7 | "Principal": { 8 | "Service": "apigateway.amazonaws.com" 9 | }, 10 | "Action": "sts:AssumeRole" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /modules/global/iam/dynamodb-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "DynamoDBExecutions", 6 | "Effect": "Allow", 7 | "Action": [${policy_action_list}], 8 | "Resource": [${policy_arn_list}] 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /modules/global/iam/lambda-role.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "", 6 | "Effect": "Allow", 7 | "Principal": { 8 | "Service": "lambda.amazonaws.com" 9 | }, 10 | "Action": "sts:AssumeRole" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /modules/global/iam/main.tf: -------------------------------------------------------------------------------- 1 | data "template_file" "lambda_dynamodb_policy" { 2 | count = var.dynamodb_tables_count > 0 ? 1 : 0 3 | template = file("${path.module}/dynamodb-policy.json") 4 | vars = { 5 | policy_arn_list = join(", ", formatlist("\"%s\"", var.dynamodb_arn_list)) 6 | policy_action_list = join(", ", formatlist("\"%s\"", var.dynamodb_policy_action_list)) 7 | } 8 | } 9 | 10 | resource "aws_iam_role_policy" "DynamoDB-Policy" { 11 | count = var.dynamodb_tables_count > 0 ? 1 : 0 12 | name = "${aws_iam_role.lambda-role.name}-Policy" 13 | role = aws_iam_role.lambda-role.id 14 | policy = data.template_file.lambda_dynamodb_policy.rendered 15 | } 16 | 17 | 18 | #LAMBDA 19 | resource "aws_iam_role" "lambda-role" { 20 | name = "${var.lambda_name}-Role" 21 | assume_role_policy = file("${path.module}/lambda-role.json") 22 | } 23 | 24 | data "template_file" "lambda_layer_policy" { 25 | count = length(var.lambda_layers) > 0 ? 1 : 0 26 | template = file("${path.module}/data/dynamodb-policy.json") 27 | vars = { 28 | policy_arn_list = join( 29 | ", ", 30 | formatlist( 31 | "\"%s\"", 32 | var.lambda_layers 33 | ), 34 | ) 35 | policy_action_list = join( 36 | ", ", 37 | formatlist( 38 | "\"%s\"", 39 | [ 40 | "lambda:GetLayerVersion" 41 | ] 42 | ) 43 | ) 44 | } 45 | } 46 | 47 | resource "aws_iam_role_policy" "Layer-Policy" { 48 | count = length(var.lambda_layers) > 0 ? 1 : 0 49 | name = "${aws_iam_role.lambda-role.name}-Layer-Policy" 50 | role = aws_iam_role.lambda-role.id 51 | policy = data.template_file.lambda_layer_policy.rendered 52 | } 53 | 54 | 55 | #API GW 56 | resource "aws_iam_role" "apigw-role" { 57 | name = "${var.api_gw_name}-Role" 58 | assume_role_policy = file("${path.module}/apigw-role.json") 59 | } 60 | 61 | resource "aws_iam_role_policy_attachment" "Lambda-CloudWatch-Logs-ReadWrite" { 62 | policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess" 63 | role = aws_iam_role.lambda-role.name 64 | } 65 | 66 | resource "aws_iam_role_policy_attachment" "API-GW-CloudWatch-Logs-ReadWrite" { 67 | policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess" 68 | role = aws_iam_role.apigw-role.name 69 | } -------------------------------------------------------------------------------- /modules/global/iam/outputs.tf: -------------------------------------------------------------------------------- 1 | output "lambda_role_arn" { 2 | value = aws_iam_role.lambda-role.arn 3 | } 4 | 5 | output "lambda_role_id" { 6 | value = aws_iam_role.lambda-role.id 7 | } 8 | 9 | output "lambda_role_name" { 10 | value = aws_iam_role.lambda-role.name 11 | } -------------------------------------------------------------------------------- /modules/global/iam/variables.tf: -------------------------------------------------------------------------------- 1 | variable "lambda_name" { 2 | description = "The name of the Lambda function" 3 | } 4 | 5 | variable "lambda_layers" { 6 | description = "Lambda Layer ARNS" 7 | type = list(string) 8 | default = [] 9 | } 10 | 11 | variable "api_gw_name" { 12 | description = "The name of the API Gateway" 13 | } 14 | 15 | variable "api_gw_id" { 16 | description = "The API GW ID" 17 | } 18 | 19 | variable "dynamodb_arn_list" { 20 | type = "list" 21 | description = "List of ARN's to allow permissions for" 22 | } 23 | 24 | variable "dynamodb_policy_action_list" { 25 | type = "list" 26 | description = "List of ARN's to allow permissions for" 27 | } 28 | 29 | variable "dynamodb_tables_count" { 30 | description = "Number of DynamoDB tables being created" 31 | } -------------------------------------------------------------------------------- /modules/services/api-gateway/main.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" {} 2 | 3 | resource "aws_api_gateway_rest_api" "api" { 4 | name = var.api_gw_name 5 | endpoint_configuration { 6 | types = [var.api_gw_endpoint_configuration_type] 7 | } 8 | } 9 | 10 | resource "aws_api_gateway_deployment" "deployment" { 11 | count = var.api_gw_disable_resource_creation ? 0 : 1 12 | rest_api_id = aws_api_gateway_rest_api.api.id 13 | stage_name = var.stage_name 14 | depends_on = [aws_api_gateway_integration.request_method_integration,aws_api_gateway_integration_response.response_method_integration] 15 | } 16 | 17 | resource "aws_api_gateway_resource" "api_resource" { 18 | parent_id = aws_api_gateway_rest_api.api.root_resource_id 19 | path_part = "api" 20 | rest_api_id = aws_api_gateway_rest_api.api.id 21 | } 22 | 23 | resource "aws_api_gateway_resource" "messages_resource" { 24 | count = var.api_gw_disable_resource_creation ? 0 : 1 25 | parent_id = aws_api_gateway_resource.api_resource.id 26 | path_part = "messages" 27 | rest_api_id = aws_api_gateway_rest_api.api.id 28 | } 29 | 30 | resource "aws_api_gateway_method" "request_method" { 31 | count = var.api_gw_disable_resource_creation ? 0 : 1 32 | authorization = "NONE" 33 | http_method = var.method 34 | resource_id = aws_api_gateway_resource.messages_resource.id 35 | rest_api_id = aws_api_gateway_rest_api.api.id 36 | } 37 | 38 | resource "aws_api_gateway_integration" "request_method_integration" { 39 | count = var.api_gw_disable_resource_creation ? 0 : 1 40 | http_method = aws_api_gateway_method.request_method.http_method 41 | resource_id = aws_api_gateway_resource.messages_resource.id 42 | rest_api_id = aws_api_gateway_rest_api.api.id 43 | type = "AWS_PROXY" 44 | uri = "arn:aws:apigateway:${var.region}:lambda:path/2015-03-31/functions/${var.lambda_arn}/invocations" 45 | integration_http_method = "POST" 46 | } 47 | 48 | resource "aws_api_gateway_method_response" "response_method" { 49 | count = var.api_gw_disable_resource_creation ? 0 : 1 50 | http_method = aws_api_gateway_integration.request_method_integration.http_method 51 | resource_id = aws_api_gateway_resource.messages_resource.id 52 | rest_api_id = aws_api_gateway_rest_api.api.id 53 | status_code = "200" 54 | response_models = { 55 | "application/json" = "Empty" 56 | } 57 | } 58 | 59 | resource "aws_api_gateway_integration_response" "response_method_integration" { 60 | count = var.api_gw_disable_resource_creation ? 0 : 1 61 | http_method = aws_api_gateway_method_response.response_method.http_method 62 | resource_id = aws_api_gateway_resource.messages_resource.id 63 | rest_api_id = aws_api_gateway_rest_api.api.id 64 | status_code = aws_api_gateway_method_response.response_method.status_code 65 | } 66 | 67 | resource "aws_lambda_permission" "apigw-lambda-allow" { 68 | action = "lambda:InvokeFunction" 69 | function_name = var.lambda_name 70 | principal = "apigateway.amazonaws.com" 71 | statement_id = "AllowExecutionFromApiGateway" 72 | depends_on = ["aws_api_gateway_rest_api.api","aws_api_gateway_resource.api_resource"] 73 | source_arn = "arn:aws:execute-api:${var.region}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.api.id}/*/*" 74 | } -------------------------------------------------------------------------------- /modules/services/api-gateway/outputs.tf: -------------------------------------------------------------------------------- 1 | output "api_url" { 2 | value = element(compact(concat(list("default"), aws_api_gateway_deployment.deployment.*.invoke_url)), 0) 3 | } 4 | 5 | output "api_gw_id" { 6 | value = aws_api_gateway_rest_api.api.id 7 | } 8 | 9 | output "api_gw_name" { 10 | value = aws_api_gateway_rest_api.api.name 11 | } 12 | 13 | output "api_gw_root_resource_id" { 14 | value = aws_api_gateway_rest_api.api.root_resource_id 15 | } 16 | 17 | output "api_gw_api_resource_id" { 18 | value = aws_api_gateway_resource.api_resource.id 19 | } 20 | 21 | output "api_gw_message_resource_id" { 22 | //Done due to Terraform inability to evaluate one side of assertion and stop there if valid 23 | value = element(compact(concat(list("default"), aws_api_gateway_resource.messages_resource.*.id)), 0) 24 | } -------------------------------------------------------------------------------- /modules/services/api-gateway/variables.tf: -------------------------------------------------------------------------------- 1 | variable "api_gw_disable_resource_creation" { 2 | description = "Specify whether to create or not the default /api/messages path or stop at /api" 3 | } 4 | 5 | variable "api_gw_endpoint_configuration_type" { 6 | description = "Specify the type of endpoint for API GW to be setup as. [EDGE, REGIONAL, PRIVATE]. Defaults to EDGE" 7 | } 8 | 9 | variable "api_gw_name" { 10 | description = "The name of the REST API" 11 | } 12 | 13 | variable "stage_name" { 14 | description = "The stage name for the API deployment" 15 | default = "dev" 16 | } 17 | 18 | variable "method" { 19 | description = "The HTTP method" 20 | default = "POST" 21 | } 22 | 23 | variable "lambda_arn" { 24 | description = "The ARN of Lambda to invoke" 25 | } 26 | 27 | variable "lambda_name" { 28 | description = "The ARN of Lambda to invoke" 29 | } 30 | 31 | variable "region" { 32 | description = "The AWS region" 33 | } 34 | 35 | variable "dependency_list" { 36 | description = "Deployment dependency list" 37 | type = "list" 38 | } -------------------------------------------------------------------------------- /modules/services/dynamodb/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_dynamodb_table" "table" { 2 | count = length(var.dynamodb_table_properties) 3 | name = lookup(var.dynamodb_table_properties[count.index], "name") 4 | read_capacity = lookup(var.dynamodb_table_properties[count.index], "read_capacity", "1") 5 | write_capacity = lookup(var.dynamodb_table_properties[count.index], "write_capacity", "1") 6 | hash_key = lookup(var.dynamodb_table_properties[count.index], "hash_key") 7 | range_key = lookup(var.dynamodb_table_properties[count.index], "range_key", "") 8 | stream_enabled = lookup(var.dynamodb_table_properties[count.index], "stream_enabled", "") 9 | stream_view_type = lookup(var.dynamodb_table_properties[count.index], "stream_view_type", "") 10 | attribute = var.dynamodb_table_attributes[count.index] 11 | local_secondary_index = var.dynamodb_table_local_secondary_index[count.index] 12 | global_secondary_index = var.dynamodb_table_secondary_index[count.index] 13 | ttl = var.dynamodb_table_ttl[count.index] 14 | tags = var.tags 15 | } -------------------------------------------------------------------------------- /modules/services/dynamodb/outputs.tf: -------------------------------------------------------------------------------- 1 | output "dynamodb_table_names" { 2 | value = [aws_dynamodb_table.table.*.name] 3 | } 4 | 5 | output "dynamodb_table_hash_keys" { 6 | value = [aws_dynamodb_table.table.*.hash_key] 7 | } 8 | 9 | output "dynamodb_table_range_keys" { 10 | value = [aws_dynamodb_table.table.*.range_key] 11 | } 12 | 13 | output "dynamodb_table_arns" { 14 | value = [aws_dynamodb_table.table.*.arn] 15 | } 16 | 17 | output "dynamodb_table_stream_arns" { 18 | value = [aws_dynamodb_table.table.*.stream_arn] 19 | } -------------------------------------------------------------------------------- /modules/services/dynamodb/variables.tf: -------------------------------------------------------------------------------- 1 | #TAGS 2 | variable "tags" { 3 | type = "map" 4 | description = "Tags for lambda" 5 | default = {} 6 | } 7 | 8 | #Setup 9 | variable "dynamodb_table_properties" { 10 | type = "list" 11 | description = "List of maps representing a table each. name (required), read_capacity(default=1), write_capacity(default=1), attributes(list), hash_key(required)" 12 | } 13 | 14 | variable "dynamodb_table_attributes" { 15 | type = "list" 16 | description = "List of maps representing each table attributes list. Required due to current HCL limitations" 17 | } 18 | 19 | variable "dynamodb_table_secondary_index" { 20 | type = "list" 21 | default = [[]] 22 | description = "List of list of maps representing each table secondary index list. Required due to current HCL limitations" 23 | } 24 | 25 | variable "dynamodb_table_local_secondary_index" { 26 | type = "list" 27 | default = [[]] 28 | description = "List of list of maps representing each table local secondary index list. Required due to current HCL limitations" 29 | } 30 | 31 | variable "dynamodb_table_ttl" { 32 | type = "list" 33 | default = [[]] 34 | description = "List of list of maps representing each table local secondary index list. Required due to current HCL limitations" 35 | } -------------------------------------------------------------------------------- /modules/services/lambda/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | s3_bucket = var.lambda_code_s3_bucket_use_existing ? var.lambda_code_s3_bucket_existing : join("", aws_s3_bucket.lambda_repo.*.bucket) 3 | } 4 | 5 | resource "aws_lambda_function" "lambda_file" { 6 | count = var.lambda_file_name == "defaultLambdaFile.zip" ? 0 : 1 7 | function_name = var.lambda_function_name 8 | filename = var.lambda_file_name 9 | description = var.lambda_description 10 | runtime = var.lambda_runtime 11 | handler = var.lambda_handler 12 | role = var.lambda_role 13 | timeout = var.lambda_timeout 14 | source_code_hash = base64sha256(file(var.lambda_file_name)) 15 | memory_size = var.lambda_memory_size 16 | layers = var.lambda_layers 17 | 18 | vpc_config { 19 | security_group_ids = var.lambda_vpc_security_group_ids 20 | subnet_ids = var.lambda_vpc_subnet_ids 21 | } 22 | 23 | environment { 24 | variables = var.environment_variables 25 | } 26 | 27 | tags = var.tags 28 | } 29 | 30 | resource "aws_lambda_function" "lambda_s3" { 31 | count = var.lambda_file_name == "defaultLambdaFile.zip" && (var.lambda_code_s3_bucket_existing != "defaultBucket" || var.lambda_code_s3_bucket_new != "defaultBucket") ? 1: 0 32 | function_name = var.lambda_function_name 33 | description = var.lambda_description 34 | runtime = var.lambda_runtime 35 | handler = var.lambda_handler 36 | role = var.lambda_role 37 | timeout = var.lambda_timeout 38 | source_code_hash = aws_s3_bucket_object.lambda_dist.etag 39 | s3_bucket = local.s3_bucket 40 | s3_key = var.lambda_code_s3_key 41 | memory_size = var.lambda_memory_size 42 | layers = var.lambda_layers 43 | 44 | vpc_config { 45 | security_group_ids = var.lambda_vpc_security_group_ids 46 | subnet_ids = var.lambda_vpc_subnet_ids 47 | } 48 | 49 | environment { 50 | variables = var.environment_variables 51 | } 52 | 53 | tags = var.tags 54 | } 55 | 56 | resource "aws_s3_bucket" "lambda_repo" { 57 | count = var.lambda_file_name == "defaultLambdaFile.zip" && var.lambda_code_s3_bucket_existing == "defaultBucket" && var.lambda_code_s3_bucket_new != "defaultBucket" ? 1 : 0 58 | bucket = var.lambda_code_s3_bucket_new 59 | region = var.region 60 | acl = var.lambda_code_s3_bucket_visibility 61 | tags = var.tags 62 | } 63 | 64 | resource "aws_s3_bucket_object" "lambda_dist" { 65 | count = var.lambda_file_name == "defaultLambdaFile.zip" && (var.lambda_code_s3_bucket_existing != "defaultBucket" || var.lambda_code_s3_bucket_new != "defaultBucket") ? 1 : 0 66 | bucket = local.s3_bucket 67 | key = var.lambda_code_s3_key 68 | source = var.lambda_zip_path 69 | etag = md5(file(var.lambda_zip_path)) 70 | storage_class = var.lambda_code_s3_storage_class 71 | } -------------------------------------------------------------------------------- /modules/services/lambda/outputs.tf: -------------------------------------------------------------------------------- 1 | output "lambda_name" { 2 | value = var.lambda_file_name == "defaultLambdaFile.zip" ? join(",",aws_lambda_function.lambda_s3.*.function_name) : join(",", aws_lambda_function.lambda_file.*.function_name) 3 | } 4 | 5 | output "lambda_arn" { 6 | value = var.lambda_file_name == "defaultLambdaFile.zip" ? join(",",aws_lambda_function.lambda_s3.*.arn) : join(",", aws_lambda_function.lambda_file.*.arn) 7 | } 8 | 9 | output "lambda_s3_bucket" { 10 | value = var.lambda_file_name == "defaultLambdaFile.zip" ? join(",",aws_s3_bucket.lambda_repo.*.bucket) : "" 11 | } -------------------------------------------------------------------------------- /modules/services/lambda/variables.tf: -------------------------------------------------------------------------------- 1 | #TAGS 2 | variable "tags" { 3 | type = "map" 4 | description = "Tags for lambda" 5 | default = {} 6 | } 7 | 8 | # ENV VARS 9 | variable "environment_variables" { 10 | type = "map" 11 | description = "Environment variables" 12 | } 13 | 14 | #SETUP 15 | variable "region" { 16 | description = "Region of Lambda & S3 source code" 17 | } 18 | 19 | variable "lambda_function_name" { 20 | description = "The name of the Lambda function" 21 | } 22 | 23 | variable "lambda_description" { 24 | description = "Lambda description" 25 | } 26 | 27 | variable "lambda_runtime" { 28 | description = "The runtime of the Lambda to create" 29 | } 30 | 31 | variable "lambda_handler" { 32 | description = "The name of Lambda function handler" 33 | } 34 | 35 | variable "lambda_role" { 36 | description = "IAM role attached to Lambda function - ARN" 37 | } 38 | 39 | variable "lambda_timeout" { 40 | description = "Maximum runtime for Lambda" 41 | default = 30 42 | } 43 | 44 | variable "lambda_file_name" { 45 | description = "Path to lambda code zip" 46 | } 47 | 48 | variable "lambda_code_s3_bucket_new" { 49 | description = "S3 bucket with source code" 50 | } 51 | 52 | variable "lambda_code_s3_bucket_use_existing" { 53 | description = "Boolean flag to specify whether to use 'lambda_code_s3_bucket_new' and create new bucket or to use 'lambda_code_s3_bucket_existing and use existing S3 bucket and now a generate new one" 54 | } 55 | 56 | variable "lambda_code_s3_bucket_existing" { 57 | description = "Existing 'aws_s3_bucket.bucket'" 58 | } 59 | 60 | variable "lambda_code_s3_key" { 61 | description = "Location of Lambda code in S3 bucket" 62 | } 63 | 64 | variable "lambda_code_s3_storage_class" { 65 | description = "Lambda code S3 storage class" 66 | } 67 | 68 | variable "lambda_code_s3_bucket_visibility" { 69 | description = "S3 bucket ACL" 70 | } 71 | 72 | variable "lambda_zip_path" { 73 | description = "Local path to Lambda source dist" 74 | } 75 | 76 | variable "lambda_memory_size" { 77 | description = "Lambda memory size" 78 | default = 128 79 | } 80 | 81 | variable "lambda_vpc_security_group_ids" { 82 | description = "Lambda VPC Security Group IDs" 83 | type = list(string) 84 | default = [] 85 | } 86 | 87 | variable "lambda_vpc_subnet_ids" { 88 | description = "Lambda VPC Subnet IDs" 89 | type = list(string) 90 | default = [] 91 | } 92 | 93 | variable "lambda_layers" { 94 | description = "Lambda Layer ARNS" 95 | type = list(string) 96 | default = [] 97 | } -------------------------------------------------------------------------------- /output.tf: -------------------------------------------------------------------------------- 1 | #API Gateway 2 | output "api_gw_api_url" { 3 | value = module.apigw.api_url 4 | } 5 | 6 | output "api_gw_name" { 7 | value = module.apigw.api_gw_name 8 | } 9 | 10 | output "api_gw_id" { 11 | value = module.apigw.api_gw_id 12 | } 13 | 14 | output "api_gw_root_resource_id" { 15 | value = module.apigw.api_gw_root_resource_id 16 | } 17 | 18 | output "api_gw_api_resource_id" { 19 | value = module.apigw.api_gw_api_resource_id 20 | } 21 | 22 | output "api_gw_message_resource_id" { 23 | value = module.apigw.api_gw_message_resource_id 24 | } 25 | 26 | #Lambda 27 | output "lambda_name" { 28 | value = module.lambda.lambda_name 29 | } 30 | 31 | output "lambda_arn" { 32 | value = module.lambda.lambda_arn 33 | } 34 | 35 | output "lambda_role_id" { 36 | value = module.iam.lambda_role_id 37 | } 38 | 39 | output "lambda_role_arn" { 40 | value = module.iam.lambda_role_arn 41 | } 42 | 43 | output "lambda_role_name" { 44 | value = module.iam.lambda_role_name 45 | } 46 | 47 | output "lambda_s3_bucket" { 48 | value = module.lambda.lambda_s3_bucket 49 | } 50 | 51 | output "dynamodb_table_name" { 52 | value = module.dynamodb.dynamodb_table_names 53 | } 54 | 55 | output "dynamodb_table_hash_keys" { 56 | value = [module.dynamodb.dynamodb_table_hash_keys] 57 | } 58 | 59 | output "dynamodb_table_range_keys" { 60 | value = [module.dynamodb.dynamodb_table_range_keys] 61 | } 62 | 63 | output "dynamodb_table_arns" { 64 | value = [module.dynamodb.dynamodb_table_arns] 65 | } 66 | 67 | output "dynamodb_table_stream_arns" { 68 | value = [module.dynamodb.dynamodb_table_stream_arns] 69 | } -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | #TAGS 2 | variable "tags" { 3 | type = "map" 4 | description = "Tags for lambda" 5 | default = {} 6 | } 7 | 8 | 9 | #Environment variables 10 | variable "environment_variables" { 11 | type = "map" 12 | description = "Azure Bot Subscription ID" 13 | } 14 | 15 | 16 | #SETUP 17 | 18 | #Global 19 | variable "region" { 20 | description = "Region to deploy in" 21 | } 22 | 23 | variable "project" { 24 | description = "Name of project" 25 | } 26 | 27 | #Lambda 28 | variable "lambda_function_name" { 29 | description = "Local path to Lambda zip code" 30 | } 31 | 32 | variable "lambda_description" { 33 | default = "" 34 | description = "Lambda description" 35 | } 36 | 37 | variable "lambda_runtime" { 38 | description = "Lambda runtime" 39 | } 40 | 41 | variable "lambda_handler" { 42 | description = "Lambda handler path" 43 | } 44 | 45 | variable "lambda_timeout" { 46 | description = "Maximum runtime for Lambda" 47 | default = 30 48 | } 49 | 50 | variable "lambda_file_name" { 51 | default = "defaultLambdaFile.zip" 52 | description = "Path to lambda code zip" 53 | } 54 | 55 | variable "lambda_code_s3_bucket_new" { 56 | default = "defaultBucket" 57 | description = "S3 bucket with source code" 58 | } 59 | 60 | variable "lambda_code_s3_bucket_use_existing" { 61 | default = "true" 62 | description = "Boolean flag to specify whether to use 'lambda_code_s3_bucket_new' and create new bucket or to use 'lambda_code_s3_bucket_existing and use existing S3 bucket and now a generate new one" 63 | } 64 | 65 | variable "lambda_code_s3_bucket_existing" { 66 | default = "defaultBucket" 67 | description = "Existing 'aws_s3_bucket.bucket'" 68 | } 69 | 70 | variable "lambda_code_s3_key" { 71 | default = "defaultS3Key" 72 | description = "Location of Lambda code in S3 bucket" 73 | } 74 | 75 | variable "lambda_code_s3_storage_class" { 76 | default = "ONEZONE_IA" 77 | description = "Lambda code S3 storage class" 78 | } 79 | 80 | variable "lambda_code_s3_bucket_visibility" { 81 | default = "private" 82 | description = "S3 bucket ACL" 83 | } 84 | 85 | variable "lambda_zip_path" { 86 | default = "defaultZipPath" 87 | description = "Local path to Lambda zip code" 88 | } 89 | 90 | variable "lambda_memory_size" { 91 | description = "Lambda memory size" 92 | } 93 | 94 | variable "lambda_vpc_security_group_ids" { 95 | description = "Lambda VPC Security Group IDs" 96 | type = list(string) 97 | default = [] 98 | } 99 | 100 | variable "lambda_vpc_subnet_ids" { 101 | description = "Lambda VPC Subnet IDs" 102 | type = list(string) 103 | default = [] 104 | } 105 | 106 | variable "lambda_layers" { 107 | description = "Lambda Layer ARNS" 108 | type = list(string) 109 | default = [] 110 | } 111 | 112 | #API Gateway Setup 113 | variable "api_gw_method" { 114 | description = "API Gateway method (GET,POST...)" 115 | default = "POST" 116 | } 117 | 118 | variable "api_gw_dependency_list" { 119 | description = "List of aws_api_gateway_integration* that require aws_api_gateway_deployment dependency" 120 | type = "list" 121 | default = [] 122 | } 123 | 124 | variable "api_gw_disable_resource_creation" { 125 | description = "Specify whether to create or not the default /api/messages path or stop at /api" 126 | default = "false" 127 | } 128 | 129 | variable "api_gw_endpoint_configuration_type" { 130 | description = "Specify the type of endpoint for API GW to be setup as. [EDGE, REGIONAL, PRIVATE]. Defaults to EDGE" 131 | default = "EDGE" 132 | } 133 | 134 | #DynamoDB 135 | variable "dynamodb_table_properties" { 136 | type = "list" 137 | description = "List of maps representing a table each. name (required), read_capacity(default=1), write_capacity(default=1), hash_key(required)" 138 | } 139 | 140 | variable "dynamodb_table_attributes" { 141 | type = "list" 142 | description = "List of list of maps representing each table attributes list. Required due to current HCL limitations" 143 | } 144 | 145 | variable "dynamodb_table_secondary_index" { 146 | type = "list" 147 | default = [[]] 148 | description = "List of list of maps representing each table secondary index list. Required due to current HCL limitations" 149 | } 150 | 151 | variable "dynamodb_table_local_secondary_index" { 152 | type = "list" 153 | default = [[]] 154 | description = "List of list of maps representing each table local secondary index list. Required due to current HCL limitations" 155 | } 156 | 157 | variable "dynamodb_policy_action_list" { 158 | description = "List of Actions to be executed" 159 | type = "list" 160 | default = ["dynamodb:DescribeTable", "dynamodb:DeleteItem", "dynamodb:GetItem", "dynamodb:Scan", "dynamodb:Query"] 161 | } 162 | 163 | variable "dynamodb_table_ttl" { 164 | type = "list" 165 | default = [[]] 166 | description = "List of list of maps representing each table local secondary index list. Required due to current HCL limitations" 167 | } --------------------------------------------------------------------------------