Last updated: Jan 26, 2024
Reading timeยท10 min
To share resources between stacks, in the same CDK app, we have to:
stackA
props
object of stackB
stackA
, so we can access the class propertiesstackA
class properties as props
when instantiating stackB
props
object of stackB
Let's look at an example where we create 2 stacks and share an S3 bucket between them.
The code snippet defines the following 2 CDK stacks:
BucketStack
provisions an S3 bucketLambdaStack
creates a
lambda function and references the
shared bucket resource from the BucketStack
import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as s3 from 'aws-cdk-lib/aws-s3'; import * as cdk from 'aws-cdk-lib'; import * as path from 'path'; export class BucketStack extends cdk.Stack { // ๐ set a property for the bucket public readonly bucket: s3.Bucket; constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // ๐ assign an S3 bucket to the class property this.bucket = new s3.Bucket(this, 'my-bucket', { removalPolicy: cdk.RemovalPolicy.DESTROY, }); } } interface LambdaStackProps extends cdk.StackProps { bucket: s3.Bucket; } export class LambdaStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props: LambdaStackProps) { super(scope, id, props); const {bucket} = props; // ๐ tag the shared bucket cdk.Tags.of(bucket).add('environment', 'staging'); cdk.Tags.of(bucket).add('department', 'accounting'); const lambdaFunction = new lambda.Function(this, 'lambda-function', { runtime: lambda.Runtime.NODEJS_18_X, handler: 'index.main', code: lambda.Code.fromAsset(path.join(__dirname, '/../src/my-lambda')), environment: { // ๐ pass bucket name to lambda BUCKET_NAME: bucket.bucketName, }, }); } }
Let's go over the code snippet.
We defined a BucketStack
, which provisions an S3 bucket. The bucket
resource is assigned as a class property, so we can access it when we
instantiate the class.
We extended the props
object of our second stack, by adding the bucket
type to it.
We defined our LambdaStack
, which will receive the shared bucket in the
props
object. In our LambdaStack
, we add some tags
to the shared bucket and pass its name as an environment variable to a Lambda
function.
Now let's look at how we instantiate the CDK stacks:
import * as cdk from 'aws-cdk-lib'; import {BucketStack, LambdaStack} from '../lib/cdk-starter-stack'; const app = new cdk.App(); const bucketStack = new BucketStack(app, 'bucket-stack', { stackName: 'bucket-stack', env: { region: process.env.CDK_DEFAULT_REGION, account: process.env.CDK_DEFAULT_ACCOUNT, }, }); const lambdaStack = new LambdaStack(app, 'lambda-stack', { // ๐ pass the S3 bucket from the other stack bucket: bucketStack.bucket, stackName: 'lambda-stack', env: { region: process.env.CDK_DEFAULT_REGION, account: process.env.CDK_DEFAULT_ACCOUNT, }, });
We first instantiate the BucketStack
and assign the instance to a variable.
We then instantiate the LambdaStack
, passing in the S3 Bucket.
At this point, we can reference the bucket
on the props
object of our
LambdaStack
.
Lastly, let's add the code for the Lambda function at src/my-lambda/index.js
:
async function main(event) { console.log('BUCKET_NAME ๐', process.env.BUCKET_NAME); return { body: JSON.stringify({message: `${process.env.BUCKET_NAME} ๐`}), statusCode: 200, }; } module.exports = {main};
The Lambda simply prints the name of the shared bucket.
When deploying the stacks, we have to make sure to deploy the BucketStack
first because we are trying to reference it in our LambdaStack
.
Let's deploy the stacks and look at the results.
npx aws-cdk deploy bucket-stack npx aws-cdk deploy lambda-stack
After the stacks have been deployed, we can see that CDK has automatically
created an Output
with the S3 bucket's name to enable us to reference it in
our other stack:
The Tags
section of our shared S3 Bucket shows that the tags we added to it
from our second stack have been applied:
Finally, if we test our function via the Lambda management console, we can see that the function returns the name of the shared bucket:
When deleting the stacks we have to first delete the LambdaStack
and then the
BucketStack
because we can't delete a stack that
exports an output that is referenced in another stack.
npx aws-cdk destroy lambda-stack npx aws-cdk destroy bucket-stack
We are going to look at an example of how to share a VPC between 2 CDK stacks in the same CDK app.
In order to share a VPC between stacks in CDK, we have to:
stackA
.props
object of stackB
with the VPC type.stackA
, so we get access to the VPC resource.stackB
and pass it the VPC resource as a prop.props
object in stackB
.Let's start by defining the following 2 stacks:
VPCStack
creates a VPCLambdaStack
creates a
lambda function and places it in the shared VPC.import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as cdk from 'aws-cdk-lib'; import * as path from 'path'; export class VPCStack extends cdk.Stack { // ๐ set a property for the vpc public readonly vpc: ec2.Vpc; constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); this.vpc = new ec2.Vpc(this, 'my-vpc', { ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'), natGateways: 0, subnetConfiguration: [ { name: 'public-subnet-1', subnetType: ec2.SubnetType.PUBLIC, cidrMask: 24, }, ], }); } } // ๐ extend the props interface of LambdaStack interface LambdaStackProps extends cdk.StackProps { vpc: ec2.Vpc; } export class LambdaStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props: LambdaStackProps) { super(scope, id, props); const {vpc} = props; cdk.Tags.of(vpc).add('environment', 'development'); cdk.Tags.of(vpc).add('department', 'dpt123'); // ๐ lambda function definition const lambdaFunction = new lambda.Function(this, 'lambda-function', { // ๐ place lambda in shared VPC vpc, allowPublicSubnet: true, runtime: lambda.Runtime.NODEJS_18_X, handler: 'index.main', code: lambda.Code.fromAsset(path.join(__dirname, '/../src/my-lambda')), environment: { // ๐ pass the VPC ID as an environment variable VPC_ID: vpc.vpcId, }, }); } }
Let's go over what we did in the code sample:
VPCStack
, which creates a VPC. The important part here is
that we created a vpc
class property and assigned the VPC resource to it.
The property can now be accessed on instances of the class.StackProps
object with the VPC typeLambdaStack
, which references the shared VPC and provisions
a lambda function in itLet's look at how the classes are instantiated:
import * as cdk from 'aws-cdk-lib'; import {LambdaStack, VPCStack} from '../lib/cdk-starter-stack'; const app = new cdk.App(); const vpcStack = new VPCStack(app, 'vpc-stack', { stackName: 'vpc-stack', env: { region: process.env.CDK_DEFAULT_REGION, account: process.env.CDK_DEFAULT_ACCOUNT, }, }); const lambdaStack = new LambdaStack(app, 'lambda-stack', { // ๐ pass the VPC from the other stack vpc: vpcStack.vpc, stackName: 'lambda-stack', env: { region: process.env.CDK_DEFAULT_REGION, account: process.env.CDK_DEFAULT_ACCOUNT, }, });
We first instantiated the VPCStack
and assigned the result to a variable.
We then instantiated our LambdaStack
, passing it the VPC resource as a
prop.
Finally, let's add the code for the lambda function at src/my-lambda/index.js
:
async function main(event) { console.log('VPC_ID ๐', process.env.VPC_ID); return { body: JSON.stringify({message: `${process.env.VPC_ID} ๐`}), statusCode: 200, }; } module.exports = {main};
The function simply references and returns the ID of the shared VPC.
The order of deployment matters because our LambdaStack
references the VPC
resource from the VPCStack
so it has to exist before the LambdaStack
is
deployed.
Let's run the deploy
commands:
npx aws-cdk deploy vpc-stack npx aws-cdk deploy lambda-stack
By looking at the Outputs
section of our VPCStack
, we can see that CDK has
automatically created outputs for the components of the VPC, which will allow us
to access it in our second stack:
If we look at the VPC section of the lambda function, we can see that it was provisioned in the shared VPC:
Finally, if we run the lambda function via the management console, it returns the ID of the shared VPC:
We have to delete the lambda-stack
first because it references an output in
the vpc-stack
.
npx aws-cdk destroy lambda-stack npx aws-cdk destroy vpc-stack
I've also written articles on how to create multiple stacks and environments in CDK and how to use nested stacks in CDK.
The Ref
intrinsic function in CloudFormation returns the value of a
parameter or resource.
Most commonly Ref
returns the name of the resource. For example, if we
reference an S3 bucket or a Dynamodb table, the
value would resolve to the name of the resource.
Before a deployment is run our CDK code gets compiled down to CloudFormation, so
we're able to use the Ref
intrinsic function in our CDK code.
To use the Ref intrinsic function in CDK, we have to access the ref
property
on a CfnResource
.
To demo using refs, I'll create a simple CDK stack that consists of a single S3 bucket:
import * as s3 from 'aws-cdk-lib/aws-s3'; import * as cdk from 'aws-cdk-lib'; export class MyCdkStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props: cdk.StackProps) { super(scope, id, props); const s3Bucket = new s3.Bucket(this, 'avatars-bucket', { removalPolicy: cdk.RemovalPolicy.DESTROY, }); const cfnBucket = s3Bucket.node.defaultChild as s3.CfnBucket; // ๐ get the bucket ref const bucketRef1 = cfnBucket.ref; console.log('bucketRef1 ๐', bucketRef1); // ๐ same thing but using the Fn class const bucketRef2 = cdk.Fn.ref(cfnBucket.logicalId); console.log('bucketRef2 ๐', bucketRef2); } }
In the code sample:
We defined an S3 bucket using the Bucket construct
We got access to the Bucket's CFN resource and
casted the type to CfnBucket
Now we are able to access the ref
property on the CfnBucket
. We then
stored the ref in the bucketRef1
variable.
Alternatively, we can access the resource's ref by invoking the
ref
static method on the
Fn class.
The ref
method takes 1 parameter - the logical id of the resource.
I'll now synth the stack to see if the ref
values resolved at synthesis time:
npx aws-cdk synth
The output shows that the values are CDK Tokens:
In short, tokens in CDK are encoded values that will get resolved at deployment time by CloudFormation. This means that we don't have access to the resolved value in our CDK code and we shouldn't use refs in conditional statements.
The Ref
value will get resolved by CloudFormation at deployment time. In
order to demo this behavior, I'll provision an Output
and set its value to be the bucket's ref:
// ๐ Output with the ref as a value new cdk.CfnOutput(this, 'myBucketRef', { value: bucketRef1, description: 'The name of the s3 bucket', });
If I run cdk synth
, the CloudFormation template will get generated in the
cdk.out
directory:
We can see that the Ref
intrinsic function has been used in the Outputs
section of our template.
Next, I'll deploy the CDK stack to see the Ref
value resolved in our
CloudFormation template:
npx aws-cdk deploy
If I open the CloudFormation console and click on the Outputs
section, I can
see the Ref
value points to the bucket name:
The GetAtt
function returns the value of a specific attribute.
GetAtt
is a function that comes from CloudFormation, but since our CDK code
gets compiled down to CloudFormation before deployment, we can use this feature
in CDK as well.
We can use the GetAtt
function in CDK in two ways:
We are going to demonstrate how to use GetAtt
both ways. I'll create a simple
CDK stack consisting of a single S3 bucket.
First, we'll use the getAtt
function directly on the CfnResource
:
import * as s3 from 'aws-cdk-lib/aws-s3'; import * as cdk from 'aws-cdk-lib'; export class MyCdkStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props: cdk.StackProps) { super(scope, id, props); const s3Bucket = new s3.Bucket(this, id); const cfnBucket = s3Bucket.node.defaultChild as s3.CfnBucket; console.log('directly ๐', cfnBucket.getAtt('Arn').toString()); } }
In the code sample, we:
Defined an S3 bucket using the level 2 Bucket construct
Used the node.defaultChild
property and cast the type to a CfnBucket
so
we can access the getAtt
method
Logged the call to getAtt
as a string
If we synth the stack at this point, we'll see that the value resolves to a Token.
In short a token is an encoded value that will be resolved by CloudFormation at deployment time. If you want to read more on tokens I have written an article - What is a Token in CDK.
import * as s3 from 'aws-cdk-lib/aws-s3'; import * as cdk from 'aws-cdk-lib'; export class MyCdkStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props: cdk.StackProps) { super(scope, id, props); // ...rest console.log( 'on Fn class ๐', cdk.Fn.getAtt(cfnBucket.logicalId, 'Arn').toString(), ); // ๐ add the call to getAtt as an Output new cdk.CfnOutput(this, 'bucketArn', { value: cfnBucket.getAtt('Arn').toString(), description: 'The arn of the s3 bucket', exportName: 'avatarsBucket', }); } }
In the code sample, we:
Used the getAtt
static method on the
Fn class.
The method takes the CloudFormation logical ID
of the resource as the first parameter and the name of the attribute as the
second.
We created an Output and specified the bucket's ARN
from the call to getAtt
as the value.
The output from the console.log
statement is a Token that will be resolved by
CloudFormation at deployment time:
Let's deploy the stack and see if the call to cfnBucket.getAtt
resolves in the
Outputs
section:
npx aws-cdk deploy \ --outputs-file ./cdk-outputs.json
After I've run the deploy
command with the outputs redirected to a file on the
local file system, the contents of cdk-outputs.json
look like:
We can see that the call to cfnBucket.getAtt
got resolved by
CloudFormation at deployment time.
The cdk.out
directory is where the CDK CLI stores file assets in preparation
for deployment. If we take a look at the synthesized CloudFormation template, we
can see the use of the intrinsic GetAtt
function:
{ "Outputs": { "bucketArn": { "Description": "The arn of the s3 bucket", "Value": { // ๐ Uses GetAtt intrinsic function "Fn::GetAtt": [ "mycdkstack4E08F0DD", "Arn" ] }, "Export": { "Name": "avatarsBucket" } } } }
I've also written a detailed guide on how to use outputs in AWS CDK.
You can learn more about the related topics by checking out the following tutorials: