Walkthrough - Part 1 - AWS Solutions Constructs

Walkthrough - Part 1

Note

AWS Solutions Constructs is supported on AWS CDK versions ≥ 1.46.0.

This tutorial walks you through how to create and deploy a simple "Hello Constructs" AWS CDK app that uses a pattern from AWS Solutions Constructs, from initializing the project to deploying the resulting AWS CloudFormation template. The Hello Constructs app will create the following simple solution:


      Architecture Diagram

Hello Constructs

Let’s get started building our first AWS CDK App using pattern based development.

Note

This is a sample modification of Hello CDK! from the CDK Workshop. If this is your first time using the AWS CDK, we recommend starting with this workshop for a hands-on walkthrough and how to leverage the CDK in building a real-world project.

Creating the App Directory and Initializing the AWS CDK

Create a directory for your CDK app, and then create a AWS CDK app in that directory.

TypeScript
mkdir hello-constructs cd hello-constructs cdk init --language typescript
Python
mkdir hello-constructs cd hello-constructs cdk init --language python
Tip

Now's a good time to open the project in your favorite IDE and explore. To learn more about the project structure, select the appropriate link:

Update project base dependencies

Warning

To ensure proper functionality, AWS Solutions Constructs and AWS CDK packages must use the same version number within your project. For example, if you are using AWS Solutions Constructs v.1.52.0, you must also use AWS CDK v.1.52.0.

Tip

Take note of the most recent version of AWS Solutions Constructs, and apply that version number to the VERSION_NUMBER placeholders in the steps below (for both AWS Solutions Constructs and AWS CDK packages). To check all the public releases of the Constructs library, click here .

TypeScript

Edit the package.json file with the following information:

"devDependencies": { "@aws-cdk/assert": "VERSION_NUMBER", "@types/jest": "^24.0.22", "@types/node": "10.17.5", "jest": "^24.9.0", "ts-jest": "^24.1.0", "aws-cdk": "VERSION_NUMBER", "ts-node": "^8.1.0", "typescript": "~3.7.2" }, "dependencies": { "@aws-cdk/core": "VERSION_NUMBER", "source-map-support": "^0.5.16" }
Python

Edit the setup.py file with the following information:

install_requires=[ "aws-cdk.core==VERSION_NUMBER", ],

Install the projects base dependencies.

TypeScript
npm install
Python
source .env/bin/activate pip install -r requirements.txt

Build and run the app and confirm that it creates an empty stack.

TypeScript
npm run build cdk synth
Python
cdk synth

You should see a stack like the following, where CDK-VERSION is the version of the CDK. (Your output may differ slightly from what’s shown here.)

TypeScript
Resources: CDKMetadata: Type: AWS::CDK::Metadata Properties: Modules: aws-cdk=CDK-VERSION,@aws-cdk/core=VERSION_NUMBER,@aws-cdk/cx-api=VERSION_NUMBER,jsii-runtime=node.js/10.17.0
Python
Resources: CDKMetadata: Type: AWS::CDK::Metadata Properties: Modules: aws-cdk=CDK-VERSION,@aws-cdk/core=VERSION_NUMBER,@aws-cdk/cx-api=VERSION_NUMBER,jsii-runtime=Python/3.7.7

Lambda handler code

We’ll start with the AWS Lambda handler code.

Create a directory lambda in the root of your project tree.

TypeScript

Add a file called lambda/hello.js with the following contents:

exports.handler = async function(event) { console.log("request:", JSON.stringify(event, undefined, 2)); return { statusCode: 200, headers: { "Content-Type": "text/plain" }, body: `Hello, AWS Solutions Constructs! You've hit ${event.path}\n` }; };
Python

Add a file called lambda/hello.py with the following contents:

import json def handler(event, context): print('request: {}'.format(json.dumps(event))) return { 'statusCode': 200, 'headers': { 'Content-Type': 'text/plain' }, 'body': 'Hello, CDK! You have hit {}\n'.format(event['path']) }

This is a simple Lambda function which returns the text "Hello, Constructs! You’ve hit [url path]". The function’s output also includes the HTTP status code and HTTP headers. These are used by API Gateway to formulate the HTTP response to the user.

This Lambda is provided in JavaScript. For more information on writing Lambda functions in your language of choice, refer to the AWS Lambda documentation.

Install the AWS CDK and AWS Solutions Constructs dependencies

The AWS Solutions Constructs is shipped with an extensive library of constructs. The library is divided into modules, one for each well-architected pattern. For example, if you want to define an Amazon API Gateway Rest API to an AWS Lambda function, we will need to use the aws-apigateway-lambda pattern library.

We also need to add the AWS Lambda and Amazon API Gateway construct library from the AWS CDK.

Install the AWS Lambda module and all its dependencies into our project:

Note

Remember to substitute the correct, matching version to be used for both AWS Solutions Constructs and the AWS CDK into the VERSION_NUMBER placeholder fields for each command. Mismatching versions between packages may cause errors.

TypeScript
npm install -s @aws-cdk/aws-lambda@VERSION_NUMBER
Python
pip install aws_cdk.aws_lambda==VERSION_NUMBER

Next, install the Amazon API Gateway module and all its dependencies into our project:

TypeScript
npm install -s @aws-cdk/aws-apigateway@VERSION_NUMBER
Python
pip install aws_cdk.aws_apigateway==VERSION_NUMBER

Finally, install the AWS Solutions Constructs aws-apigateway-lambda module and all its dependencies into our project:

TypeScript
npm install -s @aws-solutions-constructs/aws-apigateway-lambda@VERSION_NUMBER
Python
pip install aws_solutions_constructs.aws_apigateway_lambda==VERSION_NUMBER

Add an Amazon API Gateway/AWS Lambda pattern to your stack

Now, let’s define the AWS Solutions Constructs pattern for implementing an Amazon API Gateway with an AWS Lambda proxy.

TypeScript

Edit the file lib/hello-constructs.ts with the following:

import * as cdk from '@aws-cdk/core'; import * as lambda from '@aws-cdk/aws-lambda'; import * as api from '@aws-cdk/aws-apigateway'; import { ApiGatewayToLambda, ApiGatewayToLambdaProps } from '@aws-solutions-constructs/aws-apigateway-lambda'; export class HelloConstructsStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // The code that defines your stack goes here const api_lambda_props: ApiGatewayToLambdaProps = { lambdaFunctionProps: { code: lambda.Code.fromAsset('lambda'), runtime: lambda.Runtime.NODEJS_12_X, handler: 'hello.handler' }, apiGatewayProps: { defaultMethodOptions: { authorizationType: api.AuthorizationType.NONE } } }; new ApiGatewayToLambda(this, 'ApiGatewayToLambda', api_lambda_props); } }
Python

Edit the file hello_constructs/hello_constructs_stack.py with the following:

from aws_cdk import ( aws_lambda as _lambda, aws_apigateway as apigw, core, ) from aws_solutions_constructs import ( aws_apigateway_lambda as apigw_lambda ) class HelloConstructsStack(core.Stack): def __init__(self, scope: core.Construct, id: str, **kwargs) -> None: super().__init__(scope, id, **kwargs) # The code that defines your stack goes here apigw_lambda.ApiGatewayToLambda( self, 'ApiGatewayToLambda', deploy_lambda=True, lambda_function_props=_lambda.FunctionProps( runtime=_lambda.Runtime.PYTHON_3_7, code=_lambda.Code.asset('lambda'), handler='hello.handler', ), api_gateway_props=apigw.RestApiProps( default_method_options=apigw.MethodOptions( authorization_type=apigw.AuthorizationType.NONE ) ) )

That’s it. This is all you need to do in order to define an API Gateway which proxies all requests to an AWS Lambda function.

Let’s see what’s going to happen when we deploy this:

cdk diff

Output should look like this:

