Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to only build one stack in a multistack project? #11625

Closed
adam-nielsen opened this issue Nov 23, 2020 · 18 comments
Closed

How to only build one stack in a multistack project? #11625

adam-nielsen opened this issue Nov 23, 2020 · 18 comments
Assignees
Labels
closed-for-staleness This issue was automatically closed because it hadn't received any attention in a while. guidance Question that needs advice or information. response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days.

Comments

@adam-nielsen
Copy link

adam-nielsen commented Nov 23, 2020

❓ General Issue

In the documentation you can create multiple Stack instances to have a multi-stack deployment. You can then run cdk synth <stackname> to prepare only one stack.

However when you do this, it seems to still build all stacks. This means if I have one stack building Lambda functions in custom Docker containers, and a second stack that does virtually nothing, I have to sit through the whole build process for the stack I am not using. It also does things like VPC lookups for resources that are not in the final stack that you want to deploy.

This is a problem because I want to deploy each stack into different AWS accounts. One stack should go into the test/staging/production accounts, and the other should go into the build account where all the CodeCommit repos are. This means when I run the main stack in the prod account, it complains that the repo stack can't find the repo permission groups (because they only exist in the build account, not in the prod/staging/test accounts). Alternatively when I run the repo stack in the build account, it complains that the main stack can't find its VPC IDs (which doesn't exist in the build account, only in prod/staging/test).

The Question

How can I synth/deploy only one stack in a multi-stack project, with the other stacks completely ignored and not even built, so that I can completely independently deploy each stack in a multi-stack project to a different AWS account?

Environment

  • CDK CLI Version: 1.73.0
  • Module Version: 1.73.0
  • Node.js Version: 14.13.0
  • OS: Arch Linux
  • Language (Version): Javascript
@adam-nielsen adam-nielsen added guidance Question that needs advice or information. needs-triage This issue or PR still needs to be triaged. labels Nov 23, 2020
@SomayaB SomayaB changed the title [cdk] How to only build one stack in a multistack project? How to only build one stack in a multistack project? Nov 25, 2020
@NGL321 NGL321 removed the needs-triage This issue or PR still needs to be triaged. label Dec 21, 2020
@NGL321
Copy link
Contributor

NGL321 commented Dec 21, 2020

Hey @adam-nielson,

Sorry for the delayed response! I just tried to synth a single stack and was able to generate the template for each stack individually using npx cdk synth [STACKNAME] like you described. I am not certain why you are experiencing different behavior, and its hard for me to determine without seeing your top-level (where you create new StackName(app, 'StackName'); The only reason I could see is that one stack has a dependency to another and cannot be synthed without the latter being synthed first.

In regard to the concern about deploying to different accounts, I highly recommend you bake the account data into the stack declaration or at least require it as an input parameter on synth and deploy. You can do this with environments. This will mean that when you do a generic npx cdk deploy --all all stacks will be properly deployed to their given accounts.

Eg.

const app = new cdk.App();
new StackOne(app, 'StackOne', { 
    env: {
        account: '111111111',
        region: 'us-west-1'
    }
});
new StackTwo(app, 'StackTwo', {
    env: {
        account: '22222222',
        region: 'eu-east-1'
    }
});

Let me know if this helps! If not, please leave a little more code here as outlined above.

😸 😷

@NGL321 NGL321 added the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Dec 21, 2020
@github-actions
Copy link

This issue has not received a response in a while. If you want to keep this issue open, please leave a comment below and auto-close will be canceled.

@github-actions github-actions bot added closing-soon This issue will automatically close in 4 days unless further comments are made. closed-for-staleness This issue was automatically closed because it hadn't received any attention in a while. and removed closing-soon This issue will automatically close in 4 days unless further comments are made. labels Dec 29, 2020
@github-actions github-actions bot closed this as completed Jan 3, 2021
@adam-nielsen
Copy link
Author

@NGL321 Sorry for the delay responding, and thanks for your reply!

The example code you have matches my top level structure. When you say you were able to generate each stack individually this is correct, this is what happens, but the problem is that both stacks are processed before one is discarded and the final template is produced.

In your example code, if you place a time consuming task in one stack, then that is executed even if you have told cdk that you don't want to build that stack.

I guess as an example you could put a console.log() in StackOne and observe the message being printed when you have told cdk you only want to generate StackTwo. You will never see the template for StackOne but the code for it still runs. Normally this isn't a problem because it's quick, but when one stack is spending 10 minutes building Docker containers and compiling NodeJS modules for Lambda, it's really noticeable.

Hopefully this makes sense!

@TLadd
Copy link
Contributor

TLadd commented Feb 5, 2021

Yep, I experience the same behavior. When I synth/deploy other stacks that have no dependencies, it bundles up lambda functions in other stacks that take awhile to run.

@simon-lanf
Copy link

Same issue here.

@roryf
Copy link

roryf commented Jun 18, 2021

Not experienced this with CDK, but I've had similar issues previously with Terraform (deploying simple Lambda change required full infra comparison). My conclusion was that tightly coupling your infrastructure deploy to you app code bundling, and even deployment, isn't a good idea - they are different concerns.

@adam-nielsen
Copy link
Author

That's a very good point, it does make sense to split the build and the deploy. It makes rollbacks easier too because you can just run another deploy with the previous version, as opposed to rolling back the code and then rebuilding the previous version from scratch in order to deploy it.

All the CDK examples have the deploy as the main focus with the actual code you're running tacked on almost as an afterthought so I guess it's not necessarily the best architecture for a real application.

I suppose the drawback is that if you want to split the build and deploy you have to figure out where you're uploading your code to, how to manage the versions, and how to control which version should get deployed. All of which requires more work outside of CDK so I guess that's why it's not really seen in the examples.

@rmsy
Copy link

rmsy commented Jul 27, 2021

For anyone who may be interested in doing something similar, here's what I have done:

const stackBuildTargetAcct = process.env.STACK_BUILD_TARGET_ACCT || "dev";

if (stackBuildTargetAcct === "dev") {
  // define dev account stacks
} else if (stackBuildTargetAcct === "prod") {
  // define prod account stacks
}

Then you can just set the value of the STACK_BUILD_TARGET_ACCT environment variable appropriately.

@ahoffmanzotec
Copy link

Very similar as above but used the context structure. Tying the build and deploy process together thru nuke with a wrapper around the cdk synth and deploy calls. makes it pretty easy to do json file updates both for appsettings and for cdk.context.json. Have a flag in the cdk.context.json file for "currentBuild", then just wrap the stack creations in if statements same as above.

But fundamentally the cdk process feels broken in c#. No parameters are passed via arguments, --context flag values don't over write existing values in the cdk.context.json or cdk.json files, have to hack away around a process that should be able to synth a single stack at a time...very much does not feel like it is ready for release and is still a beta project....just my .02

@chrichts
Copy link

Is it possible to do this without adding an additional env variable or context value? We are already specifying the stacks we want to deploy when calling cdk synth/deploy so I would expect to be able to use this value to achieve the same as above.

Ideally, something like this should work:

stackBuildTarget = app.node.tryGetContext("TargetStack")

if (stackBuildTargetAcct === "StackOne-dev") {
  // define dev account stacks
} else if (stackBuildTargetAcct === "StackOne-prod") {
  // define prod account stacks
}

And then call it like this:

cdk deploy StackOne-dev

Anyone had success figuring this out?

@adam-nielsen
Copy link
Author

If you only want to decide based on the account, you can use process.env.CDK_DEFAULT_ACCOUNT which will contain the account ID of wherever you want to deploy to and base your condition on that. But it will tie your stack to a specific AWS account (which you can then control via cdk --profile my-profile), which won't suit everyone.

If you're trying to do this so you can name resources differently across stages (e.g. different S3 bucket names for test and prod) then you're better off passing the account into the stack as a prop, and in there define a stage (e.g. if account = 1234 then stage = 'prod' else stage = 'dev'). Then when you name your resources you just use that string, e.g. bucketName: 'mybucket-' + stage. That way you'll never update a resource in your test stack and forget to update the matching one in prod, as you'll be deploying the same stack to both environments, just with different names for some resources.

@jontiefer
Copy link

For anyone who may be interested in doing something similar, here's what I have done:

const stackBuildTargetAcct = process.env.STACK_BUILD_TARGET_ACCT || "dev";

if (stackBuildTargetAcct === "dev") {
  // define dev account stacks
} else if (stackBuildTargetAcct === "prod") {
  // define prod account stacks
}

Then you can just set the value of the STACK_BUILD_TARGET_ACCT environment variable appropriately.

Thank you, this was the solution I was looking for!

@github-anis-snoussi
Copy link

Still having this same issue.
With multuple lambda functions on different stacks and different account, this issue does cause a lot of time waste.
even using something like the STACK_BUILD_TARGET_ACCT env var isn't enough because we have other stacks within the same account which require building the lambda function.
Only solution would be to get the stack name you want to build and wrap its declaration in an if statement, which tbh should be the default behaviour of cdk.
anyone has a better/working solution ?

@github-anis-snoussi
Copy link

Very similar as above but used the context structure. Tying the build and deploy process together thru nuke with a wrapper around the cdk synth and deploy calls. makes it pretty easy to do json file updates both for appsettings and for cdk.context.json. Have a flag in the cdk.context.json file for "currentBuild", then just wrap the stack creations in if statements same as above.

But fundamentally the cdk process feels broken in c#. No parameters are passed via arguments, --context flag values don't over write existing values in the cdk.context.json or cdk.json files, have to hack away around a process that should be able to synth a single stack at a time...very much does not feel like it is ready for release and is still a beta project....just my .02

Can you please share code snippets of how you achieved that, your approach seems the closest thing to a solution to this issue.

@gabsn
Copy link

gabsn commented Oct 17, 2022

Still having this same issue. With multuple lambda functions on different stacks and different account, this issue does cause a lot of time waste. even using something like the STACK_BUILD_TARGET_ACCT env var isn't enough because we have other stacks within the same account which require building the lambda function. Only solution would be to get the stack name you want to build and wrap its declaration in an if statement, which tbh should be the default behaviour of cdk. anyone has a better/working solution ?

Definitely the only workaround I see for this issue... totally agree: when specifying a stack we shouldn't have to build other stacks, and this should be the default cdk behavior.

@StevenGBrown
Copy link

Related: #6743

@j5nb4l
Copy link

j5nb4l commented Mar 15, 2023

Is it possible to do this without adding an additional env variable or context value? We are already specifying the stacks we want to deploy when calling cdk synth/deploy so I would expect to be able to use this value to achieve the same as above.

Ideally, something like this should work:

stackBuildTarget = app.node.tryGetContext("TargetStack")

if (stackBuildTargetAcct === "StackOne-dev") {
  // define dev account stacks
} else if (stackBuildTargetAcct === "StackOne-prod") {
  // define prod account stacks
}

And then call it like this:

cdk deploy StackOne-dev

Anyone had success figuring this out?

I know this is old, but as far as I can see it is still not resolved and someone might fight it useful.

I tried using the --exclusive as suggested in #6743 but that didn't work as desired. Thanks to your comment I was able to make it work though. The context key you want is aws:cdk:bundling-stacks. In my environment running cdk version 2.68.0 (build 25fda51), the value returned is a Array<string> containing the list of the Stacks that will be synthesized, or a simple ["**"] if it's suppose to build them all. That being said, my app only has two stacks and they don't reference each other, so I'm not sure how it would work if the app is more complex than that.

const app = new App();
const bundlingStacks = app.node.tryGetContext("aws:cdk:bundling-stacks") as Array<string>;
const buildAllStacks = bundlingStacks.includes("**");

if (buildAllStacks || bundlingStacks.includes("MyStackId")) {
    new Stack(app, "MyStackId", {});
}

if (buildAllStacks || bundlingStacks.includes("MyOtherStackId")) {
    new Stack(app, "MyOtherStackId", {});
}

@emmanuelnk
Copy link

@j5nb4l your solution worked beautifully!

For clarity for others its important to note you should still use the flag --exclusively

npx cdk synth STACK_ID --exclusively
npx cdk deploy STACK_ID --exclusively

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-for-staleness This issue was automatically closed because it hadn't received any attention in a while. guidance Question that needs advice or information. response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days.
Projects
None yet
Development

No branches or pull requests