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

avatar

Borislav Hadzhiev

Fri Apr 23 20214 min read

banner

Photo by Leonard Cotte

Updated on Fri Apr 23 2021

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 CDK construct.

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 our case 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 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 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 #

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