fix: cloud control hotswap removes reserved aws: tags - #1940
Open
dgandhi62 wants to merge 2 commits into
Open
Conversation
A tags-only hotswap emitted a wholesale "replace /Tags", which declares the resource's complete desired tag set. On a CloudFormation-created resource that implies deleting the reserved aws:cloudformation tags, which the service rejects with "aws: prefixed tag key names are not allowed for external use". Address individual tags by their index in the resource's current Tags list instead, so reserved tags are never named by the patch. Indices come from a Cloud Control GetResource read; if that read fails we now raise HotswapTagReadFailed rather than falling back to the request that fails. Also re-enables the DynamicTag line in the cc-hotswap integ fixture, which was temporarily disabled to unblock CI.
A tags-only hotswap emitted a wholesale "replace /Tags", which declares the resource's complete desired tag set. On a CloudFormation-created resource that implies deleting the reserved aws:cloudformation tags, which the service rejects with "aws: prefixed tag key names are not allowed for external use". Address individual tags by their index in the resource's current Tags list instead, so reserved tags are never named by the patch. Indices come from a Cloud Control GetResource read; if that read fails we now raise HotswapTagReadFailed rather than falling back to the request that fails. Also re-enables the DynamicTag line in the cc-hotswap integ fixture, which was temporarily disabled to unblock CI.
Contributor
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1940 +/- ##
=======================================
Coverage 91.33% 91.33%
=======================================
Files 79 79
Lines 12164 12164
Branches 1721 1721
=======================================
Hits 11110 11110
Misses 1019 1019
Partials 35 35
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:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two
cli-integtests on the sharedcc-hotswapstack failed on every PR (tests 5 & 6), blocking the merge queue:hotswap deployment supports CloudControl-based resources with attribute resolutionhotswap deployment caches template and uses it for subsequent hotswapsBoth died on the
--hotswapdeploy with:Only the SQS Queue failed. The CloudWatch Dashboard and Events Rule in the same stack hotswapped successfully.
Nothing in this repo changed across the boundary: the last run with both shards green was 2026-08-31 21:12 UTC, the first red run 2026-09-01 15:24 UTC, and no commits merged to
mainin that window. The same PR passed both shards on Aug 31 and failed both on Sep 1. The hotswap code had been untouched since Aug 17 and thecc-hotswapfixture since Jul 31, so this seems to be a service-side change in what the tag update is allowed to do.Why this happened (most likely)
The Queue's only changed property is
Tags(the fixture'sDynamicTagvalue moves between deploys), so the Cloud Control hotswap path emitted a single wholesale operation:[{ "op": "replace", "path": "/Tags", "value": [ { "Key": "DynamicTag", "Value": "new value" }, { "Key": "DynamoTableArn", "Value": "arn:aws:dynamodb:..." } ]}]/Tagsaddresses the whole list, so this declares the resource's complete desired tag set. A resource created by CloudFormation also carries the reservedaws:cloudformation:stack-name/stack-id/logical-idtags, which are never present in the template. Therefore, reconciling to that set now seems to imply deleting them. Per the CloudFormation resource tagging docs theaws:prefix is reserved and such tags cannot be updated or deleted externally, and the SQS quotas say the same for delete.Fix
Address individual tags by their index in the resource's current
Tagslist, so reserved tags are never named by the patch and the service sees no change to them:[{ "op": "replace", "path": "/Tags/4", "value": { "Key": "DynamicTag", "Value": "new value" } }]The live ordering is not the template ordering, so the indices cannot be derived from the template. They come from reading current state with Cloud Control
GetResource.replace /Tags/<liveIndex>add /Tags/-remove /Tags/<liveIndex>, unless the key isaws:-prefixedOrdering matters because JSON Patch operations apply in sequence: replacements go first (their indices refer to the unmodified list), removals follow in descending index order so each one leaves the others valid, and appends go last since
/Tags/-only touches the end.If the current tags cannot be read, this raises
HotswapTagReadFailedinstead of falling back to a wholesale replace. On a CloudFormation-created resource that fallback is precisely the request the service rejects, so degrading to it would resurface the original failure with a misleading cause. The error names the resource and points atcloudcontrolapi:GetResourcepermissions, and keeps the underlying cause attached.Not covered
A few resource types model
Tagsas a{ key: value }map rather than a list. There is no index to address there, so that shape keeps the previous behaviour. It is called out in a comment as untested against a service that also carries reserved tags on a map-shaped property.Integ fixture
Re-enables the
DynamicTagline in thecc-hotswapfixture, which was temporarily disabled to unblock CI. That restores Queue/Tagshotswap coverage — without it, the only tag-carrying resource in that stack never exercises the Cloud Control tag path and nothing would catch a regression here.Testing
UpdateResourceis sent in that case. The main test asserts the serialized patch contains neither a wholesale"path":"/Tags"nor the stringaws:anywhere. 55/55 pass in the suite.cdk-hotswap-deployment-supports-cloudcontrol-based-resources.integtestreproduced the failure against a real account inus-east-1before this change, and passes with it.Known assumption
The approach assumes the live tag ordering is stable between the
GetResourceread and when Cloud Control applies the patch. The passing integ test shows this holds in practice for SQS; it is not proven to be deterministic across calls in general.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license