Skip to content

How do I pass construct objects for a cross-stack reference in a single AWS CDK project?

7 minute read
6

I want to pass construct objects for a cross-stack reference in a single AWS Cloud Development Kit (AWS CDK) project.

Resolution

To create stacks and a cross stack reference, use AWS CloudFormation. Or, use Parameter Store, a capability of AWS Systems Manager, to avoid CloudFormation errors.

Use AWS CloudFormation

The following steps create two example stacks that are named VpcStack and SecurityGroupStack. The VpcStack is a producer stack and the SecurityGroupStack is a consumer stack. In the SecurityGroupStack, a security group is created to reference the Amazon Virtual Private Cloud (Amazon VPC) ID in the VpcStack. When you create stacks, you can customize the names.

Complete the following steps:

  1. To create a project directory, run the following command:

     mkdir my-project  
  2. To navigate to the project directory, run the following command:

    cd my-project  
  3. To initialize a new AWS CDK project, run the following command:

    cdk init --language typescript 
  4. In the lib/my-project-stack.ts file, add the following import statements at the top of the file to import AWS CDK modules:

    import * as ec2 from 'aws-cdk-lib/aws-ec2';
    import * as cdk from 'aws-cdk-lib';
  5. To define a stack and set a property for the Amazon VPC, add the following export statement to your CDK project:

    export class VpcStack extends cdk.Stack { 
      public readonly vpc: ec2.IVpc;  
      constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { 
        super(scope, id, props); 
    
      this.vpc = new ec2.Vpc(this, 'Cross-Ref-Vpc', { 
        maxAzs: 2, 
        natGateways: 1, 
      }); 
     } 
    } 
  6. To define an interface to specify the props that you want to receive from the target stack, add the following lines to your CDK project:

    interface VpcStackProps extends cdk.StackProps { 
      vpc: ec2.IVpc; 
    } 
  7. Create another stack that uses the ExportValue from VpcStack:

    export class SecurityGroupStack extends cdk.Stack { 
      constructor(scope: cdk.App, id: string, props: VpcStackProps) { 
        super(scope, id, props); 
      
        const securityGroupName = "BastionHostSg"; 
        const SecurityGroup = new ec2.SecurityGroup(this, 'securityGroupName', { 
          vpc: props.vpc, 
          allowAllOutbound: true, 
          securityGroupName: securityGroupName, 
        }); 
      } 
    }

    In the preceding example SecurityGroupStack uses VpcStackProps in props. Add vpc: props.vpc to cross reference in the security group properties.

  8. In the bin/my-project/ts file, add the following statements:

    #!/usr/bin/env node
    import 'source-map-support/register';
    import * as cdk from 'aws-cdk-lib';
    import { VpcStack, SecurityGroupStack } from '../lib/my-project-stack';
    
    const app = new cdk.App();
    
    const vpc_stack = new VpcStack(app, 'vpc-stack', {});
    
    const sg_stack = new SecurityGroupStack(app, 'sg-stack', {
      vpc: vpc_stack.vpc,
    });
  9. Run the following commands to deploy the AWS CDK application:

    npm update 
    cdk deploy --all 

Note: When the AWS CDK application is deployed, sg-stack imports the ExportValue from vpc-stack.

Use Parameter Store

Create an ACM stack and an Application Load Balancer stack

When a resource replacement triggers an update to an exported value, but the export is being imported and used by another stack, you receive the following error:

"Export EXPORT_NAME cannot be updated as it is in use by STACK_NAME"

CloudFormation doesn't support updating exports when they're actively referenced by other stacks.

This error prevents the deployment from proceeding until you remove the dependency or update the stacks in the correct order.

If you receive the following CloudFormation error, then create an AWS Certificate Manager (ACM) stack and an Application Load Balancer stack.

