Skip to content
This repository has been archived by the owner on Aug 1, 2024. It is now read-only.

feat: add go workshop #636

Merged
merged 13 commits into from
Aug 29, 2022
19 changes: 19 additions & 0 deletions code/go/main-workshop/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# go.sum should be committed
!go.sum

# CDK asset staging directory
.cdk.staging
cdk.out
12 changes: 12 additions & 0 deletions code/go/main-workshop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Welcome to your CDK Go project!

This is a blank project for Go development with CDK.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

## Useful commands

* `cdk deploy` deploy this stack to your default AWS account/region
* `cdk diff` compare deployed stack with current state
* `cdk synth` emits the synthesized CloudFormation template
* `go test` run unit tests
56 changes: 56 additions & 0 deletions code/go/main-workshop/cdk-workshop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package main

import (
"cdk-workshop/hitcounter"

"github.com/aws/aws-cdk-go/awscdk/v2"
"github.com/aws/aws-cdk-go/awscdk/v2/awsapigateway"
"github.com/aws/aws-cdk-go/awscdk/v2/awslambda"
"github.com/aws/constructs-go/constructs/v10"
"github.com/aws/jsii-runtime-go"
"github.com/cdklabs/cdk-dynamo-table-viewer-go/dynamotableviewer"
)

type CdkWorkshopStackProps struct {
awscdk.StackProps
}

func NewCdkWorkshopStack(scope constructs.Construct, id string, props *CdkWorkshopStackProps) awscdk.Stack {
var sprops awscdk.StackProps
if props != nil {
sprops = props.StackProps
}
stack := awscdk.NewStack(scope, &id, &sprops)

helloHandler := awslambda.NewFunction(stack, jsii.String("HelloHandler"), &awslambda.FunctionProps{
Code: awslambda.Code_FromAsset(jsii.String("lambda"), nil),
Runtime: awslambda.Runtime_NODEJS_16_X(),
Handler: jsii.String("hello.handler"),
})

hitcounter := hitcounter.NewHitCounter(stack, "HelloHitCounter", &hitcounter.HitCounterProps{
Downstream: helloHandler,
ReadCapacity: 10,
})

awsapigateway.NewLambdaRestApi(stack, jsii.String("Endpoint"), &awsapigateway.LambdaRestApiProps{
Handler: hitcounter.Handler(),
})

dynamotableviewer.NewTableViewer(stack, jsii.String("ViewHitCounter"), &dynamotableviewer.TableViewerProps{
Title: jsii.String("Hello Hits"),
Table: hitcounter.Table(),
})

return stack
}

func main() {
peterwoodworth marked this conversation as resolved.
Show resolved Hide resolved
defer jsii.Close()

app := awscdk.NewApp(nil)

NewCdkWorkshopStack(app, "CdkWorkshopStack", &CdkWorkshopStackProps{})

app.Synth(nil)
}
102 changes: 102 additions & 0 deletions code/go/main-workshop/cdk-workshop_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package main

import (
"testing"

"cdk-workshop/hitcounter"

"github.com/aws/aws-cdk-go/awscdk/v2"
"github.com/aws/aws-cdk-go/awscdk/v2/assertions"
"github.com/aws/aws-cdk-go/awscdk/v2/awslambda"
"github.com/aws/jsii-runtime-go"
"github.com/google/go-cmp/cmp"
)

func TestTableCreatedWithEncryption(t *testing.T) {
defer jsii.Close()

// GIVEN
stack := awscdk.NewStack(nil, nil, nil)

// WHEN
testFn := awslambda.NewFunction(stack, jsii.String("TestFunction"), &awslambda.FunctionProps{
Code: awslambda.Code_FromAsset(jsii.String("lambda"), nil),
Runtime: awslambda.Runtime_NODEJS_16_X(),
Handler: jsii.String("hello.handler"),
})
hitcounter.NewHitCounter(stack, "MyTestConstruct", &hitcounter.HitCounterProps{
Downstream: testFn,
ReadCapacity: 10,
})

// THEN
template := assertions.Template_FromStack(stack)
template.HasResourceProperties(jsii.String("AWS::DynamoDB::Table"), &map[string]any{
"SSESpecification": map[string]any{
"SSEEnabled": true,
},
})
}

