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

Add a Lambda+Codepipeline Typescript example #269

Merged
merged 4 commits into from
May 8, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,6 @@ dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
Expand Down
2 changes: 1 addition & 1 deletion scripts/build-npm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ verify_star_dependencies() {
}

# Find and build all NPM projects
for pkgJson in $(find typescript -name package.json | grep -v node_modules); do
for pkgJson in $(find typescript -name cdk.json | grep -v node_modules); do
Copy link
Contributor Author

@gustakasn0v gustakasn0v Apr 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've run the following commands to compare the directories that are evaluated:

find typescript -name package.json  | grep -v node_modules | xargs -n 1 dirname | sort
find typescript -name cdj.json  | grep -v node_modules | xargs -n 1 dirname | sort

Here is the diff. Directories removed are shown in grey on the left-hand side:
Screen Shot 2020-04-18 at 5 50 34 pm
Link
These directories are now excluded as they're Lambda NPM packages, not CDK packages:

typescript/lambda-api-ci/cdk.out/asset.5d8ebe4e1b7da78e3a974de62e8d9aac8a42747f5c303e249b6d1e2855c6e92f
typescript/lambda-api-ci/src

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the catch! I had not noticed this. The fix is logical and LGTM.

😸

(
echo "=============================="
echo "building project: $(dirname $pkgJson)"
Expand Down
9 changes: 9 additions & 0 deletions typescript/lambda-api-ci/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
*.js
!jest.config.js
*.d.ts
node_modules

# CDK asset staging directory
.cdk.staging
cdk.out
*.iml
6 changes: 6 additions & 0 deletions typescript/lambda-api-ci/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.ts
!*.d.ts

# CDK asset staging directory
.cdk.staging
cdk.out
5 changes: 5 additions & 0 deletions typescript/lambda-api-ci/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"printWidth": 100,
"semi": false,
"tabWidth": 4
}
15 changes: 15 additions & 0 deletions typescript/lambda-api-ci/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# What's this?
This is code sample that uses CDK to:
* Create a Lambda function that can be invoked using API Gateway
* Create a CI using CodeSuite that deploys the Lambda+ApiGateway resources using `cdk deploy`

# How do I start using it?
* Ensure you've followed the [guide to Getting Started to AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html), and you have CDK installed, and the AWS SDK installed and credentials configured.
* [Bootstrap your AWS environment](https://docs.aws.amazon.com/cdk/latest/guide/serverless_example.html#serverless_example_deploy_and_test)
* Create a CodeCommit repository. See [this documentation](https://docs.aws.amazon.com/codecommit/latest/userguide/how-to-create-repository.html) for help.
* Place the contents of this folder inside it
* Set the repository name in the `repositoryName` prop in `bin/ci.ts`.
* Build the stack with `npm run build`
* Deploy the CI stack with `cdk deploy`
* `Todo` summarize permissions
* If you'd like to deploy just the Lambda+ApiGateway stack, you can do so with `cdk deploy -a "npx ts-node bin/lambda.ts"`
9 changes: 9 additions & 0 deletions typescript/lambda-api-ci/bin/ci.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env node
import "source-map-support/register"
import cdk = require("@aws-cdk/core")
import { CIStack } from "../lib/ci-stack"

const app = new cdk.App()
new CIStack(app, "CDKExampleLambdaApiCIStack", {
repositoryName: "lambda-api-ci",
})
12 changes: 12 additions & 0 deletions typescript/lambda-api-ci/bin/lambda.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env node
import "source-map-support/register"
import cdk = require("@aws-cdk/core")
import { CDKExampleLambdaApiStack } from "../lib/lambda-api-stack"

export const lambdaApiStackName = "CDKExampleLambdaApiStack"
export const lambdaFunctionName = "CDKExampleWidgetStoreFunction"

const app = new cdk.App()
new CDKExampleLambdaApiStack(app, lambdaApiStackName, {
functionName: lambdaFunctionName,
})
17 changes: 17 additions & 0 deletions typescript/lambda-api-ci/buildspec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

version: 0.2

phases:
install:
runtime-versions:
nodejs: 12
commands:
- npm install

build:
commands:
- npm run build
- npm run -- cdk deploy --ci --require-approval never -a "npx ts-node bin/lambda.ts"
artifacts:
files:
- "cdk.out/**/*"
3 changes: 3 additions & 0 deletions typescript/lambda-api-ci/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "npx ts-node bin/ci.ts"
}
9 changes: 9 additions & 0 deletions typescript/lambda-api-ci/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
"roots": [
"<rootDir>/test"
],
testMatch: [ '**/*.test.ts'],
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
}
118 changes: 118 additions & 0 deletions typescript/lambda-api-ci/lib/ci-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { CodeCommitSourceAction, CodeBuildAction } from "@aws-cdk/aws-codepipeline-actions"
import { PolicyStatement } from "@aws-cdk/aws-iam"
import { Construct, Stack, StackProps } from "@aws-cdk/core"
import { PipelineProject, LinuxBuildImage } from "@aws-cdk/aws-codebuild"
import { Artifact, Pipeline } from "@aws-cdk/aws-codepipeline"
import { Repository } from "@aws-cdk/aws-codecommit"
import { lambdaApiStackName, lambdaFunctionName } from "../bin/lambda"

