26

I have an App that has two stacks, both within the same region/account. One of those stacks requires the ARN of a lambda that exists in the other stack. How do I reference this?

// within stackA constructor
public StackA(Construct scope, String id, StackProps props) {
    SingletonFunction myLambda = SingletonFunction.Builder.create(this, "myLambda")
        // some code here
        .build()
    CfnOutput myLambdaArn = CfnOutput.Builder.create(this, "myLambdaArn")
        .exportName("myLambdaArn")
        .description("ARN of the lambda that I want to use in StackB")
        .value(myLambda.getFunctionArn())
        .build();
    
}

App app = new App();
Stack stackA = new StackA(app, "stackA", someAProps);    
Stack stackB = new StackB(app, "stackB", someBProps);
stackB.dependsOn(stackA);

How do pass the ARN into StackB?

Archmede
  • 1,592
  • 2
  • 20
  • 37
John
  • 10,837
  • 17
  • 78
  • 141

5 Answers5

23

CDK's official documentation has a complete example for sharing a S3 bucket between stacks. I copied it below for quicker reference.

/**
 * Stack that defines the bucket
 */
class Producer extends cdk.Stack {
  public readonly myBucket: s3.Bucket;

  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const bucket = new s3.Bucket(this, 'MyBucket', {
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });
    this.myBucket = bucket;
  }
}

interface ConsumerProps extends cdk.StackProps {
  userBucket: s3.IBucket;
}

/**
 * Stack that consumes the bucket
 */
class Consumer extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props: ConsumerProps) {
    super(scope, id, props);

    const user = new iam.User(this, 'MyUser');
    props.userBucket.grantReadWrite(user);
  }
}

const producer = new Producer(app, 'ProducerStack');
new Consumer(app, 'ConsumerStack', { userBucket: producer.myBucket });
Anjan Biswas
  • 7,746
  • 5
  • 47
  • 77
Chuck
  • 1,004
  • 11
  • 11
  • Thanks for this. Would not have found that otherwise, and the example in the docs (https://docs.aws.amazon.com/cdk/latest/guide/resources.html#resource_stack) is pretty limited. – badfun Feb 02 '21 at 21:58
18

You can access resources in a different stack, as long as they are in the same account and AWS Region. The following example defines the stack stack1, which defines an Amazon S3 bucket. Then it defines a second stack, stack2, which takes the bucket from stack1 as a constructor property.

// Helper method to build an environment
static Environment makeEnv(String account, String region) {
    return Environment.builder().account(account).region(region)
            .build();
}

App app = new App();

Environment prod = makeEnv("123456789012", "us-east-1");

StackThatProvidesABucket stack1 = new StackThatProvidesABucket(app, "Stack1",
        StackProps.builder().env(prod).build());

// stack2 will take an argument "bucket"
StackThatExpectsABucket stack2 = new StackThatExpectsABucket(app, "Stack,",
        StackProps.builder().env(prod).build(), stack1.getBucket());
Anjan Biswas
  • 7,746
  • 5
  • 47
  • 77
Abhinaya
  • 949
  • 1
  • 5
  • 12
  • Nice, do you have any documentation regarding this implementation? – Amit Baranes May 03 '20 at 19:03
  • You can find it more detailed in the below AWS documentation https://docs.aws.amazon.com/cdk/latest/guide/resources.html – Abhinaya May 03 '20 at 19:06
  • I rather work with my example since i can import and export from other region\accounts as well, but good to know. thanks for sharing :) – Amit Baranes May 03 '20 at 19:15
  • 2
    where is stack1.getBucket defined? Not defining it means we have to guess and sometimes we guess wrong. – Paul S Sep 24 '20 at 23:48
  • 1
    Here's a post with a full example: https://stackoverflow.com/a/57586393/856498 – cyberwombat Nov 02 '20 at 15:53
  • This answer is inadequate because it doesn't explain how stack1 gets a reference to the bucket in the first place. This is central to solving the problem and should be included in an accepted answer. – teuber789 Nov 09 '21 at 00:50
8

Option 1:

pass the data from Stack A to Stack B using the constructor :

You can extend cdk.stack and create a new class that will contain stackA.

In that stack, expose the relevant data you want by using public XXX: string\number (etc) ( See line 2 in the example).

Later, just pass this data into StackB constructor ( you can pass it using props as well).

Working code snippet:

Stack A:

    export class StackA extends cdk.Stack {
        public YourKey: KEY_TYPE;
    
        constructor(scope: cdk.Construct, id: string, props: cdk.StackProps ) {
            super(scope, id, props);
    
            Code goes here...
    
            // Output the key 
            new cdk.CfnOutput(this, 'KEY', { value: this.YourKey });
    
        }
    }

