Useful Links
CDK Workshop - The workshop used for these notes
AWS CDK Construct Library - This lists the constructs used in creating your resources
AWS CDK Documentation - AWS CDK Documentation
These notes are taken from the above link which i have worked through twice now. Once from the AWS CDK documentation and the second time using the link above. I am hoping that the link above will give me a better understanding of CDK and TS.
Prerequisites
export AWS_PROFILE={profile_name}
for the purposes of this i created a new IAM user and new profile
brew install node
install node for a mac
node --version
should be higher than v10.x
npm install -g aws-cdk
installs the AWS CDK
Commands
cdk init sample-app --language typescript
This installs the “sample-app” and chooses typescript as the language
cdk synth
- The CDK CLI requires you to be in the same directory as your cdk.json file.
- This command creates the CF template and stores it in cdk.out/{app-name}.template.json
- This location is defined in the node_modules/aws-cdk/lib/settings.js
cdk bootstrap
- This command is required the first time you run the cdk commands and sets up the environment.
- It deploys a new stack and builds the following resources
- StagingBucket
- StagingBucketPolicy
cdk diff
- Once changes are made to the lib/*.ts files, you can run a cdk diff and it will show you what needs to be updated
cdk deploy
- This command creates the CF stack and deploys the template created from the “synth” command
cdk destroy
- This deletes the cloudformation stack and all dependencies
- although it didn’t delete the DynamoDB table as it contained data
- something to be aware of for costs / tidying up
File Structure
-
lib/cdk-workshop-stack.ts
is where your CDK application’s main stack is defined. This is the file we’ll be spending most of our time in. -
bin/cdk-workshop.ts
is the entrypoint of the CDK application. It will load the stack defined in lib/cdk-workshop-stack.ts. -
package.json
is your npm module manifest. It includes information like the name of your app, version, dependencies and build scripts like “watch” and “build” (package-lock.json is maintained by npm) -
cdk.json
tells the toolkit how to run your app. In our case it will be “npx ts-node bin/cdk-workshop.ts” -
tsconfig.json
your project’s typescript configuration -
.gitignore
and.npmignore
tell git and npm which files to include/exclude from source control and when publishing this module to the package manager. -
node_modules
is maintained by npm and includes all your project’s dependencies.
CDK App
The CDK app is defined in the lib/ directory while the app is built from the bin/ directory. The library contains the *.ts files that are used in the creation of the resources. CDK relies on CloudFormation and the CF template is created based on the resources defined in the lib/ TS files.
The lib/cdk-workshop-stack.ts is the file that defines the resources. The constructs are first imported and can then be used in the code to create the resource.
When you deploy the stack, you are calling bin/cdk-workshop.ts which imports the stack from the lib/ directory. A new “app” definition is created and then deployed.
To update the cloudformation stacks, you can edit the lib/*.ts files and remove resources from them. Once you run a cdk diff it will show the changes needed to update the infrastructure.
Once you are happy with the changes run a cdk deploy and any resources will be removed.
Adding to the Infrastructure
I am adding a Lambda function to the infrastructure which requires the node module to be installed
npm install @aws-cdk/aws-lambda
npm install @aws-cdk/aws-apigateway
This needs to be done each time you use a new construct.
Outputs
To declare an output, create a new cdk.CfnOutput function block. Define the name of the output function which contains the following properties:
-
value
- value of the output -
description
- a short description of the output -
exportName
- the Name of the output -
condition
- this could be set to false so the output is not set for the stack
export class CdkWorkshopStack extends cdk.Stack {
constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// defines an AWS Lambda resource
const hello = new lambda.Function(this, 'HelloHandler', {
runtime: lambda.Runtime.NODEJS_14_X, // execution environment
code: lambda.Code.fromAsset('lambda'), // code loaded from "lambda" directory
handler: 'hello.handler' // file is "hello", function is "handler"
});
// 👇 create an Output
new cdk.CfnOutput(this, 'lambdaArn', {
value: hello.functionArn,
description: 'The ARN of the Lambda Function',
exportName: 'helloLambdaArn',
});
Tests
I was running some tests on my code after destroying everything. This still works as it compares the code to be deployed and tests it before you cdk deploy. After making a change to a test, it failed. I was attempting to add a test for DynamoDB encryption and updated the .ts file. This was correct, but the .js file hadn’t been updated.
This was the test i was running:
test('DynamoDB Table Created With Encryption', () => {
const stack = new cdk.Stack();
// WHEN
new HitCounter(stack, 'MyTestConstruct', {
downstream: new lambda.Function(stack, 'TestFunction', {
runtime: lambda.Runtime.NODEJS_14_X,
handler: 'hello.handler',
code: lambda.Code.fromAsset('lambda')
})
});
// THEN
const template = Template.fromStack(stack);
//
// check the template has SSEEnabled = true
//
template.hasResourceProperties('AWS::DynamoDB::Table', {
SSESpecification: {
SSEEnabled: true
}
});
});
And this was the .ts file that configmed i had enabled the encryption.
const table = new dynamodb.Table(this, 'Hits', {
partitionKey: { name: 'path', type: dynamodb.AttributeType.STRING },
//
// i was trying a few variations on encryption
//
// serverSideEncryption: true
// encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED
encryption: dynamodb.TableEncryption.AWS_MANAGED,
});
I then ran a cdk synth and confirmed that the template had been updated with the encryption:
"HelloHitCounterHits7AAEBF80": {
"Type": "AWS::DynamoDB::Table",
"Properties": {
"KeySchema": [
{
"AttributeName": "path",
"KeyType": "HASH"
}
],
"AttributeDefinitions": [
{
"AttributeName": "path",
"AttributeType": "S"
}
],
"ProvisionedThroughput": {
"ReadCapacityUnits": 5,
"WriteCapacityUnits": 5
},
//
// the encryption had been updated in the CFN template
//
"SSESpecification": {
"SSEEnabled": true
}
},
"UpdateReplacePolicy": "Retain",
"DeletionPolicy": "Retain",
"Metadata": {
"aws:cdk:path": "CdkWorkshopStack/HelloHitCounter/Hits/Resource"
}
},
But the test was failing
➜ cdk-workshop git:(master) ✗ npm run test
> cdk-workshop@0.1.0 test
> jest
FAIL test/hitcounter.test.ts
✓ DynamoDB Table Created (121 ms)
✕ DynamoDB Table Created With Encryption (41 ms)
✓ Lambda Has Environment Variables (33 ms)
.....
lots of lines of code
.....
with the following mismatches:
Missing key at /Properties/SSESpecification (using objectLike matcher)
32 | // THEN
33 | const template = Template.fromStack(stack);
> 34 | template.hasResourceProperties('AWS::DynamoDB::Table', {
| ^
35 | SSESpecification: {
36 | SSEEnabled: true
37 | }
at Template.hasResourceProperties (node_modules/@aws-cdk/assertions/lib/template.ts:52:13)
at Object.<anonymous> (test/hitcounter.test.ts:34:14)
It seemed that the test was looking at an outdated JavaScript file as this didn’t contain the encryption. After running this command, i was able to refresh the deployed the JavaScript from my updated TypeScript.
https://docs.aws.amazon.com/cdk/latest/guide/work-with-cdk-typescript.html#typescript-running
npm run build
This manually updated the JavaScript file and after running the tests again, they succeeded.
npm run test
> cdk-workshop@0.1.0 test
> jest
PASS test/hitcounter.test.ts
✓ DynamoDB Table Created (147 ms)
✓ DynamoDB Table Created With Encryption (54 ms)
✓ Lambda Has Environment Variables (42 ms)
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 0 total
Time: 2.933 s, estimated 4 s
Ran all test suites.
Versions
The versions of the files stored in the package.json are critical to the smooth running of the CDK commands.
Ensure they are all set at the same version and without the “^” as this will ensure the version is upgraded. This is in the package.json file at the root of your application.
"dependencies": {
"@aws-cdk/aws-apigateway": "1.133.0",
"@aws-cdk/aws-codecommit": "1.133.0",
"@aws-cdk/aws-dynamodb": "1.133.0",
"@aws-cdk/aws-lambda": "1.133.0",
"@aws-cdk/aws-sns": "1.133.0",
"@aws-cdk/aws-sns-subscriptions": "1.133.0",
"@aws-cdk/aws-sqs": "1.133.0",
"@aws-cdk/core": "1.133.0",
"@aws-cdk/pipelines": "1.133.0",
Once the versions are hardcoded, run these commands.
npm upgrade -g
npm audit fix
Also ensure you aren’t using the latest version of node (v17.x). On a mac, run these commands.
brew install node@16
brew remove node@17
node --version
v16.13.0
Node Version Manager
I have since discovered the Node Version Manager. This controls multiple versions of node on your machine.
brew install nvm
- follow the directions to add a ~/.nvm directory and add some bits to your ~/.zshrc
# source your environment
. ~/.zshrc
# confirm the binary is on your $PATH
nvm version
# download and install the latest version of Node (currently 17.1.0). node --version will show you 17.1.0
nvm install --lts
# list the versions of node available
nvm ls
# use the system integrated version of node (currently 16.13.0)
nvm use system
# confirm the version of node in use. This should show 16.13.0
node --version