Aws-cdk: Maintain auto-generated outputs/exports when referencing resources in others stacks.

Created on 15 Jan 2020  路  9Comments  路  Source: aws/aws-cdk


Maintain auto-generated outputs/exports when referencing resources in others stacks.

Use Case


In order to improve code readability, avoid zombies resources, and ease the deploy process in the pipeline
As a devOps
I want to be able to maintain the output/export, after not being used. (the output that gets generated when I reference a resource created in other stack)

Proposed Solution


Have an attribute that let us keep the output/export in order to be able to "remove" the reference of the resource but not the output/export, and not fail at deploy time. (almost the same way the retention policy works)

Other


The deployment process seems to be the following:
1st. Synth the template,
2nd. Removes the resources that changed in a way they have to be replaced/recreated,
3rd. Recreates/creates the resources needed,
4th. Replaces the needed references.
5th. The rest of the steps.
or so it seems.

For example:
Version 1:
API GW method XYZ(stack 1) -> ALambda(stack 2) (this creates an output for ALambda)
Version 2:
API GW method XYZ(stack 1) -> AnotherLambda(stack 2) (this creates an output for AnotherLambda but "erases" the ALambda output because is not being used, failing at deploy time either way) because it would still point to the old ALambda name

In this example fails in the hypothetical 2nd step.

In the proposal I think the deployment process wouldn't change at all and we could avoid de deployment problem.

Our specific problem was that we changed the name of a lambda function that was in a stack and was referenced by APIGateway in another stack. Throwing this error:

Export APIStackExampleStack123456:APIStackExampleStackExportsOutputFnGetAttStepFunctionInvokerABE843C6Arn8B082DE7 cannot be deleted as it is in use by APIStack213522BF

Which is clear but that give us a constraint in the pipeline.

  • [ ] :wave: I may be able to implement this feature request
  • [ ] :warning: This feature might incur a breaking change

PS: This wouldn't go against the design system of CloudFormation because is the same concept the DeletionPolicy.RETAIN resource property have.

This is a :rocket: Feature Request

@aws-cdcore cross-stack efformedium feature-request p1

Most helpful comment

I was able to create a workaround, in the same spirit of the proposed workaround at https://github.com/aws/aws-cdk/issues/5819#issuecomment-643821627, but I don't need the full logical id that CDK generates.

I create a GcStack, which "holds" dummy resources:

from itertools import count
from aws_cdk.core import Resource, CfnResource, App, Stack, Environment

class GcStack(Stack):
    def __init__(self, other_stack: Stack, cdk_environment: Environment):
        app: App = other_stack.node.root
        if not App.is_app(app):
            raise ValueError("The stack doesn't have an App")
        super().__init__(app, "GcStack", env=cdk_environment)
        self.counter = count()

    def register(self, resource: Resource, attribute: str):
        cfn_resource: CfnResource = resource.node.default_child
        CfnResource(
            self,
            f"DummyField{next(self.counter)}",
            type="Custom:Null",
            properties={"foo": cfn_resource.get_att(attribute)},
        )
        CfnResource(
            self,
            f"DummyField{next(self.counter)}",
            type="Custom:Null",
            properties={"foo": cfn_resource.ref},
        )

And then using it:

stack_with_resources = Stack(...)
cdk_environment = Environment(...)

gc_stack = GcStack(
     stack_with_resources,
     cdk_environment=cdk_environment,
 )
 gc_stack.register(
     stack_with_resources.ecr_repository, "Arn"
 )

