Skip to content

Commit 08f8cd3

Browse files
chore(core): enable deletion of more than 10 ssm parameters at a time (#27391)
Cross-region-ssm-writer-handler fails when trying to delete more than 10 ssm parameters This feature is used when exporting resources from a stack in one region to a stack in another region. It uses the `crossRegionReferences: true` flag in the stack. The reason for this change is the following error I received on a stack update (slightly modified to hide some names) ``` ❌ Deployment failed: Error: The stack named NomCoreStack failed to deploy: UPDATE_ROLLBACK_FAILED (The following resource(s) failed to update: [ExportsWriteruseast2828FA26B86FBEFA7]. ): Received response status [FAILED] from custom resource. Message returned: ValidationException: 1 validation error detected: Value '[/cdk/exports/NomServicesStack/NomCoreStackxohpRefUserPoolIdentityPoolA58D72D6C9223D28, /cdk/exports/NomIntegrationTestsStack/NomCoreStackxohpRefUserPoolUserPoolClient40176907C34D1493, /cdk/exports/NomLeafStack/NomCoreStackxohpRefUserPoolIdentityPoolA58D72D6C9223D28, /cdk/exports/NomLeafStack/NomCoreStackxohpFnGetAttUserPool6D0DFADBArnD191ECDE, /cdk/exports/NomCoreStack/NomCoreStackxohpRefUserPoolUserPoolClient40176907C34D1493, /cdk/exports/NomIntegrationTestsStack/NomCoreStackxohpFnGetAttUserPool6D0DFADBArnD191ECDE, /cdk/exports/NomIntegrationTestsStack/NomCoreStackxohpRefUserPoolIdentityPoolA58D72D6C9223D28, /cdk/exports/NomServicesStack/NomCoreStackxohpFnGetAttUserPool6D0DFADBArnD191ECDE, /cdk/exports/NomLeafStack/NomCoreStackxohpRefUserPoolUserPoolClient40176907C34D1493, /cdk/exports/NomServicesStack/NomCoreStackxohpRefUserPoolUserPoolClient40176907C34D1493, /cdk/exports/NomLeafStack/NomCoreStackxohpRefUserPoolUserPoolDomain9F01E991C941B942]' at 'names' failed to satisfy constraint: Member must have length less than or equal to 10 ``` Design for the fix is simple: Break the list of names to delete into chunks of at most 10. Invoke delete as many times as needed to delete all. Throttling is not handled here. This may come into play when there are 50 or more parameters to delete, which may not be a concern worth addressing at this point. Closes No issue created for this ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent ae24fbf commit 08f8cd3

File tree

8 files changed

+254
-169
lines changed

8 files changed

+254
-169
lines changed

packages/@aws-cdk-testing/framework-integ/test/core/test/integ.cross-region-references.js.snapshot/CrossRegionRefProducerInteg.assets.json

+5-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk-testing/framework-integ/test/core/test/integ.cross-region-references.js.snapshot/CrossRegionRefProducerInteg.template.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
"S3Bucket": {
9595
"Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-us-east-1"
9696
},
97-
"S3Key": "1a067234d252533a95ecaaccd4b3e821e6a69df0b03b918b596fc5a40eeb71a1.zip"
97+
"S3Key": "0cd3b0876ff31491e457d92c24b80c95310950e9839c43eb5a86ad9535d15597.zip"
9898
},
9999
"Timeout": 900,
100100
"MemorySize": 128,

packages/@aws-cdk-testing/framework-integ/test/core/test/integ.cross-region-references.js.snapshot/asset.0cd3b0876ff31491e457d92c24b80c95310950e9839c43eb5a86ad9535d15597/index.js

+164
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk-testing/framework-integ/test/core/test/integ.cross-region-references.js.snapshot/asset.1a067234d252533a95ecaaccd4b3e821e6a69df0b03b918b596fc5a40eeb71a1/index.js

-152
This file was deleted.

packages/@aws-cdk-testing/framework-integ/test/core/test/integ.cross-region-references.js.snapshot/manifest.json

+4-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/aws-cdk-lib/core/lib/custom-resource-provider/cross-region-export-providers/cross-region-ssm-writer-handler/index.ts

+24-9
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,9 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent
3030
const removedExports = except(oldExports, exports);
3131
await throwIfAnyInUse(ssm, removedExports);
3232
// if the ones we are removing are not in use then delete them
33-
// skip if no export names are to be deleted
3433
const removedExportsNames = Object.keys(removedExports);
35-
if (removedExportsNames.length > 0) {
36-
await ssm.deleteParameters({
37-
Names: removedExportsNames,
38-
});
39-
}
34+
// this method will skip if no export names are to be deleted
35+
await deleteParameters(ssm, removedExportsNames);
4036

