aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.58k stars 3.88k forks source link

(cfn-include): Handling parameters in a way that is less scary when migrating #13346

Open plumdog opened 3 years ago

plumdog commented 3 years ago

Introduce a way of setting parameters, but in such a way that the diff doesn't make me scared when migrating existing Cloudformation stacks into CDK.

Use Case

I have an existing production system deployed using Cloudformation with parameters, and want to bring it into CDK so I can write new code as CDK and throw away the old deployment tooling. And, where possible, manage changes to the old code through CDK (eg using cfnTemplate.getResource). And I think this will all work fine.

However, I pointed it at my yaml templates, set the parameters for each stack, ran cdk diff and was immediately terrified. There were lots and lots of changes because of clever CDK things setting parameters. I think all these changes are no-ops from what I have been able to manually review.

A small slice of the diff I see:

Parameters
[-] Parameter EnvironmentName: {"Type":"String","Description":"The name of the environment"}
...
Resources
[~] AWS::S3::Bucket mystack-include/BackupBucket BackupBucket replace
 └─ [~] BucketName (requires replacement)
     └─ [~] .Fn::Sub:
         ├─ [-] ${EnvironmentName}-backups
         └─ [+] myapp-backups

Where the existing Cloudformation parameter EnvironmentName is set to myapp, and I've passed parameters: {EnvironmentName: 'myapp'} to CfnInclude. Is it going to try to delete that bucket and replace it? I think... no? It will fail to do so even if it does try, because there's stuff in the bucket, but hopefully this illustrates the source of my fear.

Proposed Solution

Some options I can think of:

  1. Bravery. Trust in the CDK's handling of these parameters to do the right thing.
  2. Diligence. Manually verify all of the parameters will be change from: (a reference to a parameter that is set to X) to: (just X)
  3. cfn-include.CfnInclude grow the ability to set parameters "the old fashioned way", which would match with what I am migrating into CDK using this module. I would expect this to then show nothing in the diff (with the exception of the CDK metadata stuff) which would no longer terrify me. I could then maybe switch the parameters over one-by-one to reduce my fear and to make the diff easier to understand, if I felt the need.
  4. A hack. I have hunted for a way of forcing the parameters to my stacks to be the values I want them to be but not found anything. If I don't set them, then they don't complain and I guess use the values currently deployed in Cloudformation, which is ok, but means their values are not in my source control. And I can maybe set them with the command line, but I can't see a way of doing this when running diff, so am unsure. I was able to update the defaults for the parameters in the template, but I hate this because the parameter values actually set in Cloudformation will take precedence.
  5. Trust Cloudformation changesets rather than cdk's diffs. Run synth, update the stacks with the outputs from that, review the changesets, verify they are no-ops (but for adding CDKMetadata) and execute. This is annoyingly manual, but I think is quite safe.

I would be fairly happy with any of those options except 2. And there might well be another way of handling this to change the synth behaviour around parameters that I don't know.


This is a :rocket: Feature Request

skinny85 commented 3 years ago

Hey @plumdog ,

thanks for opening the issue. You've ran into a very interesting edge case in CloudFormation. The crux of the issue is:

There is a difference in the unresolved template, but not in the resolved template

Where "resolve" means "the values of the parameters have been substituted into the template body, and all CloudFormation functions have been called".

Now, the most important thing: this substitution is actually safe. Meaning, if you run cdk deploy while having that result of cdk diff, CloudFormation will generate an empty change set, and your Bucket will not be replaced.

The bad part is that, because the change set was empty, no actual deployment happened - which means cdk diff after that will still see the old template, and report that same difference.

You can get rid of it by changing any property of the affected resource - something like the metadata is enough. So, in your case, that would be:

const backupBucket = cfnInclude.getResource('BackupBucket');
backupBucket.addMetadata('A', 'B'); // this can be anything

After running cdk deploy, the metadata will force the resource to be updated, and the template for it will be changed (again, this is safe; contrary to what cdk diff says, your Bucket will not be replaced!).

After that deployment succeeds, you can safely get rid of the metadata, and perform another deployment to have your resource be "pristine".

skinny85 commented 3 years ago

Having cleared all that up, the question remains on "how do we make that cdk diff message less scary". I think it's an interesting problem! We would need to check the current value of the parameters, and "resolve" CloudFormation functions like Fn::Sub and Fn::Join to actually determine whether the value in the template and the new value are in fact the same after parameter substitution (even though they differ in the template itself).

I'm leaving this as a feature request for the cloudformation-diff module, where this logic would have to live.

vito-laurenza-zocdoc commented 3 years ago

Having cleared all that up, the question remains on "how do we make that cdk diff message less scary". I think it's an interesting problem! We would need to check the current value of the parameters, and "resolve" CloudFormation functions like Fn::Sub and Fn::Join to actually determine whether the value in the template and the new value are in fact the same after parameter substitution (even though they differ in the template itself).

I'm leaving this as a feature request for the cloudformation-diff module, where this logic would have to live.

This feature would be hugely helpful for those of us migrating lots of legacy stacks. We've been resorting to creating and manually verifying changesets and it's tedious at best.

singlewind commented 8 months ago

