How to wrap Level 1 into Level 2 Constructs in AWS CDK

avatar

Borislav Hadzhiev

Last updated: Apr 13, 2022

banner

Check out my new book

Level 1 and Level 2 Constructs in AWS CDK #

Level 1 constructs in CDK are a 1x1 mapping to the corresponding CloudFormation resource. They allow for the most flexibility, because they expose all of the properties a CloudFormation resource does.

Cfn resources are named in the format: Cfn + resource type, for example:

  • CfnBucket
  • CfnTable
  • CfnFunction

Most of the time, however, we want to use a higher level of abstraction on top of CloudFormation, that's where Level 2 constructs come in.

Level 2 constructs give us some sane defaults and most importantly expose glue methods for service to service interactions, i.e. permission grants.

Sometimes we have a Level 1 construct that we want to wrap into a Level 2 construct, this often happens when we import a CloudFormation stack into a CDK application, using CfnInclude.

Wrapping Level 1 into Level 2 Constructs in AWS CDK #

In order to wrap a Level 1 into a Level 2 construct we have to use the static from* methods exposed on the Level 2 construct.

These methods are often named in the form of:

  • fromResourceTypeName, i.e. fromBucketName, fromTableName
  • fromResourceTypeArn, i.e. fromBucketArn, fromTableArn
  • fromResourceTypeAttributes, i.e. fromBucketAttributes, fromTableAttributes

Let's look at an example of a CDK app where we:

export class MyCdkStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // 👇 define level 1 CfnBucket const cfnBucket = new s3.CfnBucket(this, 'my-cfn-bucket', { tags: [{key: 'environment', value: 'development'}], }); cfnBucket.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // 👇 wrap Level 1 into Level 2 Construct const level2Bucket = s3.Bucket.fromBucketName( this, 'my-level2-bucket', cfnBucket.ref, ); } }

In the snippet above:

  1. We create the Level 1 bucket using the CfnBucket construct.

  2. We wrap the Level 1 construct using the static fromBucketName method on the Level 2 Bucket construct.

  3. When we use the fromBucketName method, we don't create a second bucket. We just wrap the existing CfnBucket into the Level 2 construct

  4. At this point we can access any of the properties and methods exposed by the Level 2 Bucket construct, i.e. permission grants like grantRead, grantWrite, etc.

Note that Level 2 resources expose multiple methods that allow us to wrap Level 1 into Level 2 constructs.

For buckets we can use fromBucketArn, fromBucketAttributes, fromBucketName static methods.

In the example, we use the fromBucketName method and pass it the cfnBucket.ref reference that will resolve to the bucket name by CloudFormation at deployment time.

If I deploy the stack with npx aws-cdk deploy, we can see that everything works as expected, our bucket has the logical ID of mycfnbucket:

wrapped bucket

Let's look at an example where we wrap a Dynamodb table Level 1 CfnTable into a Level 2 Table construct:

export class MyCdkStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // 👇 define level 1 CfnTable const cfnTable = new dynamodb.CfnTable(this, 'cfn-table', { keySchema: [ { attributeName: 'todoId', keyType: 'HASH', }, ], attributeDefinitions: [{attributeName: 'todoId', attributeType: 'N'}], provisionedThroughput: { readCapacityUnits: 1, writeCapacityUnits: 1, }, }); cfnTable.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // 👇 wrap Level 1 into Level 2 Construct const table = dynamodb.Table.fromTableName( this, 'level-2-table', cfnTable.ref, ) as dynamodb.Table; } }

In the code snippet:

  1. We define the CfnTable resource

  2. We wrap the CfnTable Level 1 construct into the Table Level 2 construct using the fromTableName static method

  3. When we use the fromTableName method, we don't create a second table. We just wrap the existing CfnTable into the Level 2 construct

  4. At this point we can access any of the properties or methods exposed by the Level 2 Table construct, i.e. addGlobalSecondaryIndex, grantReadData, etc.

The Table level 2 construct also exposes multiple static methods that allow us to wrap a Level 1 construct like:

After I run the npx aws-cdk deploy command, we can see that we have successfully provisioned our table with logical ID of cfntable:

wrapped table

Conclusion #

In order to wrap a Level 1 into a Level 1 Construct we have to use the static from* methods exposed on the Level 2 construct.

After we wrap the Cfn resources into a Level 2 construct we are able to use all of the properties and methods that the Level 2 construct exposes.

This enables us to use a lot of functionality that's already written for us by the AWS CDK team.

If you want to do this process in reverse, that is access the Level 1 resource from a Level 2 construct, because the Level 2 construct doesn't expose the props you need to configure, you need to use an escape hatch.

Further Reading #

I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.