├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── api_method ├── main.tf ├── output.tf └── variables.tf ├── circle.yml ├── doc ├── big-picture.png └── hello.png ├── hello_lambda.py ├── hello_lambda.zip ├── lambda ├── main.tf ├── output.tf └── variables.tf ├── main.tf └── variables.tf /.gitignore: -------------------------------------------------------------------------------- 1 | # Terraform 2 | terraform.tfvars 3 | .terraform/ 4 | *.tfstate 5 | *.tfstate* 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | services: 4 | - docker 5 | 6 | language: bash 7 | 8 | script: 9 | - make test 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) TailorDev 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. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | modules = $(shell ls -1 */*.tf | xargs -I % dirname % |sort -u) 2 | 3 | TERRAFORM_VERSION = 0.9.1 4 | TERRAFORM_IMAGE = hashicorp/terraform 5 | TERRAFORM_CMD = docker run --rm -w /app -v ${HOME}/.aws:/root/.aws -v `pwd`:/app ${TERRAFORM_IMAGE}:${TERRAFORM_VERSION} 6 | 7 | .PHONY: test get 8 | 9 | default: test get 10 | 11 | get: 12 | @${TERRAFORM_CMD} get 13 | 14 | test: 15 | @for m in $(modules); do (${TERRAFORM_CMD} validate "$$m" && echo "√ $$m") || exit 1 ; done 16 | @(${TERRAFORM_CMD} validate . && echo "√ .") || exit 1 17 | 18 | plan: test get 19 | @${TERRAFORM_CMD} plan 20 | 21 | show: 22 | @${TERRAFORM_CMD} show 23 | 24 | apply: plan 25 | @${TERRAFORM_CMD} apply 26 | 27 | destroy: 28 | @${TERRAFORM_CMD} destroy -force 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Hello (AWS) Lambda with Terraform 2 | ================================= 3 | 4 | [![Build Status](https://travis-ci.org/BWITS/Terraform-lambda-apigateway.svg?branch=master)](https://travis-ci.org/BWITS/Terraform-lambda-apigateway) 5 | 6 | This project is an example of a Python (AWS) 7 | [Lambda](https://aws.amazon.com/lambda/) exposed with [API 8 | Gateway](https://aws.amazon.com/api-gateway/), configured with 9 | [Terraform](https://www.terraform.io/). This demo project is related to the 10 | following blog post: [A Tour of AWS 11 | Lambda](https://tailordev.fr/blog/2016/07/08/a-tour-of-aws-lambda/). 12 | 13 | 14 | ![](doc/big-picture.png) 15 | 16 | 17 | ## Introduction 18 | 19 | This demo project creates a `/hello` endpoint with two methods (`GET` and 20 | `POST`). Both methods are bound to a **single file** containing two handlers 21 | (a.k.a. lambda functions, one for each method). This is defined by a `handler` 22 | parameter. The code for each lambda function is written in Python (method names 23 | are just a convention): 24 | 25 | ```python 26 | def handler(event, context): 27 | return { "message": "Hello, World!" } 28 | 29 | def post_handler(event, context): 30 | return { "message": "I should have created something..." } 31 | ``` 32 | 33 | The [Terraform configuration](hello_lambda.tf) relies on two modules: 34 | [`lambda`](lambda/) and [`api_method`](api_method/). See the [Terraform Modules 35 | section](#terraform-modules) for further information. This configuration creates 36 | two lambda functions on AWS Lambda, a (deployed) REST API with a single endpoint 37 | and two HTTP methods on API Gateway, and takes care of the permissions and 38 | credentials. The figure below is an example of what you get in the API Gateway 39 | dashboard: 40 | 41 | ![](doc/hello.png) 42 | 43 | 44 | ## Getting started 45 | 46 | You must have an [AWS account](http://aws.amazon.com/). Next, you must [install 47 | Terraform](https://www.terraform.io/intro/getting-started/install.html) first. 48 | 49 | Clone this repository, then run: 50 | 51 | $ make get 52 | 53 | Create a `terraform.tfvars` file with the content below. This step is optional 54 | as Terraform will ask you to fill in the different values, but it is convenient. 55 | 56 | ```ini 57 | aws_region = "eu-west-1" 58 | ``` 59 | 60 | You are now ready to use Terraform! 61 | 62 | $ make plan 63 | 64 | If everything is OK, you can build the whole infrastructure: 65 | 66 | $ make apply 67 | 68 | You can destroy all the components by running: 69 | 70 | $ make destroy 71 | 72 | For more information, please read [the Terraform 73 | documentation](https://www.terraform.io/docs/index.html). 74 | 75 | 76 | ## Terraform Modules 77 | 78 | ### `lambda` 79 | 80 | ```hcl 81 | module "lambda" { 82 | source = "github.com/TailorDev/hello-lambda/lambda" 83 | name = "my-lambda" 84 | handler = "handler" 85 | runtime = "python2.7" # could be nodejs | nodejs4.3 | java8 | python2.7 86 | role = "my-role" 87 | } 88 | ``` 89 | 90 | **Important:** this module assumes that the source file, the lambda (in AWS), 91 | and the zip file have the **same name**. For example, we use `hello_lambda` in 92 | this project. The `handler` parameter distinguishes the different lambda 93 | functions that can be invoked. 94 | 95 | ### `api_method` 96 | 97 | ```hcl 98 | module "hello_post" { 99 | source = "github.com/TailorDev/hello-lambda/api_method" 100 | rest_api_id = "rest-api-id" 101 | resource_id = "resource-id" 102 | method = "POST" 103 | path = "resource-path" 104 | lambda = "my-lambda" 105 | region = "eu-west-1" 106 | account_id = "account-id" 107 | } 108 | ``` 109 | 110 | 111 | ## License 112 | 113 | This project and its Terraform modules are released under the MIT License. See 114 | the bundled [LICENSE](LICENSE.md) file for details. 115 | -------------------------------------------------------------------------------- /api_method/main.tf: -------------------------------------------------------------------------------- 1 | # Example: request for GET /hello 2 | resource "aws_api_gateway_method" "request_method" { 3 | rest_api_id = "${var.rest_api_id}" 4 | resource_id = "${var.resource_id}" 5 | http_method = "${var.method}" 6 | authorization = "NONE" 7 | } 8 | 9 | # Example: GET /hello => POST lambda 10 | resource "aws_api_gateway_integration" "request_method_integration" { 11 | rest_api_id = "${var.rest_api_id}" 12 | resource_id = "${var.resource_id}" 13 | http_method = "${aws_api_gateway_method.request_method.http_method}" 14 | type = "AWS" 15 | uri = "arn:aws:apigateway:${var.region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${var.region}:${var.account_id}:function:${var.lambda}/invocations" 16 | 17 | # AWS lambdas can only be invoked with the POST method 18 | integration_http_method = "POST" 19 | } 20 | 21 | # lambda => GET response 22 | resource "aws_api_gateway_method_response" "response_method" { 23 | rest_api_id = "${var.rest_api_id}" 24 | resource_id = "${var.resource_id}" 25 | http_method = "${aws_api_gateway_integration.request_method_integration.http_method}" 26 | status_code = "200" 27 | 28 | response_models = { 29 | "application/json" = "Empty" 30 | } 31 | } 32 | 33 | # Response for: GET /hello 34 | resource "aws_api_gateway_integration_response" "response_method_integration" { 35 | rest_api_id = "${var.rest_api_id}" 36 | resource_id = "${var.resource_id}" 37 | http_method = "${aws_api_gateway_method_response.response_method.http_method}" 38 | status_code = "${aws_api_gateway_method_response.response_method.status_code}" 39 | 40 | response_templates = { 41 | "application/json" = "" 42 | } 43 | } 44 | 45 | resource "aws_lambda_permission" "allow_api_gateway" { 46 | function_name = "${var.lambda}" 47 | statement_id = "AllowExecutionFromApiGateway" 48 | action = "lambda:InvokeFunction" 49 | principal = "apigateway.amazonaws.com" 50 | source_arn = "arn:aws:execute-api:${var.region}:${var.account_id}:${var.rest_api_id}/*/${var.method}${var.path}" 51 | } 52 | -------------------------------------------------------------------------------- /api_method/output.tf: -------------------------------------------------------------------------------- 1 | output "http_method" { 2 | value = "${aws_api_gateway_integration_response.response_method_integration.http_method}" 3 | } 4 | -------------------------------------------------------------------------------- /api_method/variables.tf: -------------------------------------------------------------------------------- 1 | variable "rest_api_id" { 2 | description = "The ID of the associated REST API" 3 | } 4 | 5 | variable "resource_id" { 6 | description = "The API resource ID" 7 | } 8 | 9 | variable "method" { 10 | description = "The HTTP method" 11 | default = "GET" 12 | } 13 | 14 | variable "path" { 15 | description = "The API resource path" 16 | } 17 | 18 | variable "lambda" { 19 | description = "The lambda name to invoke" 20 | } 21 | 22 | variable "region" { 23 | description = "The AWS region, e.g., eu-west-1" 24 | } 25 | 26 | variable "account_id" { 27 | description = "The AWS account ID" 28 | } 29 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | test: 2 | override: 3 | - make test 4 | -------------------------------------------------------------------------------- /doc/big-picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless-projects/Terraform-lambda-apigateway/af251ef9f416e49f4adbf1c84ad57bdc49223087/doc/big-picture.png -------------------------------------------------------------------------------- /doc/hello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless-projects/Terraform-lambda-apigateway/af251ef9f416e49f4adbf1c84ad57bdc49223087/doc/hello.png -------------------------------------------------------------------------------- /hello_lambda.py: -------------------------------------------------------------------------------- 1 | def handler(event, context): 2 | return { "message": "Hello, World!" } 3 | 4 | def post_handler(event, context): 5 | return { "message": "I should have created something..." } 6 | -------------------------------------------------------------------------------- /hello_lambda.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serverless-projects/Terraform-lambda-apigateway/af251ef9f416e49f4adbf1c84ad57bdc49223087/hello_lambda.zip -------------------------------------------------------------------------------- /lambda/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_lambda_function" "lambda" { 2 | filename = "${var.name}.zip" 3 | function_name = "${var.name}_${var.handler}" 4 | role = "${var.role}" 5 | handler = "${var.name}.${var.handler}" 6 | runtime = "${var.runtime}" 7 | } 8 | -------------------------------------------------------------------------------- /lambda/output.tf: -------------------------------------------------------------------------------- 1 | output "name" { 2 | value = "${aws_lambda_function.lambda.function_name}" 3 | } 4 | -------------------------------------------------------------------------------- /lambda/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | description = "The name of the lambda to create, which also defines (i) the archive name (.zip), (ii) the file name, and (iii) the function name" 3 | } 4 | 5 | variable "runtime" { 6 | description = "The runtime of the lambda to create" 7 | default = "nodejs" 8 | } 9 | 10 | variable "handler" { 11 | description = "The handler name of the lambda (a function defined in your lambda)" 12 | default = "handler" 13 | } 14 | 15 | variable "role" { 16 | description = "IAM role attached to the Lambda Function (ARN)" 17 | } 18 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = "${var.aws_region}" 3 | } 4 | 5 | data "aws_caller_identity" "current" { } 6 | 7 | # First, we need a role to play with Lambda 8 | resource "aws_iam_role" "iam_role_for_lambda" { 9 | name = "iam_role_for_lambda" 10 | 11 | assume_role_policy = <