My issue is cdk diffkeep fall back to no-change-set diff because parameters missing. In command help, cannot find the parameters option. How can I specify parameters to the include cloudformation template?

skinny85 commented 8 months ago

@singlewind you do it by passing them when instantiating the CfnInclude construct. See the documentation of this feature for more information.

singlewind commented 8 months ago

@skinny85, Yes, we tried this. It replaced the reference token with actual value. This is not the exact output as we expected. It will remove all the description of predefined parameters. It may cause a few issues for a few contructs to recreate. For example export CfnOutput, if another stack has import from it, the change-set will fail to deploy. Just try to figure out what will be a consistent way between diff and deploy commands.

skinny85 commented 8 months ago

How to do that is described in this comment: https://github.com/aws/aws-cdk/issues/13346#issuecomment-789110447. It won't remove or change any Outputs in the Stack (assuming you provided the correct values to the parameters), so everything should work.

singlewind commented 8 months ago

If we have a look the synth result. Previously, the parameter was referenced as token now has been changed to value. Previously, the description and type of parameters was defined in cloudformation. Now it has been wiped out. type and default values possibly doesn't need any more. Description can keep I think. The same on all the resources. The token references has been replaced by value. What if we do want keep it as reference. For example a parameter store securestring value.

skinny85 commented 8 months ago

You can choose to keep any parameters you want, so if you want to keep the ParameterStore SecureString one, you're free to do that 🙂.

Are you saying that, when you keep some or all of Parameters when you do the include, cdk diff still shows some difference for these parameters?

singlewind commented 8 months ago

Thanks @skinny85 for sharing this. No, the function to replace a CloudFormation parameter works as charm. Just for best practices and consistent. cdk deploy allow pass parameters as argument but cdk diff doesn't accept the parameters as argument. The result of that, if we're not replacing all the parameters, cdk diff unable create the diff results based on change-set.

skinny85 commented 8 months ago

Does that mean cdk diff fails? Or does it report false results?

singlewind commented 8 months ago

No. @skinny85 , it will fallback to no-change-set diff.

skinny85 commented 8 months ago

Right. And that’s the correct behavior if there are no changes, right? Or what should happen in that situation?

singlewind commented 8 months ago

@skinny85 , in this situation. The change result is from no-change-set diff. This name may confuse. It means the compare result is not from CloudFormation by create a change-set. It is based on CDK self to work out the change set. The change set will be based on the diff to the include CloudFormation stack.

skinny85 commented 8 months ago

I don't understand what that means, sorry 😀.

Can you please provide:

  1. A rough outline of your situation (what Parameters does the template you include have, what other things you have in the Stack beyond the CfnInclude construct, etc.). A small reproduction that I can try out locally on my machine would be ideal.
  2. What is the current output of cdk diff (please provide the exact command that you execute, with all arguments) for that Stack.
  3. What do you think the behavior should be of cdk diff for that Stack.

Otherwise, we'll just be spinning in circles like we did here for like the 10 last comments 😀.

Thanks, Adam

kidbrax commented 7 months ago

So if I'm understanding this correctly, cdk diff is reporting incorrect information, so this is a bug, right? cdk diff says a resource will be replaced when it actually will not be:

contrary to what cdk diff says, your Bucket will not be replaced!

And it has been that way for years?

singlewind commented 7 months ago

Sorry, I missed the reply. There are two mechanisms for cdk diff. One is just based on comparing the previous deployed template and current deploying template. Another one is analysing the difference by creating a cloudformation changeset. When use CfnInclude a template with replace of parameters, the first approach won't be able to identify the changes. Because the resource name previously is dynamic now it is becoming static. People doesn't know the consequence until they try to deploy it. If cdk diff allow pass the parameters options just like cdk deploy, we should be able to trigger the 2nd approach to identify changes and people will more confident see that compare result instead. At the moment, if there is parameters in original cloudformation template, cdk diff is unable to use the 2nd approach, it will try to use the 1st approach. If the parameter is not replace in the code, it simply throws errors. If we replaced in the CDK code, then we fall into the situation of scary message.

skinny85 commented 7 months ago

@singlewind in that case, I think it's better to split the migration into smaller steps:

  1. Create a CDK application with CfnInclude. Do not replace any parameters at this point, just import the template as-is.
  2. Run cdk diff. This should show you a small diff related to CDK metadata (I talk about it in my video on migrating from CFN to CDK).
  3. Run cdk deploy to do that CDK metadata-only update. After this, ckd diff should show no changes.
  4. Then, start replacing parameters with CfnInclude, going through the procedure outlined in https://github.com/aws/aws-cdk/issues/13346#issuecomment-789110447.
singlewind commented 7 months ago

Thanks @skinny85 , that was exactly we were doing. I just wonder whether it is possible have cdk diff can respect --parameters options as cdk deploy does.

skinny85 commented 7 months ago

I think it's not a bad idea for a feature request 🙂.

TheRealAmazonKendra commented 7 months ago

@singlewind you may want to use cdk migrate instead of CfnInclude. It will translate your CloudFormation stack into actual CDK code (L1s). The one limitation that it will create a new CDK app so if you're looking to just add a stack into an existing app, it doesn't quite do that.