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

No longer accessing another stack's resource causes deadly embrace #7602

Closed
mcalello opened this issue Apr 24, 2020 · 14 comments · Fixed by #12778
Closed

No longer accessing another stack's resource causes deadly embrace #7602

mcalello opened this issue Apr 24, 2020 · 14 comments · Fixed by #12778
Assignees
Labels
bug This issue is a bug. cross-stack Related to cross-stack resource sharing effort/medium Medium work item – several days of effort p1

Comments

@mcalello
Copy link

CDK's automatic determination and synthesis of exports to imports can get locked into a deadly embrace that cannot be resolved without destroying your stacks.

Reproduction Steps

Stack1 creates a common Security Group, sg1.
Stack2 uses sg1.

CDK's automatic determination and synthesis described here, will generate an export output from Stack1 for sg1 and an Fn:ImportValue into Stack2.

Then you deploy these stacks.

Later, you decide that Stack2 really needs it's own more specific Security Group, so you create it's own sg2. Again, CDK's automatic determination and synthesis realizes that sg1 is no longer referenced by Stack2 (or any other stack) and attempts to delete the Export of sg1 from Stack1.

This will fail and cannot be deployed.

CloudFormation will be prevented from deleting sg1 as an export from Stack1 since it is currently being used as an Import in the (existing) Stack2. Ironically, you were trying to update Stack2 to no longer reference Stack1's sg1.

Error Log

UPDATE_ROLLBACK_IN_P | AWS::CloudFormation::Stack  | Stack1 Export Stack1:ExportsOutputFnGetAtt-sg1-C785ABD5GroupId30372C66 cannot be deleted as it is in use by Stack2

Environment

  • **CLI Version : aws-cli/1.16.223 Python/3.7.4 Darwin/18.7.0 botocore/1.12.213
  • **Framework Version: 1.34.1 (build 7b21aa0)
  • **OS : macOS 10.14.6
  • **Language : Python3

Other


This is 🐛 Bug Report

@mcalello mcalello added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Apr 24, 2020
@SomayaB SomayaB added the cross-stack Related to cross-stack resource sharing label Apr 29, 2020
@austinbv
Copy link

I have a bit more code to support this as you create a circular reference that ends up meaning NO changes can be made without deleting an entire stack

export class StackA extends Stack {
  constructor(scope: Construct, id: string, props: StackProps) {
    new Instance(this, "instance1", {
      allowAllOutbound: true,
      machineImage: MachineImage.genericLinux({
        "us-east-2": "ami-*****",
      }),
      blockDevices: [
        { deviceName: "/dev/sda1", volume: mainVolume },
        { deviceName: "/dev/sdm", volume: largeVolume },
      ],
      vpc,
      instanceType: InstanceType.of(InstanceClass.M5A, InstanceSize.XLARGE2),
    });

    new IamStack(this, "IAM stack", {
      instances: [instance],
      env: props?.env,
    });
  }
}

interface IamStackProps extends StackProps {
  instances: IInstance[];
}

export class IamStack extends Stack {
  constructor(scope: Construct, id: string, props: IamStackProps) {
    // ...
    this.group.addToPolicy(
      new PolicyStatement({
        actions: ["ec2-instance-connect:SendSSHPublicKey"],
        effect: Effect.ALLOW,
        resources: props.instances.map(this.buildArn),
      })
    );
    // ...
  }
  private buildArn(instance: IInstance): string {
    return `arn:${Aws.PARTITION}:ec2:${Aws.REGION}:${Aws.ACCOUNT_ID}:instance/${instance.instanceId}`;
  }
}

No change can be made to the instance resource that would require it to recreate (update the AMI). And no change can be made to the Policy statement (including deleting it).

The error is StackA Export StackA:ExportsOutputRefinstance4096131FFAFEED01 cannot be deleted as it is in use by IAMstackF7BED611

The only want to solve this problem is to delete the dependent stack. Deploy the parent stack. Add back the dependent stack.

@austinbv
Copy link

austinbv commented May 1, 2020

so I found a way around this.

If you are updating the child stack you can deploy with the -e tag not deploy the parent stack.

If you are changing the parent stack it's a 2 step process.

  1. Remove the dependency on the parent stack. In my case it was remove the PolicyStatement
