Borislav Hadzhiev
Last updated: Apr 13, 2022
Check out my new book
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:
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.
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.
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:
fromBucketName
, fromTableName
fromBucketArn
, fromTableArn
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:
We create the Level 1 bucket using the CfnBucket
construct.
We wrap the Level 1 construct using the static fromBucketName method on the Level 2 Bucket construct.
When we use the fromBucketName
method, we don't create a second bucket. We
just wrap the existing CfnBucket into the Level 2 construct
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.
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
:
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:
We define the CfnTable resource
We wrap the CfnTable Level 1 construct into the Table Level 2 construct using the fromTableName static method
When we use the fromTableName
method, we don't create a second table. We
just wrap the existing CfnTable into the Level 2 construct
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
:
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.
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.