Conditional Resource Deployment and Environment Variables with CDK

Conditional Resource Deployment and Environment Variables with CDK
SHARE

In this demo, we will show how to conditionally deploy resources using environment variables.

Passing Environment Variables

This CDK uses a Stack with several inputs. Besides the standard env input, we are also defining StackProps that will be used to pass the bucketName from the environment to the Stack.

const devEnv = { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, }; const stackProps = { bucketName: process.env.BUCKET_NAME || '', }; new ConditionalDeployment(app, 'ConditionalDeployment', { ...stackProps, env: devEnv, });

Here we see the standard projen created devEnv and the App that is being deployed. We are additionally adding stackProps to the Stack that is being created and passing the environment variable BUCKET_NAME to it if it is present.

Stack and StackProps

interface ConditionalDeploymentProps extends StackProps { bucketName?: string; } let deploymentBucket: IBucket; export class ConditionalDeployment extends Stack { constructor(scope: Construct, id: string, props: ConditionalDeploymentProps) { super(scope, id, props);

Here we have defined ConditionalDeploymentProps as a StackProps to be passed to the Stack as well as deploymentBucket. This will be used later with either a new bucket or the existing bucket.

Condition

if (props.bucketName) { deploymentBucket = Bucket.fromBucketName(this, 'Bucket', props.bucketName); } else { deploymentBucket = new Bucket(this, 'Bucket', { encryption: BucketEncryption.S3_MANAGED, blockPublicAccess: BlockPublicAccess.BLOCK_ALL, removalPolicy: RemovalPolicy.DESTROY, autoDeleteObjects: true, }); }

This is where we will check to see if a string has been passed as a prop named bucketName to the Stack. It is not required to be passed, but if it is, we want to use that bucket as the deployment destination. To do that, we need to get the IBucket using Bucket.fromBucketName.

If no prop named bucketName was passed to the Stack, we will create a bucket. In either case, the bucket that we will be deploying to is referenced by deploymentBucket.

Deploying to the Bucket

const data = { bucketName: deploymentBucket.bucketName, text: 'Hello World', }; new BucketDeployment(this, 'Deployment', { sources: [Source.jsonData('config.json', data)], destinationBucket: deploymentBucket, });

Now, we will create a simple JSON file using Source.jsonData and upload this file to the bucket. This will either be an existing bucket referenced in the environment variables, or a newly created bucket. In either case, we can use deplyomentBucket as the destinationBucket because we are always creating an IBucket. If the bucket already exists, we use fromBucketName and if the bucket does not exist, new Bucket will return an IBucket.

Deploying with Environment Variables

Note: You must replace conditional-cdk-deploy-demo with a unique name

export BUCKET_NAME=conditional-cdk-deploy-demo yarn launch

or

BUCKET_NAME=conditional-cdk-deploy-demo yarn launch

Once completed, we can see that an existing S3 bucket has the config.json file in it.

aws s3 ls s3://conditional-cdk-deploy-demo aws s3 cp s3://conditional-cdk-deploy-demo/config.json ./ cat config.json

Deploying without Environment Variables

unset BUCKET_NAME
yarn launch

When the CDK is deployed, a new bucket will be created. This bucket name will be in the output of the CDK and can be used to verify the creation of the bucket and the deployment of the config.json file.

Testing the CDK

In order to verify that our CDK is correct, we should test it. This is done as part of a projen build. In this demo, we are using Snapshots to test.

test('Snapshot', () => { const app = new App(); const stack = new ConditionalDeployment(app, 'test', {}); const template = Template.fromStack(stack); expect(template.toJSON()).toMatchSnapshot(); }); test('SnapshotWithBucketName', () => { const app = new App(); const stack = new ConditionalDeployment(app, 'test', { bucketName: 'test-bucket', }); const template = Template.fromStack(stack); expect(template.toJSON()).toMatchSnapshot(); });

This will now create snapshots for both scenarios. Without the second snapshot, we would have uncovered lines in the test.