func TestLambdaFunction(t *testing.T) {
defer jsii.Close()

// GIVEN
stack := awscdk.NewStack(nil, nil, nil)

// WHEN
testFn := awslambda.NewFunction(stack, jsii.String("TestFunction"), &awslambda.FunctionProps{
Code: awslambda.Code_FromAsset(jsii.String("lambda"), nil),
Runtime: awslambda.Runtime_NODEJS_16_X(),
Handler: jsii.String("hello.handler"),
})
hitcounter.NewHitCounter(stack, "MyTestConstruct", &hitcounter.HitCounterProps{
Downstream: testFn,
ReadCapacity: 10,
})

// THEN
template := assertions.Template_FromStack(stack)
envCapture := assertions.NewCapture(nil)
template.HasResourceProperties(jsii.String("AWS::Lambda::Function"), &map[string]any{
"Environment": envCapture,
"Handler": "hitcounter.handler",
})
expectedEnv := &map[string]any{
"Variables": map[string]any{
"DOWNSTREAM_FUNCTION_NAME": map[string]any{
"Ref": "TestFunction22AD90FC",
},
"HITS_TABLE_NAME": map[string]any{
"Ref": "MyTestConstructHits24A357F0",
},
},
}
if !cmp.Equal(envCapture.AsObject(), expectedEnv) {
t.Error(expectedEnv, envCapture.AsObject())
}
}

func TestCanPassReadCapacity(t *testing.T) {
defer jsii.Close()

defer func() {
if r := recover(); r == nil {
t.Error("Did not throw ReadCapacity error")
}
}()

// GIVEN
stack := awscdk.NewStack(nil, nil, nil)

// WHEN
testFn := awslambda.NewFunction(stack, jsii.String("TestFunction"), &awslambda.FunctionProps{
Code: awslambda.Code_FromAsset(jsii.String("lambda"), nil),
Runtime: awslambda.Runtime_NODEJS_16_X(),
Handler: jsii.String("hello.handler"),
})
hitcounter.NewHitCounter(stack, "MyTestConstruct", &hitcounter.HitCounterProps{
Downstream: testFn,
ReadCapacity: 20,
})
}
36 changes: 36 additions & 0 deletions code/go/main-workshop/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"app": "go mod download && go run cdk-workshop.go",
"watch": {
"include": [
"**"
],
"exclude": [
"README.md",
"cdk*.json",
"go.mod",
"go.sum",
"**/*test.go"
]
},
"context": {
"@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
"@aws-cdk/core:stackRelativeExports": true,
"@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
"@aws-cdk/aws-lambda:recognizeVersionProps": true,
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true,
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/core:target-partitions": [
"aws",
"aws-cn"
]
}
}
13 changes: 13 additions & 0 deletions code/go/main-workshop/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module cdk-workshop

go 1.18

require (
github.com/aws/aws-cdk-go/awscdk/v2 v2.38.1
github.com/aws/constructs-go/constructs/v10 v10.1.84
github.com/aws/jsii-runtime-go v1.65.0
github.com/cdklabs/cdk-dynamo-table-viewer-go/dynamotableviewer v0.2.248
github.com/google/go-cmp v0.5.8
)

require github.com/Masterminds/semver/v3 v3.1.1 // indirect
35 changes: 35 additions & 0 deletions code/go/main-workshop/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/aws/aws-cdk-go/awscdk/v2 v2.0.0/go.mod h1:2PrsMzp7CioaiAHHEr8zCVkbMIKp+sZs/S15kTkLUH0=
github.com/aws/aws-cdk-go/awscdk/v2 v2.38.1 h1:5Jz4KkHMa0MS4uLQO8X32cuyHxRY8YOOWyFCJRFpfWE=
github.com/aws/aws-cdk-go/awscdk/v2 v2.38.1/go.mod h1:rNrZ+WbqCuPfpUrMcDmrEOel9ZlMCy2+E0iyNCJjS+4=
github.com/aws/constructs-go/constructs/v10 v10.0.5/go.mod h1:l9g2pvi6/NDTGfjih3Zocwk3K4ASge77Pf5KZ2j2484=
github.com/aws/constructs-go/constructs/v10 v10.0.9/go.mod h1:RC6w8bOwxLmPX7Jfo9dkEZ9iVfgH4QnaVnfWvaNOHy0=
github.com/aws/constructs-go/constructs/v10 v10.1.71/go.mod h1:nJzXC/q76n0XkbL76eG0OvJQwbD/lWc7sjerJEARubA=
github.com/aws/constructs-go/constructs/v10 v10.1.84 h1:tB6ui58Bvr/mO+qGsghf4v+4NubnBBDjTUkTY/tvBOg=
github.com/aws/constructs-go/constructs/v10 v10.1.84/go.mod h1:7+5GjvX5buJY6vZZmMxd50Ke1+NXzjva8N2eL+3txQQ=
github.com/aws/jsii-runtime-go v1.28.0/go.mod h1:6tZnlstx8bAB3vnLFF9n8bbkI//LDblAek9zFyMXV3E=
github.com/aws/jsii-runtime-go v1.37.0/go.mod h1:6tZnlstx8bAB3vnLFF9n8bbkI//LDblAek9zFyMXV3E=
github.com/aws/jsii-runtime-go v1.46.0/go.mod h1:6tZnlstx8bAB3vnLFF9n8bbkI//LDblAek9zFyMXV3E=
github.com/aws/jsii-runtime-go v1.63.2/go.mod h1:Dq2QkYFSpiHGabsCBMmLnnGkyx3lnf5k6C6fq8RN/90=
github.com/aws/jsii-runtime-go v1.65.0 h1:A6o9DpZD0+IeFrXJ/qBPX7VJne5Vuk2KSfrG5Ez2dz8=
github.com/aws/jsii-runtime-go v1.65.0/go.mod h1:Dq2QkYFSpiHGabsCBMmLnnGkyx3lnf5k6C6fq8RN/90=
github.com/cdklabs/cdk-dynamo-table-viewer-go/dynamotableviewer v0.2.248 h1:WemEJJwFDVANkBm0u/wEIEgvCITfnX5L9Co0EQW763I=
github.com/cdklabs/cdk-dynamo-table-viewer-go/dynamotableviewer v0.2.248/go.mod h1:k4cRRJUP2rBTxriNTIlJqoXgsTVoYtoCZPi6OLRGKXY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
64 changes: 64 additions & 0 deletions code/go/main-workshop/hitcounter/hitcounter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package hitcounter

