├── .gitignore ├── go.mod ├── Makefile ├── main.go ├── .terraform.lock.hcl ├── go.sum ├── README.md └── main.tf /.gitignore: -------------------------------------------------------------------------------- 1 | .terraform 2 | terraform.tfstate 3 | terraform.tfstate.backup 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/snsinfu/terraform-lambda-example 2 | 3 | go 1.16 4 | 5 | require github.com/aws/aws-lambda-go v1.23.0 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test clean 2 | 3 | test: deploy.done 4 | curl -fsSL -D - "$$(terraform output -raw url)?name=Lambda" 5 | 6 | clean: 7 | terraform destroy 8 | rm -f init.done deploy.done hello.zip hello 9 | 10 | init.done: 11 | terraform init 12 | touch $@ 13 | 14 | deploy.done: init.done main.tf hello.zip 15 | terraform apply 16 | touch $@ 17 | 18 | hello.zip: hello 19 | zip $@ $< 20 | 21 | hello: main.go 22 | go get . 23 | GOOS=linux GOARCH=amd64 go build -o $@ 24 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/aws/aws-lambda-go/events" 8 | "github.com/aws/aws-lambda-go/lambda" 9 | ) 10 | 11 | // The input type and the output type are defined by the API Gateway. 12 | func handleRequest(req events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { 13 | name, ok := req.QueryStringParameters["name"] 14 | if !ok { 15 | res := events.APIGatewayProxyResponse{ 16 | StatusCode: http.StatusBadRequest, 17 | } 18 | return res, nil 19 | } 20 | 21 | res := events.APIGatewayProxyResponse{ 22 | StatusCode: http.StatusOK, 23 | Headers: map[string]string{"Content-Type": "text/plain; charset=utf-8"}, 24 | Body: fmt.Sprintf("Hello, %s!\n", name), 25 | } 26 | return res, nil 27 | } 28 | 29 | func main() { 30 | lambda.Start(handleRequest) 31 | } 32 | -------------------------------------------------------------------------------- /.terraform.lock.hcl: -------------------------------------------------------------------------------- 1 | # This file is maintained automatically by "terraform init". 2 | # Manual edits may be lost in future updates. 3 | 4 | provider "registry.terraform.io/hashicorp/aws" { 5 | version = "3.35.0" 6 | hashes = [ 7 | "h1:xOkRDvfIw457egXTu+4LAl7Kog31KKx339t8N+7TgPU=", 8 | "zh:0b7cf15369fe940190f2e3fd77300119a16a9b821a7b15e049a6e349126b833d", 9 | "zh:65680b35a45df6dc9ebe4439aa28dbe5767f8745443d0807656759d81ed23f5d", 10 | "zh:75a71517d40b842308bc7a13a8dadcade99de3344292318b28a3ad95f09994e7", 11 | "zh:865e618aa41f4a3842e818f6389f4aac89a985b409d1bb108ac753ea6292215d", 12 | "zh:893658f93e57ea6c2bb458cdcf7d51e617147f53a9b2ed7741689bec3cfca4f9", 13 | "zh:89d63eab0ad7fe3a891e6567052dd3227520ae48c212465d3a2b0bed326b319d", 14 | "zh:94a408adcc52ce1758ecc2aad8394c35254029d18f358392b86602705a688b3d", 15 | "zh:a82d09d03cb5480b3b4f318d1db3a64d80a701a429f3a84520e4a26f66b9a178", 16 | "zh:aee92e745c43de2cc30913bddd8e625a8c6511f900f8e4104a0ccb746f276428", 17 | "zh:b1178f4c2db30d7e49c3af441f79ac932bb5b8a8f05b0d770515b732ad4ac388", 18 | "zh:b2684d0124ed6c394c83005def7387a6f8c9ac5f459dd7fc2fe56ea1aa97b20c", 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/aws/aws-lambda-go v1.23.0 h1:Vjwow5COkFJp7GePkk9kjAo/DyX36b7wVPKwseQZbRo= 3 | github.com/aws/aws-lambda-go v1.23.0/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU= 4 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 5 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 12 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 13 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 14 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 15 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 16 | github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= 17 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 18 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 19 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 20 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= 21 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform + Golang AWS Lambda + API Gateway 2 | 3 | This is a minimal Hello World example of deploying an HTTP API backed by an AWS Lambda function. The function is written in Go and deployment is automated with Terraform. 4 | 5 | - [Prerequisites](#prerequisites) 6 | - [Usage](#usage) 7 | - [References](#references) 8 | 9 | ## Prerequisites 10 | 11 | ### Terraform and Go 12 | 13 | Install [Terraform][terraform] and [Go][golang]. On macOS with Homebrew: 14 | 15 | ```console 16 | $ brew install go terraform 17 | ``` 18 | 19 | [terraform]: https://www.terraform.io/ 20 | [golang]: https://www.terraform.io/ 21 | 22 | ### AWS credentials 23 | 24 | Configure your AWS access key and secret key with the `aws configure` command, or just create a file `~/.aws/credentials` containing the keys: 25 | 26 | ``` 27 | [default] 28 | aws_access_key_id = KEY 29 | aws_secret_access_key = KEY 30 | ``` 31 | 32 | The access key ID and the secret access key can be generated in the AWS management console. 33 | 34 | ### AWS region 35 | 36 | The environment variable `AWS_DEFAULT_REGION` should be set to your favorite region. `us-east-1` would just work if you are not sure: 37 | 38 | ```console 39 | $ export AWS_DEFAULT_REGION=us-east-1 40 | ``` 41 | 42 | This environment variable is used by the [Terraform AWS provider][terraform-aws]. 43 | 44 | [terraform-aws]: https://www.terraform.io/docs/providers/aws/ 45 | 46 | ## Usage 47 | 48 | Run `make` to build and deploy an API: 49 | 50 | ```console 51 | $ make 52 | ``` 53 | 54 | In the process Terraform will ask you for a confirmation, so type `yes`. Everything should finish in less than a minute! After this you can play with the API: 55 | 56 | ```console 57 | $ curl -fsSL $(terraform output -raw url)?name=world 58 | Hello, world! 59 | $ curl -fsSL $(terraform output -raw url)?name=lambda 60 | Hello, lambda! 61 | ``` 62 | 63 | Cleanup: 64 | 65 | ```console 66 | $ make clean 67 | ``` 68 | 69 | ### About the Makefile 70 | 71 | The Makefile is for convenience and does nothing special. It just runs following commands for you: 72 | 73 | ```console 74 | $ terraform init 75 | $ go get . 76 | $ GOOS=linux GOARCH=amd64 go build -o hello 77 | $ zip hello.zip hello 78 | $ terraform apply 79 | $ terraform destroy 80 | ``` 81 | 82 | ## References 83 | 84 | ### Lambda 85 | 86 | - [AWS Lambda Developer Guide](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html) 87 | - [Building Lambda Functions](https://docs.aws.amazon.com/lambda/latest/dg/lambda-app.html) 88 | 89 | ### API Gateway 90 | 91 | - [AWS API Gateway Developer Guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html) 92 | - [Build an API Gateway API with Lambda Integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-with-lambda-integration.html) 93 | - [Deploying an API in Amazon API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-deploy-api.html) 94 | 95 | ### Terraform 96 | 97 | - [Terraform AWS provider](https://www.terraform.io/docs/providers/aws/) 98 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | # This is required to get the AWS region via ${data.aws_region.current}. 2 | data "aws_region" "current" { 3 | } 4 | 5 | # Define a Lambda function. 6 | # 7 | # The handler is the name of the executable for go1.x runtime. 8 | resource "aws_lambda_function" "hello" { 9 | function_name = "hello" 10 | filename = "hello.zip" 11 | handler = "hello" 12 | source_code_hash = sha256(filebase64("hello.zip")) 13 | role = aws_iam_role.hello.arn 14 | runtime = "go1.x" 15 | memory_size = 128 16 | timeout = 1 17 | } 18 | 19 | # A Lambda function may access to other AWS resources such as S3 bucket. So an 20 | # IAM role needs to be defined. This hello world example does not access to 21 | # any resource, so the role is empty. 22 | # 23 | # The date 2012-10-17 is just the version of the policy language used here [1]. 24 | # 25 | # [1]: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html 26 | resource "aws_iam_role" "hello" { 27 | name = "hello" 28 | assume_role_policy = < API Gateway 64 | resource "aws_api_gateway_method" "hello" { 65 | rest_api_id = aws_api_gateway_rest_api.hello.id 66 | resource_id = aws_api_gateway_resource.hello.id 67 | http_method = "GET" 68 | authorization = "NONE" 69 | } 70 | 71 | # POST 72 | # API Gateway ------> Lambda 73 | # For Lambda the method is always POST and the type is always AWS_PROXY. 74 | # 75 | # The date 2015-03-31 in the URI is just the version of AWS Lambda. 76 | resource "aws_api_gateway_integration" "hello" { 77 | rest_api_id = aws_api_gateway_rest_api.hello.id 78 | resource_id = aws_api_gateway_resource.hello.id 79 | http_method = aws_api_gateway_method.hello.http_method 80 | integration_http_method = "POST" 81 | type = "AWS_PROXY" 82 | uri = "arn:aws:apigateway:${data.aws_region.current.name}:lambda:path/2015-03-31/functions/${aws_lambda_function.hello.arn}/invocations" 83 | } 84 | 85 | # This resource defines the URL of the API Gateway. 86 | resource "aws_api_gateway_deployment" "hello_v1" { 87 | depends_on = [ 88 | "aws_api_gateway_integration.hello" 89 | ] 90 | rest_api_id = aws_api_gateway_rest_api.hello.id 91 | stage_name = "v1" 92 | } 93 | 94 | # Set the generated URL as an output. Run `terraform output url` to get this. 95 | output "url" { 96 | value = "${aws_api_gateway_deployment.hello_v1.invoke_url}${aws_api_gateway_resource.hello.path}" 97 | } 98 | --------------------------------------------------------------------------------