interface CIStackProps extends StackProps {
repositoryName: string
}

export class CIStack extends Stack {
constructor(scope: Construct, name: string, props: CIStackProps) {
super(scope, name, props)

const pipeline = new Pipeline(this, "Pipeline", {})

const repo = Repository.fromRepositoryName(
this,
"WidgetsServiceRepository",
props.repositoryName
)
const sourceOutput = new Artifact("SourceOutput")
const sourceAction = new CodeCommitSourceAction({
actionName: "CodeCommit",
repository: repo,
output: sourceOutput,
})
pipeline.addStage({
stageName: "Source",
actions: [sourceAction],
})

this.createBuildStage(pipeline, sourceOutput)
}

private createBuildStage(pipeline: Pipeline, sourceOutput: Artifact) {
const project = new PipelineProject(this, `BuildProject`, {
environment: {
buildImage: LinuxBuildImage.STANDARD_3_0,
},
})

const cdkDeployPolicy = new PolicyStatement()
cdkDeployPolicy.addActions(
"cloudformation:GetTemplate",
"cloudformation:CreateChangeSet",
"cloudformation:DescribeChangeSet",
"cloudformation:ExecuteChangeSet",
"cloudformation:DescribeStackEvents",
"cloudformation:DeleteChangeSet",
"cloudformation:DescribeStacks",
"s3:*Object",
"s3:ListBucket",
"s3:getBucketLocation",
"lambda:UpdateFunctionCode",
"lambda:GetFunction",
"lambda:CreateFunction",
"lambda:DeleteFunction",
"lambda:GetFunctionConfiguration",
"lambda:AddPermission",
"lambda:RemovePermission"
)
cdkDeployPolicy.addResources(
this.formatArn({
service: "cloudformation",
resource: "stack",
resourceName: "CDKToolkit/*",
}),
this.formatArn({
service: "cloudformation",
resource: "stack",
resourceName: `${lambdaApiStackName}/*`,
}),
this.formatArn({
service: "lambda",
resource: "function",
sep: ":",
resourceName: lambdaFunctionName,
}),
"arn:aws:s3:::cdktoolkit-stagingbucket-*"
)
const editOrCreateLambdaDependencies = new PolicyStatement()
editOrCreateLambdaDependencies.addActions(
"iam:GetRole",
"iam:PassRole",
"iam:CreateRole",
"iam:AttachRolePolicy",
"iam:PutRolePolicy",
"apigateway:GET",
"apigateway:DELETE",
"apigateway:PUT",
"apigateway:POST",
"apigateway:PATCH",
"s3:CreateBucket",
"s3:PutBucketTagging"
)
editOrCreateLambdaDependencies.addResources("*")
project.addToRolePolicy(cdkDeployPolicy)
project.addToRolePolicy(editOrCreateLambdaDependencies)

const buildOutput = new Artifact(`BuildOutput`)
const buildAction = new CodeBuildAction({
actionName: `Build`,
project,
input: sourceOutput,
outputs: [buildOutput],
})

pipeline.addStage({
stageName: "build",
actions: [buildAction],
})

return buildOutput
}
}
49 changes: 49 additions & 0 deletions typescript/lambda-api-ci/lib/lambda-api-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { LambdaIntegration, MethodLoggingLevel, RestApi } from "@aws-cdk/aws-apigateway"
import { PolicyStatement } from "@aws-cdk/aws-iam"
import { Function, Runtime, AssetCode, Code } from "@aws-cdk/aws-lambda"
import { Construct, Duration, Stack, StackProps } from "@aws-cdk/core"
import s3 = require("@aws-cdk/aws-s3")