export class IamStack extends Stack {
  constructor(scope: Construct, id: string, props: IamStackProps) {
    // ...
   // this.group.addToPolicy(
    //  new PolicyStatement({
    //    actions: ["ec2-instance-connect:SendSSHPublicKey"],
    //    effect: Effect.ALLOW,
    //    resources: props.instances.map(this.buildArn),
    //  })
    //);
    // ...
  }
  private buildArn(instance: IInstance): string {
    return `arn:${Aws.PARTITION}:ec2:${Aws.REGION}:${Aws.ACCOUNT_ID}:instance/${instance.instanceId}`;
  }
}

Then deploy the IAMStack with the -e tag.

  1. Change the parent ami and uncomment the IAMStack and deploy the whole stack.

@eladb
Copy link
Contributor

eladb commented May 7, 2020

We are aware of this issue (duplicate #3414), but I am not sure exactly what we can do about it. Any ideas?

@eladb eladb added the p1 label May 7, 2020
@austinbv
Copy link

austinbv commented May 7, 2020

Our solution is to deploy the substack with the -e flag then deploy the parent stack.

Even some documentation around that would be stronger than just two immutable stacks. Could you always update leaf nodes first, then update the full stack again

@SomayaB SomayaB removed the needs-triage This issue or PR still needs to be triaged. label May 19, 2020
@eladb eladb added the effort/medium Medium work item – several days of effort label Aug 4, 2020
@eladb eladb assigned rix0rrr and unassigned eladb Aug 17, 2020
@mcalello
Copy link
Author

@eladb We hit this problem again today. After thinking through it again, we are wondering... could you mark the no longer referenced output for deletion, but not actually delete it in this pass. Then in a subsequent deploy, it could be safely deleted?

@eladb
Copy link
Contributor

eladb commented Sep 13, 2020

The CDK (currently) doesn't have knowledge of the current state, only the desired state. The exports/imports that are created as a result of cross stack references are only defined if the reference exists. Otherwise they are simply not defined in your CDK app and we don't know that they existed there before.

@smeyffret
Copy link

Could we force the export somehow, even without the resource? If I understand correctly the export name is only based on the path of the construct and the type of reference we're using, not the actual construct itself (please correct me if I'm wrong). Could we have an helper method that will generate the export name for us, so that we can manually keep the export while removing the resource?

const { exportId, exportName } = helperMethodToGenerateImplicitExportName({
    scope: this,
    constructIdToExport: legacyConstructId,
    typeOfReference: 'Ref', // could be 'Arn' or any other type of references
});
new CfnOutput(this, exportId, {
    exportName: exportName,
    value: 'dummy_value',
    description: 'Dummy export to prevent deadly embrace',
});

The method would generate the name StackA:ExportsOutputRefinstance4096131FFAFEED01 for us, thus allowing to remove the resource.

If we need to keep the value untouched (instead of hardcoding 'dummy_value' here), could we use AwsCustomResource to query the existing value of this export and return it as part of the helper method as well?

I personally have to add those exports manually when I am in this situation (hardcoded in the code), and I have a helper script to help query the names from CloudFormation for all stacks. But a dynamic way of generating them would go a long way.

@Dzhuneyt
Copy link
Contributor

Dzhuneyt commented Oct 7, 2020

We encountered this unsolvable case in a trivial infrastructure:

RDS stack -> (referenced in) -> ECS stack

This causes CDK to create CloudFormation outputs and inputs between the two stacks, which is fine. However, at some point, we decided to drop this dependency in favor of another approach of retrieving Database credentials (using Secrets Manager, etc).

This caused CDK to fail though because it first detects that the RDS stack has no longer any dependencies and tries to delete the exported output, but at the same time - fails to do so - because the ECS stack is currently using that output (even though our desired effect is that it no longer uses it.

Is there an elegant solution to this? Maybe one where CDK is smart enough to first deploy the ECS stack to clean up the link between the two?

@skinny85
Copy link
Contributor

@Dzhuneyt while dealing with removing cross-stack references can be tricky in the CDK, there is actually a way to do it. Check out this article: https://www.endoflineblog.com/cdk-tips-03-how-to-unblock-cross-stack-references for a step-by-step guide on how to unblock yourself.

Thanks,
Adam

@edisongustavo
Copy link
Contributor

I have a workaround here too: #5819 (comment)

@eikeon
Copy link

eikeon commented Jan 5, 2021

@Dzhuneyt while dealing with removing cross-stack references can be tricky in the CDK, there is actually a way to do it. Check out this article: https://www.endoflineblog.com/cdk-tips-03-how-to-unblock-cross-stack-references for a step-by-step guide on how to unblock yourself.

Thanks,
Adam

@skinny85, Thank you for your post. Do you happen to have an example of creating the dummy resource in the case one’s stack is in a CDK pipeline?

@skinny85
Copy link
Contributor

skinny85 commented Jan 5, 2021

@Dzhuneyt while dealing with removing cross-stack references can be tricky in the CDK, there is actually a way to do it. Check out this article: https://www.endoflineblog.com/cdk-tips-03-how-to-unblock-cross-stack-references for a step-by-step guide on how to unblock yourself.
Thanks,
Adam

@skinny85, Thank you for your post. Do you happen to have an example of creating the dummy resource in the case one’s stack is in a CDK pipeline?

It's exactly the same. You just git push after creating the dummy Outputs instead of running cdk deploy.

@eikeon
Copy link

eikeon commented Jan 6, 2021

Coming up with all the values to create the dummies for the various stages the stack appears in is not exactly the same from my perspective. But think I've dug deep enough to create a function that'll compute them... what @smeyffret was suggesting. Seems like that would be a low hanging fruit option to offer up to help people navigate this deadly embrace while something more magical is worked out. @eladb @rix0rrr has the explicitly declare the output option been considered -- from above: #7602 (comment)

rix0rrr added a commit that referenced this issue Jan 29, 2021
"Deadly embrace" is a great term but an annoying problem. The best
way to work around it is to manually ensure the CloudFormation Export
exists while you remove the consuming relationship. Adam has a blog
post about this, but the mechanism can be more smooth.

Add a method, `stack.exportAttribute(...)` which can be used to
create the Export for the duration of the deployment that breaks
the relationship, and add an explanation of how to use it.

Fixes #7602.
@mergify mergify bot closed this as completed in #12778 Feb 1, 2021
mergify bot pushed a commit that referenced this issue Feb 1, 2021
…e" (#12778)

Deadly embrace (<3 who came up with this term) is an issue where a consumer
stack depends on a producer stack via CloudFormation Exports, and you want to
remove the use from the consumer.

Removal of the resource sharing implicitly removes the CloudFormation Export,
but now CloudFormation won't let you deploy that because the deployment order
is always forced to be (1st) producer (2nd) consumer, and when the producer deploys
and tries to remove the Export, the consumer is still using it.

The best way to work around it is to manually ensure the CloudFormation Export
exists while you remove the consuming relationship. @skinny85 has a [blog
post] about this, but the mechanism can be more smooth.

Add a method, `stack.exportValue(...)` which can be used to
create the Export for the duration of the deployment that breaks
the relationship, and add an explanation of how to use it.

Genericize the method a bit so it also solves a long-standing issue about no L2 support for exports.

Fixes #7602, fixes #2036.

[blog post]: https://www.endoflineblog.com/cdk-tips-03-how-to-unblock-cross-stack-references


----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
@github-actions
Copy link

github-actions bot commented Feb 1, 2021

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

NovakGu pushed a commit to NovakGu/aws-cdk that referenced this issue Feb 18, 2021
…e" (aws#12778)

Deadly embrace (<3 who came up with this term) is an issue where a consumer
stack depends on a producer stack via CloudFormation Exports, and you want to
remove the use from the consumer.

Removal of the resource sharing implicitly removes the CloudFormation Export,
but now CloudFormation won't let you deploy that because the deployment order
is always forced to be (1st) producer (2nd) consumer, and when the producer deploys
and tries to remove the Export, the consumer is still using it.

The best way to work around it is to manually ensure the CloudFormation Export
exists while you remove the consuming relationship. @skinny85 has a [blog
post] about this, but the mechanism can be more smooth.

Add a method, `stack.exportValue(...)` which can be used to
create the Export for the duration of the deployment that breaks
the relationship, and add an explanation of how to use it.

Genericize the method a bit so it also solves a long-standing issue about no L2 support for exports.

Fixes aws#7602, fixes aws#2036.

[blog post]: https://www.endoflineblog.com/cdk-tips-03-how-to-unblock-cross-stack-references


----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue is a bug. cross-stack Related to cross-stack resource sharing effort/medium Medium work item – several days of effort p1
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants