Skip to content

(import): fullDiff mutates the cached current template (sorts DependsOn), leaking reordered DependsOn into the import change set and failing with "modified resources ... not being imported" #1575

@moeyashi

Description

@moeyashi

Describe the bug

cdk import fails with

You have modified resources [...] in your template that are not being imported.
Update, create or delete operations cannot be executed during import operations.

even when the user did not modify those resources, as long as the stack already contains a resource whose DependsOn array is not in alphabetical order.

The resources reported as "modified" are exactly the ones whose DependsOn arrays get re-sorted by the CLI before the IMPORT change set is submitted.

Root cause

ResourceImporter caches the deployed template and reuses the same object to build the IMPORT change-set template. However, cloudformation-diff's fullDiff() mutates its input in place via normalize(), which sorts DependsOn arrays. Because the cached "current template" is first passed to fullDiff() (during import discovery) and later reused as the base for the change-set template, the sorted DependsOn leaks into the submitted IMPORT change set.

CloudFormation's IMPORT change set forbids any modification to non-imported resources, so this purely cosmetic reorder is rejected.

Code references (current main):

  • packages/@aws-cdk/cloudformation-diff/lib/diff-template.ts
    • fullDiff() calls normalize(currentTemplate) / normalize(newTemplate) — mutates the caller's objects (L48–L55)
    • normalize() sorts DependsOn in place: template[key] = template[key].sort(); (L221–L231)
  • packages/@aws-cdk/toolkit-lib/lib/api/resource-import/importer.ts
    • currentTemplate() caches the deployed template into _currentTemplate (L297–L301)
    • discoverImportableResources() passes that cached object to cfnDiff.fullDiff(currentTemplate, this.stack.template) (L249–L251) → mutates it
    • currentTemplateWithAdditions() reuses the same cached (now-mutated) object to build the import template (L307–L308)
    • importResourcesFromMap() submits it as the IMPORT change set (L198–L202)

So fullDiff() should be side-effect free (normalize a copy), and/or ResourceImporter should not reuse the diffed template object as the change-set base.

Expected behavior

cdk import submits the already-deployed (non-imported) resources byte-for-byte unchanged, so importing new resources never trips the "modified resources ... not being imported" guard for resources the user did not touch.

Current behavior

The submitted change-set template has the affected resource's DependsOn sorted alphabetically, while the deployed template has the natural (synth) order. For example a CDK L2 Lambda produces:

  • deployed / cdk synth: DependsOn: [ <Fn>ServiceRoleDefaultPolicy<hash>, <Fn>ServiceRole<hash> ]
  • submitted by cdk import: DependsOn: [ <Fn>ServiceRole<hash>, <Fn>ServiceRoleDefaultPolicy<hash> ] (alphabetical)

CloudFormation then reports that resource (and the resources depending on it) as modified and aborts the import.

Reproduction steps

  1. Deploy a stack that contains a resource with a multi-element DependsOn whose natural order is not alphabetical. A CDK L2 lambda.Function reliably triggers this: it generates Function -> [ ...ServiceRoleDefaultPolicy..., ...ServiceRole... ], whose natural order differs from the alphabetical order (...ServiceRole<hash> sorts before ...ServiceRoleDefaultPolicy<hash>).
  2. Add a new resource to the stack that you intend to import by identifier (e.g. an existing resource of an import-supported type).
  3. Run cdk import (with --force if there are other non-import diffs).
  4. Import is rejected with You have modified resources [<the Function and its dependency graph>] in your template that are not being imported, despite no change to those resources.

Inspecting the CreateChangeSet request (e.g. via -vvv) shows the only difference for the flagged resources is the order of the DependsOn array.

Workaround

Force the affected resource's DependsOn to a single element (sorting a one-element array is a no-op) via an escape hatch, e.g.:

(fn.node.defaultChild as lambda.CfnFunction).addOverride('DependsOn', [<defaultPolicyLogicalId>]);

Environment

  • CDK CLI Version: 2.1125.0
  • Framework Version (aws-cdk-lib): 2.93.0 or 2.201.0
  • Node.js Version: v20.6.0 or v22.11.0
  • OS: macOS

This is 🐛 Bug Report

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions