Skip to content

Remediating “Cannot delete/update export <EXPORT_NAME> as it is in use by <IMPORTING_STACK>.” And migrating to SSM Parameters for dynamic updates

7 minute read
Content level: Intermediate
1

Migrating to SSM parameters for CDK cross stack references to dynamically reference resources between stacks

Use Case:

This Article walks through the migration from CDK native cross stack references, which utilize CloudFormation Imports and Exports, to the utilization of SSM Parameters which are able to be updated dynamically when deploying multiple stacks from CDK.

Scenario:

Two stacks are deployed in the same deployment from CDK where one stack is utilizing a value from another stack using the CDK native method of CloudFormation Exports/Imports. Upon update/deletion of the exported resource, an error is returned that the export cannot be deleted or updated due to it being in use by the second stack. For this example, we are attempting to update the reference from one CloudWatch Alarm to a new CloudWatch Alarm. In the current configuration, both alarms are deployed in Stack A as “MyAlarmOriginalExport” and “MyAlarmNew”. “MyAlarmOriginalExport” is imported into Stack B. While attempting to change the Export from “MyAlarmOriginalExport” to “MyAlarmNew”, we receive the error “Delete canceled. Cannot delete export StackA:ExportsOutputFnGetAttMyAlarm696658B6ArnC0ADA1C3 as it is in use by StackB.” The Below example shows this current code configuration. StackA:

export class StackA extends cdk.Stack {
  readonly myAlarmNew: cw.Alarm;
  public readonly myAlarmOriginalExport: cw.Alarm;
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    this.myAlarmOriginalExport = new cw.Alarm(this, 'MyAlarm', {
      metric: new cw.Metric({
        metricName: 'Errors',
        namespace: 'MyNamespace',
        dimensionsMap: { Operation: 'GetItem' },
      }).with({
        period: cdk.Duration.seconds(60),
      }),
      threshold: 1,
      evaluationPeriods: 3,
    });
    this.myAlarmNew = new cw.Alarm(this, 'MyAlarmNew', {
      metric: new cw.Metric({
        metricName: 'Errors',
        namespace: 'MyNamespace',
        dimensionsMap: { Operation: 'GetItem' },
      }).with({
        period: cdk.Duration.seconds(60),
      }),
      threshold: 1,
      evaluationPeriods: 3,
    });

StackB:

interface StackBProps extends cdk.StackProps {
  alarm: cw.Alarm;
}
export class StackB extends cdk.Stack {

  constructor(scope: Construct, id: string, props: StackBProps) {
    super(scope, id, props);
const alarmRule = cw.AlarmRule.fromAlarm(props.alarm, cw.AlarmState.ALARM);
new cw.CompositeAlarm(this, 'MyAwesomeCompositeAlarm', {
  alarmRule,
});
  }
}

AppStack:

const app = new cdk.App();
const stackA = new StackA(app, 'StackA', {
});
const stackB = new StackB(app, 'StackB', {
  alarm: stackA.myAlarmOriginalExport
})
stackB.node.addDependency(
  stackA)

Remediation using SSM Parameters and Manually specified exports in CDK:

To allow the stack to be updated utilizing SSM parameters, there are two possible methods:

Manually specifying the Export:

To allow the export to remain in the stack after it is no longer being referenced in a different stack, we must manually build the export that was generated by CDK, utilizing the same values for the Output Logical ID, the name of the Export, and referencing the “MyAlarmOriginalExport” resource ARN as this maintains the current configuration of the output. We then add the SSM Parameter that has the value of the new “MyAlarmNew” we are wishing to utilize in StackB and provide a name for our Parameter. The additional lines of Code are shown below.

    const output = new cdk.CfnOutput(this, 'MyOutput', {
      value: this.myAlarmOriginalExport.alarmArn,
      exportName: 'StackA:ExportsOutputFnGetAttMyAlarm696658B6ArnC0ADA1C3',
    })
    output.overrideLogicalId('ExportsOutputFnGetAttMyAlarm696658B6ArnC0ADA1C3')
    new ssm.StringParameter(this, 'MyParam', {
      stringValue: this.myAlarmNew.alarmArn,
      parameterName: '/myapp/AlarmParam'
    })

In StackB, we remove the cross stack reference, add one line to reference the SSM parameter we had created previously in Stack A, build the Alarm resource so that it may be properly referenced in the stack, and alter the line that was previously utilizing the import, to now utilize the Alarm resource constructed from the SSM parameter value. These changes are shown below.

interface StackBProps extends cdk.StackProps {
  // alarm: cw.Alarm; //No longer required
}
export class StackB extends cdk.Stack {

