How to use escape hatches in AWS CDK

avatar

Borislav Hadzhiev

Fri Apr 23 20214 min read

banner

Photo by Cesar Abner

Updated on Fri Apr 23 2021

We use escape hatches in AWS CDK by accessing the node.defaultChild property on the higher level constructs.

Using Escape Hatches in AWS CDK #

If we need to customize a resource property that is not exposed by the higher level Construct we're using, then we need to use an escape hatch.

In order to use an escape hatch in CDK, we have to get access to the Level 1 Cfn Resource. We can do that by accessing the node.defaultChild property on the construct.

A common case where we have to use an escape hatch, because of functionality that's not yet implemented in the higher level construct is when modifying the email configuration of a cognito user pool:

// ๐Ÿ‘‡ define user pool using Level 2 construct
const userPool = new cognito.UserPool(this, 'userpool', {
  // ...props
});

// ๐Ÿ‘‡ access the node.defaultChild property (escape hatch)
const cfnUserPool = userPool.node.defaultChild as cognito.CfnUserPool;

// ๐Ÿ‘‡ work with the CloudFormation resource
cfnUserPool.emailConfiguration = {
  emailSendingAccount: 'DEVELOPER',
  replyToEmailAddress: 'john@example.com',
  sourceArn: `arn:aws:ses:us-east-1:123456789:identity/john@example.com`,
};

In the above code snippet:

  1. We define our User Pool using a Level 2 construct. Unfortunately, at the time of writing the Level 2 construct does not support updating the email configuration, so we have to use an escape hatch.

  2. We access the node.defaultChild property on the Level 2 User Pool construct and cast it as the CfnUserPool resource.

  3. We can now set the emailConfiguration property on the Level 1 CfnUserPool construct.

Let's look at another example, this time we'll use a Dynamodb table:

// ๐Ÿ‘‡ table definition using Level 2 construct
const table = new dynamodb.Table(this, 'my-table', {
  partitionKey: {name: 'todoId', type: dynamodb.AttributeType.NUMBER},
});

// ๐Ÿ‘‡ get access to the Level 1 Cfn resource (escape hatch)
const cfnTable = table.node.defaultChild as dynamodb.CfnTable;

// ๐Ÿ‘‡ update properties on the Level 1 Cfn resource
cfnTable.billingMode = dynamodb.BillingMode.PROVISIONED;
cfnTable.provisionedThroughput = {
  readCapacityUnits: 1,
  writeCapacityUnits: 1,
};
cfnTable.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY);
cfnTable.tags.setTag('env', 'dev');

In our code snippet we:

  1. Define the Dynamodb table using the Level 2 Table construct.

  2. We get the Level 1 Cfn resource by accessing the node.defaultChild property on the Level 2 construct. Note that we also cast the Cfn resource as the appropriate type, so we can then access any properties and methods available on the CfnTable construct

  3. Now that we have access to the CloudFormation resource we are able to set any properties and access any methods, made available in CloudFormation

Escape Hatches - Discussion #

When working with constructs in CDK we aim to use higher levels of abstraction.

Higher levels of abstraction allow us to take advantage of some of the sane defaults and glue methods for service to service interactions put in place by the CDK team in the CDK constructs library.

On the other hand, it's inevitable that by using higher levels of abstraction we lose some of the ability to customize certain resources.

If we need to customize a resource property that is not exposed by the Construct we're using, then we need to use an escape hatch.

We only use escape hatches where we need to fill a gap and interact with configuration properties that are not accessible on a higher level construct.

Our CDK code eventually gets compiled to CloudFormation, which is very customizable, however it also is quite verbose and difficult to manage at scale.

Escape hatches give us the ability to use the best of both worlds.

We can write our code using higher levels of abstraction with Level 2 and Level 3 constructs, however we are also able to use an escape hatch and access the CloudFormation (Level 1) resource and update its properties.

Higher level constructs are just wrappers around the Level 1 Cfn constructs, which means that in order to use an escape hatch we have to find a way to access the Cfn construct.

A Cfn construct is a 1x1 mapping to the corresponding CloudFormation resource and enables us to use all of the configuration properties available in CloudFormation.

Cfn constructs are named in the form of Cfn + the name of the resource, for example:

  • CfnBucket
  • CfnTable
  • CfnFunction

We can access the Cfn resource of a higher level construct by using construct.node.defaultChild.

After we get access to a Cfn resource we have to cast it's type and then we are able to set and access any of the properties and methods made available in CloudFormation.

The ability to use escape hatches enables us to write our code using higher level of abstractions and only revert back to CloudFormation resources in the case a functionality is not exposed to the user of the construct.

Further Reading #

Join my newsletter

I'll send you 1 email a week with links to all of the articles I've written that week

Buy Me A Coffee