This creates a stack named GcStack that only contains Custom:Null resources which reference the resources from stack_with_resources. You don't need to deploy the GcStack, but I think it doesn't hurt if you deploy it (I haven't tested though).

With this workaround you can reference resources across many many accounts/environments without having to go through all of the outputs that each account/environment generate.

I hope this helps someone.

All 9 comments

We are having a similar issue, and would appreciate guidance on ways to address.

Currently:
Stack A
dynamo_table_a

Stack B
references dynamo_table_a

We would like to remove the reference from Stack B. However, because there are no more external dependencies on dynamo_table_a CDK tries to delete the CFN Output. This fails on deploy because Stack B (which hasn't been updated yet) still needs that CFN Output.

@nicklofaso
I'll do my best to explain the solution we came up to.
Disclaimer: this is not pretty, this is not nice, but it works.
You'll have to deploy at least twice, but with minimal overhead, and you can automate it.

We created a "Garbage Collector Stack" that has the following recommendations:

  • It should serves only for resources that are going to disappear or be modified.
  • It should be deleted after the change its done.

So...

  1. We add a reference of the resource we want to the GCStack, and we deploy that.
  2. We remove the previous reference, and we deploy that.
  3. You can safely destroy the GCStack manually, remove it from the code, and redeploy, but I know this is not something you might want to do.
    I haven't test this yet, but I was thinking in adding a task in the PipeLine, or create a custom resource, that deletes the GCStack. Or creating a specialized Stack class called GCStack, that inside has the logic to delete itself v铆a boto3 in a custom resource or something simillar.

Hope this help.

@jindriago-scf thanks for the quick response

That is the exact solution we came up with this afternoon! But as you mentioned we were not pleased that we have to manually delete the GCStack to be able to remove it from the CDK code.

This is likely how we will proceed for now, but definitely +1 for a feature like you proposed in the future.

This issue also references the same problem https://github.com/aws/aws-cdk/issues/7602

My current workaround for this is to create a separate stack B, whoms only purpose is for me to add references from stack A. Stack B is never deployed, so in effect we can have exports in stack A that is not referenced in the deployed resources.

It doesn't feel very pretty, but it works as an escape hatch to eventually remove exports without changing the order of deployment. I have a reusable component for it up here: https://github.com/capralifecycle/liflig-cdk/blob/master/src/force-exports.ts

For solving this issue, we somehow need to know what exports should be created without being referenced. I think we have at least two ways to do this:

(1) Peek the deployed environments. But this would require credentials when synthesizing, and possibly more issues.

(2) Commit a list to the repository of all exports, similar as how ec2.Vpc.fromLookup will update cdk.context.json. Then, if a resources is no longer referenced, but in this list, the export will still be created.

There will be at least two situations this file might be cleaned up: The resource no longer exists. The resources is no longer referenced, and all updates are deployed, so we can eventually remove the export as well.

Perhaps a step on the way is to make it easier to force an export without having to create a cross-stack reference.

You can workaround this problem by explicitly declaring CFN output, no need to create/define separate stack. Here is a code template that i used as a workaround

new cdk.CfnOutput(this, 'TmpOutput', {
            value: <value as outputted in the stack>,
            exportName: <export name as outputted in the stack>,
        });

Note that value and exportName should be exactly the same as in current stack.

This is very nice! I like this workaround.

We should add that to our docs.

This works 'til certain point for us,

In our use case/workflow we generate the resources with a prefix --cause each dev has his own set of stacks deployed in AWS-- and that changes the exports generated by each "environment". I tried this but found it very challenging to auto generate the exports dynamically because of this:

Note that value and exportName should be exactly the same as in current stack.

Have you considered implementing an abstraction of cdk.CfnOutput so it can create an export by passing just the resource and scope?

Could this abstraction live in aws_cdk.aws_cloudformation.Output and be something like Ouptut(scope: Construct, resource: str)?

I was able to create a workaround, in the same spirit of the proposed workaround at https://github.com/aws/aws-cdk/issues/5819#issuecomment-643821627, but I don't need the full logical id that CDK generates.

I create a GcStack, which "holds" dummy resources:

from itertools import count
from aws_cdk.core import Resource, CfnResource, App, Stack, Environment

class GcStack(Stack):
    def __init__(self, other_stack: Stack, cdk_environment: Environment):
        app: App = other_stack.node.root
        if not App.is_app(app):
            raise ValueError("The stack doesn't have an App")
        super().__init__(app, "GcStack", env=cdk_environment)
        self.counter = count()

    def register(self, resource: Resource, attribute: str):
        cfn_resource: CfnResource = resource.node.default_child
        CfnResource(
            self,
            f"DummyField{next(self.counter)}",
            type="Custom:Null",
            properties={"foo": cfn_resource.get_att(attribute)},
        )
        CfnResource(
            self,
            f"DummyField{next(self.counter)}",
            type="Custom:Null",
            properties={"foo": cfn_resource.ref},
        )

And then using it:

stack_with_resources = Stack(...)
cdk_environment = Environment(...)

gc_stack = GcStack(
     stack_with_resources,
     cdk_environment=cdk_environment,
 )
 gc_stack.register(
     stack_with_resources.ecr_repository, "Arn"
 )

This creates a stack named GcStack that only contains Custom:Null resources which reference the resources from stack_with_resources. You don't need to deploy the GcStack, but I think it doesn't hurt if you deploy it (I haven't tested though).

With this workaround you can reference resources across many many accounts/environments without having to go through all of the outputs that each account/environment generate.

I hope this helps someone.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

eladb picture eladb  路  3Comments

Kent1 picture Kent1  路  3Comments

ababra picture ababra  路  3Comments

eladb picture eladb  路  3Comments

PaulMaddox picture PaulMaddox  路  3Comments