interface LambdaApiStackProps extends StackProps {
functionName: string
}

export class CDKExampleLambdaApiStack extends Stack {
private restApi: RestApi
private lambdaFunction: Function
private bucket: s3.Bucket

constructor(scope: Construct, id: string, props: LambdaApiStackProps) {
super(scope, id, props)

this.bucket = new s3.Bucket(this, "WidgetStore")

this.restApi = new RestApi(this, this.stackName + "RestApi", {
deployOptions: {
stageName: "beta",
metricsEnabled: true,
loggingLevel: MethodLoggingLevel.INFO,
dataTraceEnabled: true,
},
})

const lambdaPolicy = new PolicyStatement()
lambdaPolicy.addActions("s3:ListBucket")
lambdaPolicy.addResources(this.bucket.bucketArn)

this.lambdaFunction = new Function(this, props.functionName, {
functionName: props.functionName,
handler: "handler.handler",
runtime: Runtime.NODEJS_10_X,
code: new AssetCode(`./src`),
memorySize: 512,
timeout: Duration.seconds(10),
environment: {
BUCKET: this.bucket.bucketName,
},
initialPolicy: [lambdaPolicy],
})

this.restApi.root.addMethod("GET", new LambdaIntegration(this.lambdaFunction, {}))
}
}
46 changes: 46 additions & 0 deletions typescript/lambda-api-ci/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "lambda-api-ci",
"version": "0.1.0",
"bin": {
"lambda-api-ci": "bin/lambda-api-ci.js"
},
"scripts": {
"build": "npm run prettier && tsc && npm run build-lambda",
"build-lambda": "cd src && npm run build",
"watch": "tsc -w",
"test": "jest",
"cdk": "cdk",
"prettier": "prettier --write '**/{bin,lib,src,tst}/*.ts'"
},
"devDependencies": {
"aws-cdk": "*",
"@aws-cdk/core": "*",
"@aws-cdk/assert": "*",
"@aws-cdk/aws-apigateway": "*",
"@aws-cdk/aws-codebuild": "*",
"@aws-cdk/aws-codecommit": "*",
"@aws-cdk/aws-codepipeline": "*",
"@aws-cdk/aws-codepipeline-actions": "*",
"@aws-cdk/aws-cloudformation": "*",
"@types/jest": "^24.0.18",
"@types/node": "^13.7.0",
"jest": "^24.9.0",
"ts-jest": "^24.0.2",
"ts-node": "^8.1.0",
"typescript": "^3.8.3",
"prettier": "^2.0.4"
},
"dependencies": {
"aws-sdk": "^2.617.0",
"source-map-support": "^0.5.9"
},
"description": "* `npm run build` compile typescript to js * `npm run watch` watch for changes and compile * `npm run test` perform the jest unit tests * `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",
"main": "jest.config.js",
"directories": {
"lib": "lib",
"test": "test"
},
"keywords": [],
"author": "",
"license": "ISC"
}
44 changes: 44 additions & 0 deletions typescript/lambda-api-ci/src/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { S3 } from "aws-sdk"

const bucketName = process.env.BUCKET!

// From https://docs.aws.amazon.com/cdk/latest/guide/serverless_example.html
const handler = async function (event: any, context: any) {
const S3Client = new S3()

try {
var method = event.httpMethod

if (method === "GET") {
if (event.path === "/") {
const data = await S3Client.listObjectsV2({ Bucket: bucketName }).promise()
var body = {
widgets: data.Contents!.map(function (e) {
return e.Key
}),
}
return {
statusCode: 200,
headers: {},
body: JSON.stringify(body),
}
}
}

// We only accept GET for now
return {
statusCode: 400,
headers: {},
body: "We only accept GET /",
}
} catch (error) {
const body = error.stack || JSON.stringify(error, null, 2)
return {
statusCode: 400,
headers: {},
body: JSON.stringify(body),
}
}
}

export { handler }
Loading