Stack HelloConstructsStack
IAM Statement Changes
┌───┬─────────────────────────────┬────────┬─────────────────────────────┬─────────────────────────────┬──────────────────────────────┐
│   │ Resource                    │ Effect │ Action                      │ Principal                   │ Condition                    │
├───┼─────────────────────────────┼────────┼─────────────────────────────┼─────────────────────────────┼──────────────────────────────┤
│ + │ ${LambdaFunction.Arn}       │ Allow  │ lambda:InvokeFunction       │ Service:apigateway.amazonaw │ "ArnLike": {                 │
│   │                             │        │                             │ s.com                       │   "AWS:SourceArn": "arn:${AW │
│   │                             │        │                             │                             │ S::Partition}:execute-api:${ │
│   │                             │        │                             │                             │ AWS::Region}:${AWS::AccountI │
│   │                             │        │                             │                             │ d}:${RestApi0C43BF4B}/${Rest │
│   │                             │        │                             │                             │ Api/DeploymentStage.prod}/*/ │
│   │                             │        │                             │                             │ {proxy+}"                    │
│   │                             │        │                             │                             │ }                            │
│ + │ ${LambdaFunction.Arn}       │ Allow  │ lambda:InvokeFunction       │ Service:apigateway.amazonaw │ "ArnLike": {                 │
│   │                             │        │                             │ s.com                       │   "AWS:SourceArn": "arn:${AW │
│   │                             │        │                             │                             │ S::Partition}:execute-api:${ │
│   │                             │        │                             │                             │ AWS::Region}:${AWS::AccountI │
│   │                             │        │                             │                             │ d}:${RestApi0C43BF4B}/test-i │
│   │                             │        │                             │                             │ nvoke-stage/*/{proxy+}"      │
│   │                             │        │                             │                             │ }                            │
│ + │ ${LambdaFunction.Arn}       │ Allow  │ lambda:InvokeFunction       │ Service:apigateway.amazonaw │ "ArnLike": {                 │
│   │                             │        │                             │ s.com                       │   "AWS:SourceArn": "arn:${AW │
│   │                             │        │                             │                             │ S::Partition}:execute-api:${ │
│   │                             │        │                             │                             │ AWS::Region}:${AWS::AccountI │
│   │                             │        │                             │                             │ d}:${RestApi0C43BF4B}/${Rest │
│   │                             │        │                             │                             │ Api/DeploymentStage.prod}/*/ │
│   │                             │        │                             │                             │ "                            │
│   │                             │        │                             │                             │ }                            │
│ + │ ${LambdaFunction.Arn}       │ Allow  │ lambda:InvokeFunction       │ Service:apigateway.amazonaw │ "ArnLike": {                 │
│   │                             │        │                             │ s.com                       │   "AWS:SourceArn": "arn:${AW │
│   │                             │        │                             │                             │ S::Partition}:execute-api:${ │
│   │                             │        │                             │                             │ AWS::Region}:${AWS::AccountI │
│   │                             │        │                             │                             │ d}:${RestApi0C43BF4B}/test-i │
│   │                             │        │                             │                             │ nvoke-stage/*/"              │
│   │                             │        │                             │                             │ }                            │
├───┼─────────────────────────────┼────────┼─────────────────────────────┼─────────────────────────────┼──────────────────────────────┤
│ + │ ${LambdaFunctionServiceRole │ Allow  │ sts:AssumeRole              │ Service:lambda.amazonaws.co │                              │
│   │ .Arn}                       │        │                             │ m                           │                              │
├───┼─────────────────────────────┼────────┼─────────────────────────────┼─────────────────────────────┼──────────────────────────────┤
│ + │ ${LambdaRestApiCloudWatchRo │ Allow  │ sts:AssumeRole              │ Service:apigateway.amazonaw │                              │
│   │ le.Arn}                     │        │                             │ s.com                       │                              │
├───┼─────────────────────────────┼────────┼─────────────────────────────┼─────────────────────────────┼──────────────────────────────┤
│ + │ arn:aws:logs:${AWS::Region} │ Allow  │ logs:CreateLogGroup         │ AWS:${LambdaRestApiCloudWat │                              │
│   │ :${AWS::AccountId}:*        │        │ logs:CreateLogStream        │ chRole}                     │                              │
│   │                             │        │ logs:DescribeLogGroups      │                             │                              │
│   │                             │        │ logs:DescribeLogStreams     │                             │                              │
│   │                             │        │ logs:FilterLogEvents        │                             │                              │
│   │                             │        │ logs:GetLogEvents           │                             │                              │
│   │                             │        │ logs:PutLogEvents           │                             │                              │
├───┼─────────────────────────────┼────────┼─────────────────────────────┼─────────────────────────────┼──────────────────────────────┤
│ + │ arn:aws:logs:${AWS::Region} │ Allow  │ logs:CreateLogGroup         │ AWS:${LambdaFunctionService │                              │
│   │ :${AWS::AccountId}:log-grou │        │ logs:CreateLogStream        │ Role}                       │                              │
│   │ p:/aws/lambda/*             │        │ logs:PutLogEvents           │                             │                              │
└───┴─────────────────────────────┴────────┴─────────────────────────────┴─────────────────────────────┴──────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Parameters
[+] Parameter AssetParameters/ba91444ebd644d9419e8cfee417f3aaa728507dd428788a2fc40574646c4340a/S3Bucket AssetParametersba91444ebd644d9419e8cfee417f3aaa728507dd428788a2fc40574646c4340aS3Bucket9780A3BC: {"Type":"String","Description":"S3 bucket for asset \"ba91444ebd644d9419e8cfee417f3aaa728507dd428788a2fc40574646c4340a\""}
[+] Parameter AssetParameters/ba91444ebd644d9419e8cfee417f3aaa728507dd428788a2fc40574646c4340a/S3VersionKey AssetParametersba91444ebd644d9419e8cfee417f3aaa728507dd428788a2fc40574646c4340aS3VersionKey37F36FFB: {"Type":"String","Description":"S3 key for asset version \"ba91444ebd644d9419e8cfee417f3aaa728507dd428788a2fc40574646c4340a\""}
[+] Parameter AssetParameters/ba91444ebd644d9419e8cfee417f3aaa728507dd428788a2fc40574646c4340a/ArtifactHash AssetParametersba91444ebd644d9419e8cfee417f3aaa728507dd428788a2fc40574646c4340aArtifactHash80199FBC: {"Type":"String","Description":"Artifact hash for asset \"ba91444ebd644d9419e8cfee417f3aaa728507dd428788a2fc40574646c4340a\""}

Conditions
[+] Condition CDKMetadataAvailable: {"Fn::Or":[{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-northeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ap-southeast-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"ca-central-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"cn-northwest-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-central-1"]}]},{"Fn::Or":[{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-north-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"eu-west-3"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"me-south-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"sa-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-east-2"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-1"]},{"Fn::Equals":[{"Ref":"AWS::Region"},"us-west-2"]}]}]}

Resources
[+] AWS::Logs::LogGroup ApiGatewayToLambda/ApiAccessLogGroup ApiGatewayToLambdaApiAccessLogGroupE2B41502 
[+] AWS::IAM::Role LambdaFunctionServiceRole LambdaFunctionServiceRole0C4CDE0B 
[+] AWS::Lambda::Function LambdaFunction LambdaFunctionBF21E41F 
[+] AWS::ApiGateway::RestApi RestApi RestApi0C43BF4B 
[+] AWS::ApiGateway::Deployment RestApi/Deployment RestApiDeployment180EC503d2c6df3c8dc8b7193b98c1a0bff4e677 
[+] AWS::ApiGateway::Stage RestApi/DeploymentStage.prod RestApiDeploymentStageprod3855DE66 
[+] AWS::ApiGateway::Resource RestApi/Default/{proxy+} RestApiproxyC95856DD 
[+] AWS::Lambda::Permission RestApi/Default/{proxy+}/ANY/ApiPermission.HelloConstructsStackRestApiFDB18C2E.ANY..{proxy+} RestApiproxyANYApiPermissionHelloConstructsStackRestApiFDB18C2EANYproxyE43D39B3 
[+] AWS::Lambda::Permission RestApi/Default/{proxy+}/ANY/ApiPermission.Test.HelloConstructsStackRestApiFDB18C2E.ANY..{proxy+} RestApiproxyANYApiPermissionTestHelloConstructsStackRestApiFDB18C2EANYproxy0B23CDC7 
[+] AWS::ApiGateway::Method RestApi/Default/{proxy+}/ANY RestApiproxyANY1786B242 
[+] AWS::Lambda::Permission RestApi/Default/ANY/ApiPermission.HelloConstructsStackRestApiFDB18C2E.ANY.. RestApiANYApiPermissionHelloConstructsStackRestApiFDB18C2EANY5684C1E6 
[+] AWS::Lambda::Permission RestApi/Default/ANY/ApiPermission.Test.HelloConstructsStackRestApiFDB18C2E.ANY.. RestApiANYApiPermissionTestHelloConstructsStackRestApiFDB18C2EANY81DBDF56 
[+] AWS::ApiGateway::Method RestApi/Default/ANY RestApiANYA7C1DC94 
[+] AWS::ApiGateway::UsagePlan RestApi/UsagePlan RestApiUsagePlan6E1C537A 
[+] AWS::Logs::LogGroup ApiAccessLogGroup ApiAccessLogGroupCEA70788 
[+] AWS::IAM::Role LambdaRestApiCloudWatchRole LambdaRestApiCloudWatchRoleF339D4E6 
[+] AWS::ApiGateway::Account LambdaRestApiAccount LambdaRestApiAccount 

Outputs
[+] Output RestApi/Endpoint RestApiEndpoint0551178A: {"Value":{"Fn::Join":["",["https://",{"Ref":"RestApi0C43BF4B"},".execute-api.",{"Ref":"AWS::Region"},".",{"Ref":"AWS::URLSuffix"},"/",{"Ref":"RestApiDeploymentStageprod3855DE66"},"/"]]}}

That’s nice. This simple example with one well-architected pattern from the AWS Solutions Constructs added 21 new resources to your stack.

cdk deploy

Tip

Before you can deploy your first AWS CDK app containing a Lambda function, you must bootstrap your AWS environment. This creates a staging bucket that the AWS CDK uses to deploy stacks containing assets. If this is the first time you are using the AWS CDK to deploy assets, you will need to run the cdk bootstrap to deploy the CDK toolkit stack into your AWS environment.

Okay, ready to deploy?

cdk deploy

Stack outputs

When deployment is complete, you’ll notice this line:

Outputs:
HelloConstructsStack.RestApiEndpoint0551178A = https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod/

This is a stack output that’s automatically added by the AWS Solutions Constructs pattern and includes the URL of the API Gateway endpoint.

Testing your app

Let’s try to hit this endpoint with curl. Copy the URL and execute (your prefix and Region will likely be different).

curl https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com/prod/

Output should look like this:

Hello, AWS Solutions Constructs! You've hit /

If this is the output you received, your app works!