9

I'm trying AWS CDK and got stuck when I tried to execute a code block that depends on the stack completion.

Here's my current code:

class Application extends cdk.Construct {
    constructor(scope: cdk.Construct, id: string) {
        super(scope, id);
        const webStack = new WebsiteStack(app, `website-stack-${id}`, { stage: id })
        const buildStack = new CodeBuildStack(app, `codebuild-stack-${id}`, { stage:id, bucket: webStack.websiteBucket, distribution: webStack.websiteDistribution });
        this.generateBuildParameter(id, webStack, buildStack)
    }

    generateBuildParameter(id: string, webStack: WebsiteStack, buildStack: CodeBuildStack) {
        const buildParam = {
            projectName: buildStack.buildProject.projectName,
            sourceVersion: id,
            environmentVariablesOverride: [
              { name: "STAGE", value: id, type: "PLAINTEXT" },
              { name: "WEBSITE_BUCKET", value: webStack.websiteBucket.bucketName, type: "PLAINTEXT" },
              { name: "CLOUDFRONT_DISTRIBUTION_ID", value: webStack.websiteDistribution.distributionId, "type": "PLAINTEXT" }
            ],
            buildspecOverride: "./buildspec.yml"
        }
        fse.outputJson(`./cdk.out/build-parameters/build-${id}.json`, buildParam, (err: Error) => {
            if (err) {
                throw err
            };
            console.log(`build parameter has been created in "../cdk.out/build-parameters/build-${id}.json"`);
        })
    }
}

I'm just trying to generate a json file that depends on the buildStack. However, it seems that it's not waiting for the stack to complete.

Here's my current output:

{
   "projectName":"${Token[TOKEN.41]}",
   "sourceVersion":"master",
   "environmentVariablesOverride":[{"name":"STAGE","value":"master","type":"PLAINTEXT"},{"name":"WEBSITE_BUCKET","value":"${Token[TOKEN.17]}","type":"PLAINTEXT"},{"name":"CLOUDFRONT_DISTRIBUTION_ID","value":"${Token[TOKEN.26]}","type":"PLAINTEXT"}],
   "buildspecOverride":"./buildspec.yml"
}

Does AWS CDK support Promise or some sort to wait the stack to be completed?

kkesley
  • 3,258
  • 1
  • 28
  • 55

1 Answers1

14

If you're trying to reference 'dynamic' things like the CloudFront distribution Id that will be generated, I would probably try to have 2 different stacks, and have one depend on the other.

I'm not sure I understand your use case correctly. But maybe check out the Core package readme that contains how to parameterize certain things and pass in information across stacks.

https://docs.aws.amazon.com/cdk/api/latest/docs/core-readme.html

EDIT: you can do something like:

var s1 = new stackOne();
var s2 = new stackTwo().addDependency(s1);

This blog post was helpful for me: https://lanwen.ru/posts/aws-cdk-edge-lambda/

Edit: Some practical examples of sharing resources between stacks. StackA creates a CloudFront distribution (The ID of the distribution is dynamic)

StackB needs the CloudFront distribution Id to set up the alarm.

// stackA
export class CloudFrontStack extends cdk.Stack {
  readonly distribution: cf.CloudFrontWebDistribution;
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
 distribution = new cf.CloudFrontWebDistribution(this, 'my-cloud-front-dist', {//props here...} );
 }
}

// stack B
export class AlarmStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, cloudfrontDistributionId: string, props?: cdk.StackProps) {
    super(scope, id, props);   
    new alarm.Alarm()// alarm definition, need ID of CF Distribution here.
 }
}

//index.ts where everything is linked:
const app = new cdk.App();
const stack1= new CloudFrontStack(app, 'CFStack1');
const stack2= new AlarmStack(app, 'AlarmStack', stack1.distribution.distributionId);
// you can even specify that stack2 cannot be created unless stack1 succeeds.
stack2.addDependency(stack1);

EDIT2: For using resources that have been created after a stack is built and outside of the CDK, the easiest way I can think of is to define CfnOutputs and then query the AWS api using the CLI, either manually or in the CI/CD pipeline if we're automating more things after.

Example2: using the previous example we will define an output called CloudFront-DistributioId and query it using the CLI.

// stackA
export class CloudFrontStack extends cdk.Stack {
  readonly distribution: cf.CloudFrontWebDistribution;
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
 distribution = new cf.CloudFrontWebDistribution(this, 'my-cloud-front-dist', {//props here...} );

// define a cloud formation output so we can query later  
new CfnOutput(this, 'CloudFront-DistributionId', {
  exportName: 'CloudFront-DistributionId',
  value: cloudFrontDistribution.distributionId,
  description: 'The dynamic value created by aws of our CloudFront distribution id. '
});
 }
}

After the stack is created, in the pipeline/cli, use the following command to get the value of the variable:

aws cloudformation describe-stacks --stack-name CloudFrontStack --query "Stacks[0].Outputs[?OutputKey=='CloudFront-DistributionId'].OutputValue"

This will produce the Distribution ID that was created after the stack built.

charlybones
  • 309
  • 4
  • 12
  • Hi @charlybones, I'm able to refer the resources between stack. However, my problem is that I want to refer the resources in my cdk code after the stack is created. (I'm creating a JSON file which consists of resource names). However, the value seems to be `TOKEN[....]` rather than the actual resource name. – kkesley Aug 18 '19 at 22:24
  • 1
    Hi @kkesley, I'm missing something. The `${Token[TOKEN.41]}` is something I've seen when trying to read from ParameterStore in AWS while attempting to parameterize stacks. I don't fully understand what it is you're doing to get that. I can tell you though, the CDK does not support replacing Json tokens after the fact. You're synthesizing a CloudFormation file that might not have all the information you need at "synth" time. What is the "buildStack" definition? You might need to use the CfnOutput object to basically instruct CF to use the names you assign to things, and use that in your json. – charlybones Aug 20 '19 at 09:31
  • I apologise for the confusion. My goal is to produce a JSON file which has a dynamic resource name in it (from CDK). Is it possible? – kkesley Aug 20 '19 at 22:32
  • If your goal is to reference resources from StackA in StackB, you don't need a custom Json file to communicate between stacks... This is already supported. See this thread https://stackoverflow.com/questions/52922936/how-to-import-security-group-from-another-stack-using-aws-cdk/57570779#57570779 If you need JSON file for something other than stack creations, use CfnOutput then use the aws cli to query the dynamic value using describe-stacks https://stackoverflow.com/questions/51686580/how-to-get-stack-output-from-aws-sam – charlybones Aug 21 '19 at 14:22
  • Thanks for your answer! My goal is the latter. I need the JSON file for purposes other than creating stacks. Could you edit your answer based on that instead of basing on the first assumption? I'll mark your answer as correct once you do that :) – kkesley Aug 21 '19 at 22:26