import (
"github.com/aws/aws-cdk-go/awscdk/v2"
"github.com/aws/aws-cdk-go/awscdk/v2/awsdynamodb"
"github.com/aws/aws-cdk-go/awscdk/v2/awslambda"
"github.com/aws/constructs-go/constructs/v10"
"github.com/aws/jsii-runtime-go"
)

type HitCounterProps struct {
Downstream awslambda.IFunction
ReadCapacity float64
}

type hitCounter struct {
constructs.Construct
handler awslambda.IFunction
table awsdynamodb.Table
}

type HitCounter interface {
constructs.Construct
Handler() awslambda.IFunction
Table() awsdynamodb.Table
}

func NewHitCounter(scope constructs.Construct, id string, props *HitCounterProps) HitCounter {
if props.ReadCapacity < 5 || props.ReadCapacity > 20 {
panic("ReadCapacity must be between 5 and 20")
}

this := constructs.NewConstruct(scope, &id)

table := awsdynamodb.NewTable(this, jsii.String("Hits"), &awsdynamodb.TableProps{
PartitionKey: &awsdynamodb.Attribute{Name: jsii.String("path"), Type: awsdynamodb.AttributeType_STRING},
RemovalPolicy: awscdk.RemovalPolicy_DESTROY,
Encryption: awsdynamodb.TableEncryption_AWS_MANAGED,
ReadCapacity: jsii.Number(props.ReadCapacity),
})

handler := awslambda.NewFunction(this, jsii.String("HitCounterHandler"), &awslambda.FunctionProps{
Runtime: awslambda.Runtime_NODEJS_16_X(),
Handler: jsii.String("hitcounter.handler"),
Code: awslambda.Code_FromAsset(jsii.String("lambda"), nil),
Environment: &map[string]*string{
"DOWNSTREAM_FUNCTION_NAME": (*props).Downstream.FunctionName(),
"HITS_TABLE_NAME": table.TableName(),
},
})

table.GrantReadWriteData(handler)
props.Downstream.GrantInvoke(handler)

return &hitCounter{this, handler, table}
}

func (h *hitCounter) Handler() awslambda.IFunction {
return h.handler
}

func (h *hitCounter) Table() awsdynamodb.Table {
return h.table
}
8 changes: 8 additions & 0 deletions code/go/main-workshop/lambda/hello.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
exports.handler = async function(event) {
console.log("request:", JSON.stringify(event, undefined, 2));
return {
statusCode: 200,
headers: { "Content-Type": "text/plain" },
body: `Hello, CDK! You've hit ${event.path}\n`
};
};
28 changes: 28 additions & 0 deletions code/go/main-workshop/lambda/hitcounter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { DynamoDB, Lambda } = require('aws-sdk');

exports.handler = async function(event) {
console.log("request:", JSON.stringify(event, undefined, 2));

// create AWS SDK clients
const dynamo = new DynamoDB();
const lambda = new Lambda();

// update dynamo entry for "path" with hits++
await dynamo.updateItem({
TableName: process.env.HITS_TABLE_NAME,
Key: { path: { S: event.path } },
UpdateExpression: 'ADD hits :incr',
ExpressionAttributeValues: { ':incr': { N: '1' } }
}).promise();

// call downstream function and capture response
const resp = await lambda.invoke({
FunctionName: process.env.DOWNSTREAM_FUNCTION_NAME,
Payload: JSON.stringify(event)
}).promise();

console.log('downstream response:', JSON.stringify(resp, undefined, 2));

// return response back to upstream caller
return JSON.parse(resp.Payload);
};
Loading