amazon-dynamodbaws-cdkaws-cdk-typescript

How to create else reference resources in AWS CDK in CI/CD?


I’m curious about how to use CI/CD in conjunction with AWS CDK; specifically, I want need a pattern where if a given resource does not yet exist, it is created else the (therefore existing) resource is referenced.

Here is an attempt to create a DynamoDb table if it does not exist else reference it. I’m curious, is this the correct/canonical way to do this?

const existingTable = dynamodb.Table.fromTableName(stack, 'ExistingTable', 'existing-table-name');

const table = new dynamodb.Table(stack, 'MyTable', {
  tableName: 'my-table',
  partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING },
  // Define other table attributes
});

if (!existingTable.tableArn) {
  // Create the table if it doesn't exist
  table.create();
}

Solution

  • This is not something you can do only using CDK. CDK is not a library that interacts with AWS resource APIs by default (there are some exceptions, such as the functions that generally start with lookup*). It is a library that generates CloudFormation templates. Those templates do not have the ability to "create a resource if it doesn't exist" semantic.

    You have a few options here:

    1. You can use the AWS SDK libraries to do the lookup yourself. While this will often work, keep in mind it isn't fool-proof. It is susceptible to Time-of-check-time-of-use (TOCTOU) errors, where the resource did/didn't exist at the time the template was synthesized, but was later created/removed by something else.
    2. You can use a Custom Resource, which would do the check and create using the libraries. This is similarly finicky (and custom resources are notoriously difficult to get right).
    3. You can add a configuration option to your context, where you specify the table name and/or ARN of the table. Then you would conditionally create the resource or use the fromTableName/fromTableArn methods. This is my personal preference, and where appropriate I use this pattern.

    Keep in mind that, generally speaking, this is an anti-pattern. You should know at deploy-time whether you are are using an existing resource or creating a new one. Having it dynamically detect and change what is deployed opens you up to errors/unexpected behavior, up to and including data loss:

    1. What happens if the existing table happens to be deleted by some other resource? A new table will be created when you update. That table will be owned by some new stack, and not contain the data that is expected. Similarly, if this stack is being deployed more than once in an account, the next owner of the table will depend on whoever deploys next (which is likely unexpected).
    2. You have no inherent dependency on the table. Related to #1, a better pattern would be to use an Fn::ImportValue to ensure there is a dependency between two stacks, where one provides the table and the other uses it.