To create both stacks, complete the following steps:

  1. Create a project directory:

    mkdir my-project 
  2. Navigate to the new directory:

      
    cd my-project 
  3. and invoke cdk init in the new directory:

    cdk init --language typescript
  4. Rename lib/my-project-stack.ts to lib/acm-stack.ts. Then, add the following import statements to file:

    import * as cdk from 'aws-cdk-lib';
    import * as acm from "aws-cdk-lib/aws-certificatemanager";
    import * as ssm from "aws-cdk-lib/aws-ssm";
    import {Construct} from 'constructs'; 
  5. To define and export an interface acmProps, add the following export statement to lib/acm-stack.ts:

    export interface acmProps extends cdk.StackProps { 
      readonly acmName: string; 
      readonly acmArnExportPath: string; 
     } 
  6. Add the following code to the acm-stack.ts file:

    export class acmStack extends cdk.Stack { 
      constructor(scope: Construct, id: string, props: acmProps) { 
      super(scope, id, props);  
      const cert = new acm.Certificate(this, 'Certificate', { 
      domainName: 'example_domainName.com', 
      validation: acm.CertificateValidation.fromDns(), 
     }); 
     const parameter = new ssm.StringParameter(this, 'acmArnParameter', { 
      parameterName: props.acmArnExportPath, 
      stringValue: cert.certificateArn, 
     }); 
     } 
    } 

    The preceding example defines a stack that's named acmStack and creates an ACM certificate with your domainName and your validation set. It also creates a Parameter Store to add the certificate ARN as a value.
    Note: Modify the validation method for your requirements.

  7. Create a typeScript file that's named albStack in the /lib directory. Then, import the following AWS CDK modules:

    import * as cdk from 'aws-cdk-lib'; 
    import * as ec2 from "aws-cdk-lib/aws-ec2"; 
    import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; 
    import * as ssm from "aws-cdk-lib/aws-ssm"; 
    import * as acm from "aws-cdk-lib/aws-certificatemanager"; 
    import {Construct} from 'constructs'; 
  8. Add the following export statement to define an interface:

    export interface albProps extends cdk.StackProps { 
      readonly acmArnExportPath: string; 
    } 
  9. Add the following code to your lib/alb-stack.ts file in your AWS CDK application:

    export class albStack extends cdk.Stack { 
      constructor(scope: Construct, id: string, props: albProps) { 
      super(scope, id, props); 
      const vpc = new ec2.Vpc(this, "VPC", { natGateways:1 }); 
      const acmArn = ssm.StringParameter.valueForStringParameter(this, props.acmArnExportPath); 
      const certificate = acm.Certificate.fromCertificateArn(this, 'acm', acmArn); 
      const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', { 
      vpc, 
      internetFacing: true, 
     }); 
      alb.addRedirect(); 
     const listener = alb.addListener ('Listener',{ 
     port: 443, 
     certificates: [certificate], 
     }); 
      listener.addTargets('Instance', {port: 80}); 
     } 
    } 

    The preceding statements create an Amazon VPC with one natGateway to reduce cost and define an acmArn to retrieve the value from the Parameter Store. The statements also define a certificate that converts the acmArn (type: String) to type: IListenerCertificate, creates an Application Load Balancer, and add a Listener and sslCertificateArn that references the value in certificates (type: IListenerCertificate).

  10. Add the following code to your bin/my-project.ts file that completes the following tasks:

    #!/usr/bin/env node 
    import 'source-map-support/register'; 
    import * as cdk from 'aws-cdk-lib'; 
    import {acmStack, acmProps} from '../lib/acm-stack'; 
    import {albStack, albProps} from '../lib/alb-stack'; 
    
    const env = {
      account: process.env.CDK_DEFAULT_ACCOUNT,
      region: process.env.CDK_DEFAULT_REGION
    };
    
    const certificateArnSsmPath = "/cdk/acm/cross-stacks-reference/certArn"; 
    
    const app = new cdk.App(); 
    
    const acm_stack = new acmStack(app, "cdk-ssm-acm-stack", { 
      env: env, 
      acmName: "ssm-acm", 
      acmArnExportPath: certificateArnSsmPath, 
    }); 
    
    const alb_stack = new albStack(app, "cdk-ssm-alb-stack", { 
      env: env, 
      acmArnExportPath: certificateArnSsmPath, 
    }); 
    
    alb_stack.addDependency(acm_stack);
  11. Run the following command to deploy the AWS CDK application:

    npm update  
    cdk deploy --all

    The preceding statements define the env variable, certificateArnSsmPath, an AWS CDK application, an ACM stack with the AWS CDK stack name cdk-ssm-acm-stack, and an ALB stack with the AWS CDK stack name cdk-ssm-alb-stack. It also adds a dependency for the ACM and Application Load Balancer stacks so that the ACM stack is created before the Application Load Balancer stack.

Renew the ACM certificate

To renew the ACM certificate before expiration and make sure that the CloudFormation doesn't become stuck in the UPDATE_COMPLETE_CLEANUP_IN_PROGRESS state, complete the following steps:

  1. Add a new certificate in the lib/acm-stack.ts file and name it renew. Then, change the stringValue attribute in parameter to renew.certificateArn:

    export class acmStack extends cdk.Stack {
      constructor(scope: Construct, id: string, props: acmProps) {
        super(scope, id, props);
    
        const cert = new acm.Certificate(this, 'Certificate', {
          domainName: 'example_domainName.com',
          validation: acm.CertificateValidation.fromDns(),
        });
    
        const renew = new acm.Certificate(this, 'renewCertificate', {
          domainName: 'example_domainName.com',
          validation: acm.CertificateValidation.fromDns(),
        });
    
        const parameter = new ssm.StringParameter(this, 'acmArnParameter', {
          parameterName: props.acmArnExportPath,
          stringValue: renew.certificateArn,
        });
      }
    }
  2. Update the AWS CDK application:

    cdk deploy --all 
  3. To clean up the old certificate, remove the certificate construct or add // in front of the certificate constructs to comment it out in the acm-stack.ts file:

    export class acmStack extends cdk.Stack {
      constructor(scope: Construct, id: string, props: acmProps) {
        super(scope, id, props);
    
        // const cert = new acm.Certificate(this, 'Certificate', {
        //   domainName: 'example_domainName.com',
        //   validation: acm.CertificateValidation.fromDns(),
        // });
    
        const renew = new acm.Certificate(this, 'renewCertificate', {
          domainName: 'example_domainName.com',
          validation: acm.CertificateValidation.fromDns(),
        });
    
        const parameter = new ssm.StringParameter(this, 'acmArnParameter', {
          parameterName: props.acmArnExportPath,
          stringValue: renew.certificateArn,
        });
      }
    }

    Note: Replace example_domainName with your domain name.

  4. Update the AWS CDK application.

    cdk deploy --all
3 Comments

For the love of god can I please see the finished code base for this example instead of a bunch of snippets. I can't find a good example of this beyond this guide and I can't figure out where am I supposed to be putting all of these snippets.

replied 3 years ago

Thank you for your comment. We'll review and update the Knowledge Center article as needed.

AWS
MODERATOR

replied 2 years ago

Hi @jsommerville,

I have an example of code in my GitHub repo for this re:Post, which you can refer to for the full code: https://github.com/aws-6w8hnx/cdk-workshop-cross-stack-reference

replied 2 years ago