Repository navigation
fix(cloudformation-diff): diff crashes on removed resource metadata - #2106
abhiramshaji-cyber wants to merge 1 commit into
Conversation
When a change set is used, metadata differences are hidden by rebuilding the Metadata difference from the new value on both sides. If the new template has no Metadata on a resource that had it before, for example when synthesizing with --path-metadata false against a stack deployed with metadata, both sides are undefined and the Difference constructor throws "oldValue and newValue are both undefined!". Keep the old value when the metadata was removed, so every case (added, removed, changed) becomes an equal pair and is ignored as intended. Closes aws#390
|
I only noticed afterwards that @fukunaga-kengo opened #1741 for this in July, which marked the existing Metadata difference as unchanged instead of building a new pair. That is a nice approach too, and credit to them for it. Would you prefer that direction? I am very happy to switch, or to step aside if they want to reopen theirs. |
|
@abhiramshaji-cyber @fukunaga-kengo Thanks for both of your fixes! I think each PR is missing a test case:
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2106 +/- ##
=======================================
Coverage 92.08% 92.08%
=======================================
Files 83 83
Lines 13246 13246
Branches 1886 1885 -1
=======================================
Hits 12197 12197
Misses 1011 1011
Partials 38 38
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Automated review A maintainer will still review this — treat the notes below as a starting point. 📌 Commit This PR fixes a crash in The change is clean and correctly scoped. The removal, addition, and changed-on-both-sides cases all yield a defined value, and an explicit 🔴 0 blocking · 🟡 0 recommended · ⚪ 0 optional Generated automatically. React 👍 or 👎 to tell us whether this review helped, so we can improve these reviews. |
|
@fukunaga-kengo I'm leaning towards #1741 once additional tests are added. |
|
Thanks @mrgrain for the review, and thanks @fukunaga-kengo for getting there first with #1741. I'm happy to close this one in favour of #1741. To help, here is a test for #1741 that drops the test.each([
['removed', true, false],
['added', false, true],
])('metadata key that was %s is obscured from the diff', (_, oldHasMetadata, newHasMetadata) => {
const template = (hasMetadata: boolean) => ({
Resources: {
BucketResource: {
Type: 'AWS::S3::Bucket',
Properties: { BucketName: 'magic-bucket' },
...(hasMetadata ? { Metadata: { 'aws:cdk:path': '/foo/BucketResource' } } : {}),
},
},
});
const differences = fullDiff(template(oldHasMetadata), template(newHasMetadata), {});
expect(differences.differenceCount).toBe(0);
}); |
|
Closed in favor of #1741 |
Pull request was closed
Running a diff against a stack whose resources lost their metadata crashed with an assertion error, and now the diff prints and ignores that metadata as intended.
What this code is for
fullDiffinpackages/@aws-cdk/cloudformation-diff/lib/diff-template.tscompares the deployed template with the new one, and when a change set is available it hands every changed resource toTemplateAndChangeSetDiffMerger.overrideDiffResourceChangeImpactWithChangeSetChangeImpact. That method corrects each difference with what the change set says. For the resourceMetadatasection it replaces the difference with an equal pair, so metadata changes never show up incdk diffortoolkit.diff().Before this PR
A user runs
cdk diff --path-metadata false --asset-metadata falseagainst a stack that was deployed with metadata, as @andresliszt described.Accountresource hasMetadata: { 'aws:cdk:path': ... }, and the new template has noMetadataon it.Otherdifference namedMetadatawitholdValueset andnewValueundefined.overrideDiffResourceChangeImpactWithChangeSetChangeImpactreachescase 'Metadata'and callsnew types.Difference(value.newValue, value.newValue).Differenceconstructor throwsAssertionError [ERR_ASSERTION]: oldValue and newValue are both undefined!.After this PR
Same user, same command.
case 'Metadata'now picksvalue.oldValuewhenvalue.isRemovalis true, andvalue.newValueotherwise.new types.Difference(ignoredMetadata, ignoredMetadata)with the deployed metadata on both sides, so the difference is not counted.value.newValueexactly as before.The diagram
flowchart LR classDef box fill:#eef1ff,stroke:#4c5bd4,color:#111827 classDef bad fill:#ffd6d6,stroke:#cc0000,color:#111827,stroke-width:3px classDef good fill:#c8f0c8,stroke:#008800,color:#111827,stroke-width:3px subgraph BEFORE["BEFORE: diff crashes, 0 lines printed"] direction TB B1["cdk diff with --path-metadata false"] --> B2["fullDiff with a change set"] B2 --> B3["case Metadata in the merger"] B3 --> B4{"Difference(newValue, newValue)"} B4 -->|"newValue undefined"| B5["AssertionError, diff fails"] B4 -->|"newValue present"| B6["metadata ignored"] end subgraph AFTER["AFTER: diff prints, metadata ignored"] direction TB A1["cdk diff with --path-metadata false"] --> A2["fullDiff with a change set"] A2 --> A3["case Metadata in the merger"] A3 --> A4{"THE CHANGE: oldValue when isRemoval"} A4 -->|"metadata removed"| A5["equal pair, diff prints"] A4 -->|"metadata added or changed"| A6["newValue as before"] end B1 ~~~ A1 class B1,B2,B3,B6 box class A1,A2,A3,A6 box class B4,B5 bad class A4,A5 good linkStyle 3 stroke:#cc0000,stroke-width:5px linkStyle 8 stroke:#008800,stroke-width:5px style BEFORE fill:#fff5f5,stroke:#cc0000,color:#111827 style AFTER fill:#f3fff3,stroke:#008800,color:#111827The change
template-and-changeset-diff-merger.ts,case 'Metadata'now builds the pair fromvalue.isRemoval ? value.oldValue : value.newValue. That is the whole fix. Removed, added and changed metadata all become an equal pair, and none of them can reach the constructor with two undefined values. I choseisRemovalover??so aMetadata: nullin the new template is still kept asnull.test.eachintemplate-and-changeset-diff-merger.test.tscovers metadata that was removed and metadata that was added. The removed case fails onmainwith the same assertion as the issue, and both pass with this change. I also checked, outside the suite,nullagainst absent in both directions and absent on both sides; all give zero differences.Thank you @jakajancar for the detailed report and the logs, which showed exactly which differences reached the merger. @pahud pointed straight at
TemplateAndChangeSetDiffMerger, @coderbyheart found thatDiffMethod.TemplateOnly()avoids it, and @andresliszt noticed that the metadata flags were the trigger, which is what led me to this line.I would really value your view on one choice, and I am happy to change it:
Metadatadifference as an equal pair. Another option is to drop theMetadataentry from the resource entirely. Would you prefer that? I kept the current shape to stay as close as possible to what is there today.What changes for a user
cdk diffandtoolkit.diff()no longer crash when the new template drops resource metadata, for example with--path-metadata falseor--asset-metadata false.Fixes #390
Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license