From c84666c22658e22956d79b83d0d226278d829f9d Mon Sep 17 00:00:00 2001 From: Momo Kornher Date: Thu, 3 Aug 2023 11:35:38 +0200 Subject: [PATCH] feat(core): new RemovalPolicy.RETAIN_EXCEPT_ON_CREATE to only retain resources that have been successfully created (#26602) Adds support for the RetainExceptOnCreate DeletionPolicy. When `applyToUpdateReplacePolicy` is set, this uses the 'Retain' UpdateReplacePolicy. https://aws.amazon.com/about-aws/whats-new/2023/07/aws-cloudformation-deletion-policies-dev-test-cycle/ https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html#aws-attribute-deletionpolicy-options Closes https://github.com/aws/aws-cdk/issues/26570 Replaces #26595 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../core/lib/cfn-resource-policy.ts | 8 ++++ packages/aws-cdk-lib/core/lib/cfn-resource.ts | 11 ++++- .../aws-cdk-lib/core/lib/removal-policy.ts | 15 ++++++ .../core/test/removal-policy.test.ts | 48 +++++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 packages/aws-cdk-lib/core/test/removal-policy.test.ts diff --git a/packages/aws-cdk-lib/core/lib/cfn-resource-policy.ts b/packages/aws-cdk-lib/core/lib/cfn-resource-policy.ts index 2983ad78dc09e..0d04d1cbcac6d 100644 --- a/packages/aws-cdk-lib/core/lib/cfn-resource-policy.ts +++ b/packages/aws-cdk-lib/core/lib/cfn-resource-policy.ts @@ -91,6 +91,14 @@ export enum CfnDeletionPolicy { */ RETAIN = 'Retain', + /** + * RetainExceptOnCreate behaves like Retain for stack operations, except for the stack operation that initially created the resource. + * If the stack operation that created the resource is rolled back, CloudFormation deletes the resource. For all other stack operations, + * such as stack deletion, CloudFormation retains the resource and its contents. The result is that new, empty, and unused resources are deleted, + * while in-use resources and their data are retained. + */ + RETAIN_EXCEPT_ON_CREATE = 'RetainExceptOnCreate', + /** * For resources that support snapshots (AWS::EC2::Volume, AWS::ElastiCache::CacheCluster, AWS::ElastiCache::ReplicationGroup, * AWS::RDS::DBInstance, AWS::RDS::DBCluster, and AWS::Redshift::Cluster), AWS CloudFormation creates a snapshot for the diff --git a/packages/aws-cdk-lib/core/lib/cfn-resource.ts b/packages/aws-cdk-lib/core/lib/cfn-resource.ts index b8b1733f016fc..43afeb56cd804 100644 --- a/packages/aws-cdk-lib/core/lib/cfn-resource.ts +++ b/packages/aws-cdk-lib/core/lib/cfn-resource.ts @@ -120,14 +120,22 @@ export class CfnResource extends CfnRefElement { policy = policy || options.default || RemovalPolicy.RETAIN; let deletionPolicy; + let updateReplacePolicy; switch (policy) { case RemovalPolicy.DESTROY: deletionPolicy = CfnDeletionPolicy.DELETE; + updateReplacePolicy = CfnDeletionPolicy.DELETE; break; case RemovalPolicy.RETAIN: deletionPolicy = CfnDeletionPolicy.RETAIN; + updateReplacePolicy = CfnDeletionPolicy.RETAIN; + break; + + case RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE: + deletionPolicy = CfnDeletionPolicy.RETAIN_EXCEPT_ON_CREATE; + updateReplacePolicy = CfnDeletionPolicy.RETAIN; break; case RemovalPolicy.SNAPSHOT: @@ -153,6 +161,7 @@ export class CfnResource extends CfnRefElement { } deletionPolicy = CfnDeletionPolicy.SNAPSHOT; + updateReplacePolicy = CfnDeletionPolicy.SNAPSHOT; break; default: @@ -161,7 +170,7 @@ export class CfnResource extends CfnRefElement { this.cfnOptions.deletionPolicy = deletionPolicy; if (options.applyToUpdateReplacePolicy !== false) { - this.cfnOptions.updateReplacePolicy = deletionPolicy; + this.cfnOptions.updateReplacePolicy = updateReplacePolicy; } } diff --git a/packages/aws-cdk-lib/core/lib/removal-policy.ts b/packages/aws-cdk-lib/core/lib/removal-policy.ts index 2252ed6471d3a..95466fdec6f0a 100644 --- a/packages/aws-cdk-lib/core/lib/removal-policy.ts +++ b/packages/aws-cdk-lib/core/lib/removal-policy.ts @@ -48,6 +48,21 @@ export enum RemovalPolicy { * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html#aws-attribute-deletionpolicy-options */ SNAPSHOT = 'snapshot', + + /** + * Resource will be retained when they are requested to be deleted during a stack delete request + * or need to be replaced due to a stack update request. + * Resource are not retained, if the creation is rolled back. + * + * The result is that new, empty, and unused resources are deleted, + * while in-use resources and their data are retained. + * + * This uses the 'RetainExceptOnCreate' DeletionPolicy, + * and the 'Retain' UpdateReplacePolicy, when `applyToUpdateReplacePolicy` is set. + * + * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-deletionpolicy.html#aws-attribute-deletionpolicy-options + */ + RETAIN_ON_UPDATE_OR_DELETE = 'retain-on-update-or-delete', } export interface RemovalPolicyOptions { diff --git a/packages/aws-cdk-lib/core/test/removal-policy.test.ts b/packages/aws-cdk-lib/core/test/removal-policy.test.ts new file mode 100644 index 0000000000000..72054bae37322 --- /dev/null +++ b/packages/aws-cdk-lib/core/test/removal-policy.test.ts @@ -0,0 +1,48 @@ +import { toCloudFormation } from './util'; +import { CfnResource, Stack, RemovalPolicy } from '../lib'; + +describe('removal policy', () => { + test.each([ + [RemovalPolicy.RETAIN, 'Retain'], + [RemovalPolicy.DESTROY, 'Delete'], + [RemovalPolicy.SNAPSHOT, 'Snapshot'], + [RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE, 'RetainExceptOnCreate'], + ])('should set correct DeletionPolicy for RemovalPolicy.%s', (removalPolicy: RemovalPolicy, deletionPolicy: string) => { + const stack = new Stack(); + + const resource = new CfnResource(stack, 'Resource', { type: 'MOCK' }); + resource.applyRemovalPolicy(removalPolicy); + + expect(toCloudFormation(stack)).toEqual({ + Resources: { + Resource: expect.objectContaining({ + Type: 'MOCK', + DeletionPolicy: deletionPolicy, + }), + }, + }); + }); + + test.each([ + [RemovalPolicy.RETAIN, 'Retain'], + [RemovalPolicy.DESTROY, 'Delete'], + [RemovalPolicy.SNAPSHOT, 'Snapshot'], + [RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE, 'Retain'], + ])('should set correct UpdateReplacePolicy for RemovalPolicy.%s', (removalPolicy: RemovalPolicy, updateReplacePolicy: string) => { + const stack = new Stack(); + + const resource = new CfnResource(stack, 'Resource', { type: 'MOCK' }); + resource.applyRemovalPolicy(removalPolicy, { + applyToUpdateReplacePolicy: true, + }); + + expect(toCloudFormation(stack)).toEqual({ + Resources: { + Resource: expect.objectContaining({ + Type: 'MOCK', + UpdateReplacePolicy: updateReplacePolicy, + }), + }, + }); + }); +}); \ No newline at end of file