logo

AWS Exam GuidesNewsletterBlogContact

👋 Hey! My name is Wojciech Gawroński, but others call me AWS Maniac.

My goal is to safely guide you through the cloudy and foggy space of the AWS portfolio.

Sharing resources in AWS CDK

5 min read, last updated on 2020-12-22

As described in my blog post about Constructs, the AWS CDK provides a rich class library of constructs, and AWS resources are a particular example of a construct. Additionally, it is sporadic when a resource exists without any context and without relations with other resources.

If we would like to connect our resources to the proper infrastructure graph, we need to model interdependencies between constructs. In AWS CloudFormation, we would either use Fn::Ref or Outputs, Parameters, Export, and Fn::ImportValue. But how should that be done in AWS CDK to preserve idiomatic way?

What options do we have?

We do have two kinds of scenarios here:

  1. Internal sharing across AWS CDK Constructs and Stacks.
  2. Sharing in both ways between AWS CDK and external AWS CloudFormation templates.

Let’s discuss both in order.

Internal Sharing

Internal Sharing means that we want to share resources between higher-level and lower-level Constructs (including Stacks) inside AWS CDK Application.

Single stack

This is easy because you can access the properties, e.g., share the reference between constructs by passing it to the Constructs. AWS CDK discovers that we are inside a single stack and will use Fn::Ref via logical identifiers from AWS CloudFormation. Sweet!

Multiple stacks

If we’d like to build an infrastructure graph across multiple stacks, the solution will be the same as what I described in the previous paragraph, except it will use Outputs, Exports,, and Fn::ImportValue.

If you have worked with this in AWS CloudFormation, you know this can be good and bad at the same time. The truth is that we rarely want such tight coupling between stacks as it is enforced by this functionality.

How to avoid that? There are two options:

  1. Parameters - which are problematic, as I described it here.
  2. As we now can use code and programming language, we can easily employ AWS SSM Parameter Store values, which will work in this case, much like Exports, but without this tight coupling.

External Sharing

However, that’s not all. AWS CDK was designed to cooperate in a mixed environment, with a very wise assumption that we will not rewrite everything into AWS CDK immediately. So we need to have a way to exchange information in a bidirectional way between AWS CDK and existing AWS CloudFormation stacks.

From AWS CDK to AWS CloudFormation

Let’s explain this in an example.

If you define a VPC inside a CDK Application and you’d like to use it from an AWS CloudFormation template, it actually functions much like how you would share the template between plain CFN templates. You would use Outputs and Exports in one template and Parameters or Imports in the other.

In AWS CDK exporting works by calling vpc.export() inside your CDK Application. It creates Exports for all attributes of your VPC that another CDK stack would need to import, and those outputs are available to any AWS CloudFormation stack in the region we operate. Deploy a template via AWS CDK and pick your favorite method of getting the VPC information into the other template.

One note: if you’re not happy about the default naming convention of the Exports (it’s understandable since they are designed to be consumed transparently), you can change it. You’re free to add some additional outputs (via new Output()) to your CDK Stack, which translates directly into the AWS CloudFormation Outputs section, and you can create exported values from there as well.

From AWS CloudFormation to AWS CDK

Let’s analyze a reverse case if you already have an existing VPC deployed through CloudFormation or otherwise, and you want to consume it in a CDK Application.

What you want to do is to import it via Vpc.fromLookup(...) (via its name or a few other options) or Vpc.fromVpcAttributes(...) (more flexible because of many attributes from the VpcAttributes struct). Similarly, you can do the same thing for additional resources (e.g., Bucket.fromBucketArn(...)), which will quickly introduce those values into the context. I explained contexts here, but one thing to remember is that it will be looked up once and stay there unless you remove it.

Keep in mind that looking up VPC ID is not the only mechanism we have. We can lookup also AMI ID (LookupMachineImage), Amazon Route 53 Hosted Zone (HostedZone.fromLookup), availability zones (stack.availabilityZones), and literally any value with the help of AWS Systems Manager Parameter Store (SSM). Below you can find an example where one stack creates a parameter and the second one does the lookup of that value:

// Stack 1: Creating `StringParameter` for API Gateway URL
//          inside AWS SSM Paremeter Store.

new ssm.StringParameter(this, 'SSMParameterForUploadURI', {
    parameterName: this.uploadURI,
    stringValue: `${api.url}`
});

// Stack 2: Getting the value of that parameter from AWS SSM.
//          Of course with the help of the lookup.

this.variables['API_ENDPOINT_UPLOAD'] =
  ssm.StringParameter.valueFromLookup(this, props.uploadURI);

If lookups are not enough - alternatively, you can also use other options:

  • AWS CloudFormation Parameters - more cons, especially in the context of AWS CDK, that I explained here.
  • AWS CloudFormation Imports - it will work if resources were created via CloudFormation, and exported.
  • Synthesis-time parameters - not ideal in all cases, but you can choose to pass in the actual values when running the CDK Application. You can do it either as context values or as a hardcoded parameter to your constructs - in the end, the AWS CloudFormation template comes out with the identifiers already filled in.

There is also one additional option. However, it’s not applicable in all cases: you can include the whole template and manage it via AWS CDK from now on:

import * as cdk from "@aws-cdk/core";
import * as fs from "fs";

new cdk.CfnInclude(this, "ExistingInfrastructure", {
  template:
    JSON.parse(fs.readFileSync("template.json").toString())
});

// ...

// And now assuming that `template.json` file contains
// a resource with logical ID equal to `S3Bucket`:

const bucketArn = cdk.Fn.getAtt("S3Bucket", "Arn");

However, keep in mind that you should manage the included template from the AWS CDK from now on.

It’s also worth noting that together with overriding logical ID, this is also a great way to introduce existing resources to AWS CDK without redeploying them.

Summary

This topic is essential, especially if you are managing existing infrastructure.

As AWS CDK is based on AWS CloudFormation, it is both a blessing (that we can use existing mechanisms as-is) and a curse (that those mechanisms are constraining us in many cases). Our job is to understand and choose the proper machinery, and I hope this post will help you.

Subscribe to the newsletter and get notifications about new posts.

Subscribe

👋 Hey! My name is Wojciech Gawroński, but some people call me AWS Maniac. I am your trusted guide through the AWS Madness. If you want to learn more about me, you can start here.

Share it:

YouTube, Twitter, LinkedIn, Instagram, Facebook
awsmaniac.com © 2021, built with Gatsby and template from @kjendrzyca