  constructor(scope: Construct, id: string, props: StackBProps) {
    super(scope, id, props);
const alarmArn = ssm.StringParameter.valueForStringParameter(this, '/myapp/AlarmParam') //Added Line
const alarm = cw.Alarm.fromAlarmArn(this, 'ImportedAlarmNew', alarmArn); //Added Line
const alarmRule = cw.AlarmRule.fromAlarm(alarm, cw.AlarmState.ALARM); //Altered Line
// const alarmRule = cw.AlarmRule.fromAlarm(props.alarm, cw.AlarmState.ALARM); // Previous value for above line
new cw.CompositeAlarm(this, 'MyAwesomeCompositeAlarm', {
  alarmRule,
});
  }
}

Finally, in the App stack, we remove the import specification of the Alarm the was previously specified as this is no longer required. We also add an explicit dependency so that Stack A is updated with the SSM parameter creation before Stack B references this SSM parameter.

const app = new cdk.App();
const stackA = new StackA(app, 'StackA', {
});
const stackB = new StackB(app, 'StackB', {
  // alarm: stackA.myAlarmNew //No Longer Required
})
stackB.node.addDependency(
  stackA)

Once these updates are applied to the CDK code, both stacks can be updated in the same update operation due to the export having been manually defined in Stack A which mitigates the error regarding the export being updated or deleted while in use. When Stack B is updated, the original value being imported to the stack is now a reference to the new SSM parameter that was created in Stack A. After this update completes, the manually defined export may be removed from the CDK code.

Remediation with SSM Parameters and Manual CloudFormation Updates:

A second method of remediation that works best with deployments to a single region with exports in use across one or more stacks being deployed by CDK is manual updates to the CloudFormation stacks importing the exports that were created by CDK. Going back to the initial state of our previous example where the export in Stack A was not able to be updated due to it being in use in Stack B, we will walk through the CloudFormation and CDK updates required to use SSM parameters.

  1. Start with Identifying the export name and value from Stack A from the “Outputs” tab of Stack A in the console. In this case the export name is “StackA:ExportsOutputFnGetAttMyAlarm696658B6ArnC0ADA1C3” and the value is the ARN of the CloudWatch Alarm “arn:aws:cloudwatch:us-west-2:111122223333:alarm:StackA-MyAlarm696658B6-QKufw6drYgxm”
  2. Then select Stack B and select “Update stack”, “Make a direct update”, “Edit in Infrastructure Composer”
  3. Once Infrastructure Composer is open, search for the export name we identified in step one. In this example, the exported value is only imported in this stack in one place as seen below in the template snippet.
                "AlarmRule": {
                    "Fn::Join": [
                        "",
                        [
                            "ALARM(\"",
                            {
                                "Fn::ImportValue": "StackA:ExportsOutputFnGetAttMyAlarm696658B6ArnC0ADA1C3"
                            },
                            "\")"
                        ]
                    ]
                }
            }
  1. Once the import(s) of the exported value are identified in Stack B, replace the entire import function with a hard coded value of the Export identified in step one. This change is shown in the below template snippet.
                "AlarmRule": {
                    "Fn::Join": [
                        "",
                        [
                            "ALARM(\"",
                                "arn:aws:cloudwatch:us-west-2:111122223333:alarm:StackA-MyAlarm696658B6-QKufw6drYgxm",
                            "\")"
                        ]
                    ]
                }
            }
  1. Select the “Update Template” option in the upper right corner and select the “Next” option through to push the update to the stack.
  2. Once the update is completed on Stack B, the SSM parameter can be created to reference “AlarmNew” in Stack A as shown below with no requirement to manually specify the Export in Stack A as it is no longer in use.
    new ssm.StringParameter(this, 'MyParam', {
      stringValue: this.myAlarmNew.alarmArn,
      parameterName: '/myapp/AlarmParam'
    })
  1. In Stack B, the same changes from the previous solution can now be applied to reference the SSM parameter rather than the CDK cross stack reference.

Utilization of either of these methods to migrate from the native CloudFormation Import/Export that CDK utilizes for cross stack references allows for these cross stack references to be updated even while in use by additional stacks. This mitigates the “Export could not be updated/Deleted” error that is common while implementing cross stack references using CDK.