fix: prevent fullDiff mutation corrupting cdk import change set#1602
fix: prevent fullDiff mutation corrupting cdk import change set#1602saeekailas wants to merge 7 commits into
Conversation
Clone templates before normalization to prevent mutation of originals.
| const currentCopy = JSON.parse(JSON.stringify(currentTemplate)); | ||
| const newCopy = JSON.parse(JSON.stringify(newTemplate)); |
There was a problem hiding this comment.
use structuredClone instead
There was a problem hiding this comment.
Updated to use structuredClone
good call much cleaner
| private async currentTemplate(): Promise<any> { | ||
| if (!this._currentTemplate) { | ||
| this._currentTemplate = await this.cfn.readCurrentTemplate(this.stack); | ||
| this._currentTemplate = JSON.parse(JSON.stringify(await this.cfn.readCurrentTemplate(this.stack))); |
There was a problem hiding this comment.
Is this required here? Should we freeze the object if we don't expect changes to it?
There was a problem hiding this comment.
Yes, this is required. Without it, fullDiff() mutates the cached object downstream, which corrupts the change set built afterward that's the exact root cause of the bug.
I've kept the clone and also added Object.freeze() to make the intent explicit: this cached value should never be modified. Let me know if you'd prefer a different approach.
| */ | ||
| private async currentTemplateWithAdditions(additions: ImportableResource[]): Promise<any> { | ||
| const template = await this.currentTemplate(); | ||
| const template = JSON.parse(JSON.stringify(await this.currentTemplate())); |
There was a problem hiding this comment.
Updated to structuredClone here as well.
Head branch was pushed to by a user without write access
|
Thanks for the review @mrgrain! Updated all three switched to structuredClone in both places, and added Object.freeze() on the cached template to make the no-mutation contract explicit. Also rebased on top of main. Ready for another look when you get a chance. |
Problem
cdk importwas failing with:You have modified resources [...] in your template that are not
being imported. Update, create or delete operations cannot be
executed during import operations.
This error was appearing even when the user had not modified any
resources. It affected any stack containing a resource with a
multi-element DependsOn array whose natural order is not alphabetical.
A CDK L2 lambda.Function reliably triggers this because CDK generates:
DependsOn: [ FnServiceRoleDefaultPolicy, FnServiceRole ]
which is not in alphabetical order. After the bug, the submitted
change set had them reversed, which CloudFormation treated as a
modification and rejected the import.
Root Cause
Two issues working together:
fullDiff() in diff-template.ts called normalize() directly on the
caller's template objects, sorting DependsOn arrays in-place.
This means fullDiff() was secretly mutating whatever object you
passed into it.
ResourceImporter in importer.ts cached the deployed template and
reused the exact same object for both:
So by the time the change set was submitted, DependsOn arrays were
reordered and CloudFormation saw that as a modification.
Fix
packages/@aws-cdk/cloudformation-diff/lib/diff-template.ts
Clone both templates before calling normalize() so fullDiff() is
completely side-effect free and never mutates the caller's objects.
packages/@aws-cdk/toolkit-lib/lib/api/resource-import/importer.ts
Deep clone the deployed template when storing it in _currentTemplate
so no downstream mutation can ever corrupt the cached value.
Also clone inside currentTemplateWithAdditions() before appending
new resources so the cache stays a clean mirror of the deployed state.
Testing
Added unit test that calls fullDiff() with a template containing
a multi-element DependsOn in non-alphabetical order and asserts
the original object is unchanged after the call.
Manually verified cdk import succeeds on a stack containing a
CDK L2 lambda.Function (the original reproduction case).
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license
Related
Fixes #1575