Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add go sample for API Gateway #601

Merged
merged 2 commits into from Mar 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 42 additions & 0 deletions aws-go-lambda-gateway/Gopkg.toml
@@ -0,0 +1,42 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true


[[constraint]]
name = "github.com/pulumi/pulumi"
version = "1.12.0"

[[constraint]]
name = "github.com/pulumi/pulumi-aws"
version = "1.25.0"

[[constraint]]
name = "github.com/aws/aws-lambda-go"
version = "1.15.0"

[prune]
go-tests = true
unused-packages = true
3 changes: 3 additions & 0 deletions aws-go-lambda-gateway/Makefile
@@ -0,0 +1,3 @@
build::
GOOS=linux GOARCH=amd64 go build -o ./handler/handler ./handler/handler.go
zip -j ./handler/handler.zip ./handler/handler
13 changes: 13 additions & 0 deletions aws-go-lambda-gateway/Pulumi.yaml
@@ -0,0 +1,13 @@
name: go-lambda
runtime: go
description: Basic example of an AWS lambda With API Gateway
template:
config:
aws:region:
description: The AWS region to deploy into
default: us-east-1
go-lambda:accountid:
description: The AWS account ID to use
go-lambda:gateway-region:
description: The AWS region to deploy the AWS API Gateway to
default: us-east-1
124 changes: 124 additions & 0 deletions aws-go-lambda-gateway/README.md
@@ -0,0 +1,124 @@
# AWS Golang Lambda With API Gateway

This example creates a lambda that does a simple `ToUpper` on the path input of an API request and returns it.

## Deploying the App

To deploy your infrastructure, follow the below steps.

### Prerequisites

1. [Install Pulumi](https://www.pulumi.com/docs/get-started/install/)
2. [Configure AWS Credentials](https://www.pulumi.com/docs/intro/cloud-providers/aws/setup/)
3. [Clone aws-go-lambda](https://github.com/aws/aws-lambda-go)

### Steps

After cloning this repo, run these commands from the working directory:

1. Restore your Go dependencies. This example currently uses [Dep](https://github.com/golang/dep) to do so:

```bash
$ dep ensure
```

1. Build the handler:

- For developers on Linux and macOS:

```bash
make build
```

- For developers on Windows:

- Get the `build-lambda-zip` tool:

```bash
set GO111MODULE=on
go.exe get -u github.com/aws/aws-lambda-go/cmd/build-lambda-zip
```

- Use the tool from your GOPATH:

```bash
set GOOS=linux
set GOARCH=amd64
set CGO_ENABLED=0
go build -o handler\handler handler\handler.go
%USERPROFILE%\Go\bin\build-lambda-zip.exe -o handler\handler.zip handler\handler
```


2. Create a new Pulumi stack, which is an isolated deployment target for this example:

```bash
pulumi stack init
```

3. Set the required configuration variables for this program:
```bash
$ pulumi config set aws:region us-west-2
$ pulumi config set accountid <your AWS account ID>
$ pulumi config set gateway-region <the region where you want to deploy your API gateway>
```

4. Execute the Pulumi program to create our lambda:

```bash
$ pulumi up
Previewing update (dev):
Type Name Plan
+ pulumi:pulumi:Stack go-lambda-dev create
+ ├─ aws:apigateway:RestApi UpperCaseGateway create
+ ├─ aws:iam:Role task-exec-role create
+ ├─ aws:apigateway:Resource UpperAPI create
+ ├─ aws:iam:RolePolicy lambda-log-policy create
+ ├─ aws:apigateway:Method AnyMethod create
+ ├─ aws:lambda:Function basicLambda create
+ ├─ aws:lambda:Permission APIPermission create
+ ├─ aws:apigateway:Integration LambdaIntegration create
+ └─ aws:apigateway:Deployment APIDeployment create

Resources:
+ 10 to create

Do you want to perform this update? yes
Updating (dev):
Type Name Status
+ pulumi:pulumi:Stack go-lambda-dev created
+ ├─ aws:apigateway:RestApi UpperCaseGateway created
+ ├─ aws:iam:Role task-exec-role created
+ ├─ aws:apigateway:Resource UpperAPI created
+ ├─ aws:iam:RolePolicy lambda-log-policy created
+ ├─ aws:apigateway:Method AnyMethod created
+ ├─ aws:lambda:Function basicLambda created
+ ├─ aws:apigateway:Integration LambdaIntegration created
+ ├─ aws:lambda:Permission APIPermission created
+ └─ aws:apigateway:Deployment APIDeployment created

Outputs:
invocation URL: "https://<gateway-id>.execute-api.us-west-2.amazonaws.com/prod/{message}"
lambda : "arn:aws:lambda:us-west-2:ACCOUNTID:function:basicLambda-75711af"

Resources:
+ 10 created

Duration: 29s
```

5. Call our lambda function from the cli:

```bash
curl https://<gateway-id>.execute-api.us-west-2.amazonaws.com/prod/helloworld
HELLOWORLD%
```

6. From there, feel free to experiment. Simply making edits, rebuilding your handler, and running `pulumi up` will update your lambda.

7. Afterwards, destroy your stack and remove it:

```bash
pulumi destroy --yes
pulumi stack rm --yes
```
20 changes: 20 additions & 0 deletions aws-go-lambda-gateway/handler/handler.go
@@ -0,0 +1,20 @@
package main

import (
"strings"

"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)

// handler is a simple function that takes a string and does a ToUpper.
func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
return events.APIGatewayProxyResponse{
StatusCode: 200,
Body: strings.ToUpper(request.Path[1:]),
}, nil
}

func main() {
lambda.Start(handler)
}
145 changes: 145 additions & 0 deletions aws-go-lambda-gateway/main.go
@@ -0,0 +1,145 @@
package main

import (
"github.com/pulumi/pulumi-aws/sdk/go/aws/apigateway"
"github.com/pulumi/pulumi-aws/sdk/go/aws/iam"
"github.com/pulumi/pulumi-aws/sdk/go/aws/lambda"
"github.com/pulumi/pulumi/sdk/go/pulumi"
)

func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Read the configuration data from Pulumi.<stack>.yaml
gatewayRegion, _ := ctx.GetConfig("go-lambda:gateway-region")
accountID, _ := ctx.GetConfig("go-lambda:accountid")

// Create an IAM role.
role, err := iam.NewRole(ctx, "task-exec-role", &iam.RoleArgs{
AssumeRolePolicy: pulumi.String(`{
"Version": "2012-10-17",
"Statement": [{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}]
}`),
})
if err != nil {
return err
}

// Attach a policy to allow writing logs to CloudWatch
logPolicy, err := iam.NewRolePolicy(ctx, "lambda-log-policy", &iam.RolePolicyArgs{
Role: role.Name,
Policy: pulumi.String(`{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
}]
}`),
})

// Set arguments for constructing the function resource.
args := &lambda.FunctionArgs{
Handler: pulumi.String("handler"),
Role: role.Arn,
Runtime: pulumi.String("go1.x"),
Code: pulumi.NewFileArchive("./handler/handler.zip"),
}

// Create the lambda using the args.
function, err := lambda.NewFunction(
ctx,
"basicLambda",
args,
pulumi.DependsOn([]pulumi.Resource{logPolicy}),
)
if err != nil {
return err
}

// Create a new API Gateway.
gateway, err := apigateway.NewRestApi(ctx, "UpperCaseGateway", &apigateway.RestApiArgs{
Name: pulumi.String("UpperCaseGateway"),
Description: pulumi.String("An API Gateway for the UpperCase function"),
Policy: pulumi.String(`{ "Version": "2012-10-17", "Statement": [ { "Action": "sts:AssumeRole", "Principal": { "Service": "lambda.amazonaws.com" }, "Effect": "Allow", "Sid": "" },{ "Action": "execute-api:Invoke", "Resource":"execute-api:/*", "Principal": "*", "Effect": "Allow", "Sid": "" } ] }`),
})
if err != nil {
return err
}

// Add a resource to the API Gateway.
// This makes the API Gateway accept requests on "/{message}".
apiresource, err := apigateway.NewResource(ctx, "UpperAPI", &apigateway.ResourceArgs{
RestApi: gateway.ID(),
PathPart: pulumi.String("{proxy+}"),
ParentId: gateway.RootResourceId,
}, pulumi.DependsOn([]pulumi.Resource{gateway}))
if err != nil {
return err
}

// Add a method to the API Gateway.
_, err = apigateway.NewMethod(ctx, "AnyMethod", &apigateway.MethodArgs{
HttpMethod: pulumi.String("ANY"),
Authorization: pulumi.String("NONE"),
RestApi: gateway.ID(),
ResourceId: apiresource.ID(),
}, pulumi.DependsOn([]pulumi.Resource{gateway, apiresource}))
if err != nil {
return err
}

// Add an integration to the API Gateway.
// This makes communication between the API Gateway and the Lambda function work
_, err = apigateway.NewIntegration(ctx, "LambdaIntegration", &apigateway.IntegrationArgs{
HttpMethod: pulumi.String("ANY"),
IntegrationHttpMethod: pulumi.String("POST"),
ResourceId: apiresource.ID(),
RestApi: gateway.ID(),
Type: pulumi.String("AWS_PROXY"),
Uri: function.InvokeArn,
}, pulumi.DependsOn([]pulumi.Resource{gateway, apiresource, function}))
if err != nil {
return err
}

// Add a resource based policy to the Lambda function.
// This is the final step and allows AWS API Gateway to communicate with the AWS Lambda function
permission, err := lambda.NewPermission(ctx, "APIPermission", &lambda.PermissionArgs{
Action: pulumi.String("lambda:InvokeFunction"),
Function: function.Name,
Principal: pulumi.String("apigateway.amazonaws.com"),
SourceArn: pulumi.Sprintf("arn:aws:execute-api:%s:%s:%s/*/*/*", gatewayRegion, accountID, gateway.ID()),
}, pulumi.DependsOn([]pulumi.Resource{gateway, apiresource, function}))
if err != nil {
return err
}

// Create a new deployment
_, err = apigateway.NewDeployment(ctx, "APIDeployment", &apigateway.DeploymentArgs{
Description: pulumi.String("UpperCase API deployment"),
RestApi: gateway.ID(),
StageDescription: pulumi.String("Production"),
StageName: pulumi.String("prod"),
}, pulumi.DependsOn([]pulumi.Resource{gateway, apiresource, function, permission}))
if err != nil {
return err
}

// Export the lambda ARN and API Gateway URL
ctx.Export("lambda", function.Arn)
ctx.Export("invocation URL", pulumi.Sprintf("https://%s.execute-api.%s.amazonaws.com/prod/{message}", gateway.ID(), gatewayRegion))

return nil
})
}