Stack B:

export class StackB extends cdk.Stack {
    constructor(scope: cdk.Construct, id: string,importedKey: KEY_TYPE, props: cdk.props) {
        super(scope, id, props)

        Code goes here...
        
        console.log(importedKey)

    }
}

bin ts:

const importedKey = new StackA(app, 'id',props).YourKey;
new StackB(app, 'id',importedKey,props);

Option 2:

Sometimes it's just better to save this kind of stuff in the parameter store and read it from there.

More info here.

Amit Baranes
  • 7,398
  • 2
  • 31
  • 53
  • Will this work please for cross-account deployments? e.g. How would I reference a resource like a Lambda defined within `Stack A` from within `Stack B`, if they were in 2 different accounts? I'm trying to do a deployment of `Stack B` with credentials for that account but getting an error that I need credentials for `Stack A` too. – John May 15 '20 at 19:37
  • This should work as with cross region\account as well.. can you sure the error? – Amit Baranes May 15 '20 at 19:42
  • Error looks like: "Need to perform AWS calls for account 111111111111, but no credentials found. Tried: default credentials", where I use credentials for account 222222222222 in order to deploy stack B. Related question here: https://stackoverflow.com/review/suggested-edits/26137203 – John May 15 '20 at 19:52
  • where do you set the value of YourKey in Stack A? – Paul S Sep 24 '20 at 23:46
  • @PaulS you can set it hard-coded or fill it using `this.YourKey` – Amit Baranes Sep 25 '20 at 09:45
  • @AmitBaranes when I try to pass the endpoint url of an apigateway to another stack/construct via `new cdk.CfnOutput(this, api.url, { value: this.apiUrl });` I always get `Cannot use tokens in construct ID: https://${Token[TOKEN.54]}.execute-api.${Token[AWS.Region.4]}.${Token[AWS.URLSuffix.1]}/${Token[TOKEN.72]}/` Did you encounter this problem as well? – benito_h Sep 27 '20 at 12:33
  • @benito_h try - `cdk.CfnOutput(this, 'api', { value: this.apiUrl });` – Amit Baranes Sep 29 '20 at 08:58
7

I found all of the answers to be on the right path, but none explained it fully and/or well. In this example, I'm passing a VPC from a VPC stack to an ECS cluster.

First, add a property to the originating stack. This property is set whenever the asset is created:

export class VpcStack extends cdk.Stack {
    readonly vpc: Vpc;

    constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);
    
        // Here
        this.vpc = new Vpc(this, 'vpc', {
            maxAzs: 3,
            cidr: '10.0.0.0/16',
        });
    });
}

Next, require this property as a parameter to the consuming stack:

// Create an interface that extends cdk.StackProps
// The VPC property is added here
interface EcsClusterStackProps extends cdk.StackProps {
    vpc: Vpc,
}

export class EcsClusterStack extends cdk.Stack {
    // Use your interface instead of the regular cdk.StackProps
    constructor(scope: cdk.Construct, id: string, props: EcsClusterStackProps) {  
        super(scope, id, props);
    
        // Use the passed-in VPC where you need it
        new Cluster(this, "myCluster", {
            capacity: {
                instanceType: InstanceType.of(InstanceClass.M6I, InstanceSize.LARGE)
            },
            clusterName: "myCluster",
            vpc: props.vpc,  // Here
        });
    }
}

Third, pass the reference in your app file:

const app = new cdk.App();

// Create the VPC stack
const vpcStack = new VpcStack(app, 'vpc-stack', {
    env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
});

// Pass the VPC directly to the consuming stack's constructor
const ecsClusterStack = new EcsClusterStack(app, 'ecs-cluster-stack', {
    vpc: vpcStack.vpc,  // Here
});

Hopefully this helps clarify some of the ambiguous areas.

teuber789
  • 1,527
  • 16
  • 28
  • A couple resources that helped me: [Bobby's blog](https://bobbyhadz.com/blog/aws-cdk-share-resources-between-stacks) and the [AWS docs](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-s3-readme.html#sharing-buckets-between-stacks) – teuber789 Nov 09 '21 at 00:49
0

On Alien Attack https://github.com/aws-samples/aws-alien-attack I created a class named ResourceAwareStack that extends Stack, and implements methods to add/get resources that can be shared between stacks. Take a look at it.

  • 1
    This may answer the question, but when the link becomes stale the answer becomes redundant. Can you add more detail so the answer becomes stand-alone without outside links... – Brian Tompsett - 汤莱恩 Jun 08 '23 at 16:38