4137
// also throw an error if we are creating a new export that already exists for some reason
4238
await throwIfAnyInUse(ssm, newExports);
@@ -48,9 +44,7 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent
4844
// the stack deletion.
4945
await throwIfAnyInUse(ssm, exports);
5046
// if none are in use then delete all of them
51-
await ssm.deleteParameters({
52-
Names: Object.keys(exports),
53-
});
47+
await deleteParameters(ssm, Object.keys(exports));
5448
return;
5549
default:
5650
return;
@@ -74,6 +68,27 @@ async function putParameters(ssm: SSM, parameters: CrossRegionExports): Promise<
7468
}));
7569
}
7670

71+
/**
72+
* Delete parameters no longer in use.
73+
* From https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_DeleteParameters.html there
74+
* is a constraint on names. It must have size at least 1 and at most 10.
75+
*/
76+
async function deleteParameters(ssm: SSM, names: string[]) {
77+
// max allowed by DeleteParameters api
78+
const maxSize = 10;
79+
// more testable if we delete in order
80+
names.sort();
81+
for (let chunkStartIdx = 0; chunkStartIdx < names.length; chunkStartIdx += maxSize) {
82+
const chunkOfNames = names.slice(chunkStartIdx, chunkStartIdx + maxSize);
83+
// also observe minimum size constraint: Names parameter must have size at least 1
84+
if (chunkOfNames.length > 0) {
85+
await ssm.deleteParameters({
86+
Names: chunkOfNames,
87+
});
88+
}
89+
}
90+
}
91+
7792
/**
7893
* Query for existing parameters that are in use
7994
*/

packages/aws-cdk-lib/core/test/custom-resource-provider/cross-region-ssm-writer-handler.test.ts

+56-1
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,62 @@ describe('cross-region-ssm-writer entrypoint', () => {
361361
});
362362
});
363363

364-
test('thorws if parameters are in use', async () => {
364+
test('more than 10 parameters are deleted', async () => {
365+
// GIVEN
366+
const event = makeEvent({
367+
RequestType: 'Delete',
368+
ResourceProperties: {
369+
ServiceToken: '<ServiceToken>',
370+
WriterProps: {
371+
region: 'us-east-1',
372+
exports: {
373+
'/cdk/exports/MyStack/RemovedExport01': 'RemovedValue01',
374+
'/cdk/exports/MyStack/RemovedExport02': 'RemovedValue02',
375+
'/cdk/exports/MyStack/RemovedExport03': 'RemovedValue03',
376+
'/cdk/exports/MyStack/RemovedExport04': 'RemovedValue04',
377+
'/cdk/exports/MyStack/RemovedExport05': 'RemovedValue05',
378+
'/cdk/exports/MyStack/RemovedExport06': 'RemovedValue06',
379+
'/cdk/exports/MyStack/RemovedExport07': 'RemovedValue07',
380+
'/cdk/exports/MyStack/RemovedExport08': 'RemovedValue08',
381+
'/cdk/exports/MyStack/RemovedExport09': 'RemovedValue09',
382+
'/cdk/exports/MyStack/RemovedExport10': 'RemovedValue10',
383+
'/cdk/exports/MyStack/RemovedExport11': 'RemovedValue11',
384+
'/cdk/exports/MyStack/RemovedExport12': 'RemovedValue12',
385+
},
386+
},
387+
},
388+
});
389+
390+
// WHEN
391+
await handler(event);
392+
393+
// THEN
394+
expect(mockPutParameter).toHaveBeenCalledTimes(0);
395+
expect(mocklistTagsForResource).toHaveBeenCalledTimes(12);
396+
expect(mockDeleteParameters).toHaveBeenCalledTimes(2);
397+
expect(mockDeleteParameters).toHaveBeenCalledWith({
398+
Names: [
399+
'/cdk/exports/MyStack/RemovedExport01',
400+
'/cdk/exports/MyStack/RemovedExport02',
401+
'/cdk/exports/MyStack/RemovedExport03',
402+
'/cdk/exports/MyStack/RemovedExport04',
403+
'/cdk/exports/MyStack/RemovedExport05',
404+
'/cdk/exports/MyStack/RemovedExport06',
405+
'/cdk/exports/MyStack/RemovedExport07',
406+
'/cdk/exports/MyStack/RemovedExport08',
407+
'/cdk/exports/MyStack/RemovedExport09',
408+
'/cdk/exports/MyStack/RemovedExport10',
409+
],
410+
});
411+
expect(mockDeleteParameters).toHaveBeenCalledWith({
412+
Names: [
413+
'/cdk/exports/MyStack/RemovedExport11',
414+
'/cdk/exports/MyStack/RemovedExport12',
415+
],
416+
});
417+
});
418+
419+
test('throws if parameters are in use', async () => {
365420
// GIVEN
366421
const event = makeEvent({
367422
RequestType: 'Delete',

0 commit comments

Comments
 (0)