From 70f9e4314b2a1556acde1c4437c3d8ae0e47e629 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Tue, 26 Nov 2024 04:01:04 +0000 Subject: [PATCH 01/20] 1.0 --- .../aws-cdk-lib/core/lib/removal-policys.ts | 137 ++++++++++++ .../core/test/removal-policys.test.ts | 207 ++++++++++++++++++ 2 files changed, 344 insertions(+) create mode 100644 packages/aws-cdk-lib/core/lib/removal-policys.ts create mode 100644 packages/aws-cdk-lib/core/test/removal-policys.test.ts diff --git a/packages/aws-cdk-lib/core/lib/removal-policys.ts b/packages/aws-cdk-lib/core/lib/removal-policys.ts new file mode 100644 index 0000000000000..34af289141300 --- /dev/null +++ b/packages/aws-cdk-lib/core/lib/removal-policys.ts @@ -0,0 +1,137 @@ +import { IConstruct } from 'constructs'; +import { Aspects, IAspect } from './aspect'; +import { CfnResource } from './cfn-resource'; +import { RemovalPolicy } from './removal-policy'; + +/** + * Properties for applying a removal policy + */ +export interface RemovalPolicyProps { + /** + * Apply the removal policy only to specific resource types. + * You can specify either the CloudFormation resource type (e.g., 'AWS::S3::Bucket') + * or the CDK resource class (e.g., CfnBucket). + * @default - apply to all resources + */ + readonly applyToResourceTypes?: Array; + + /** + * Exclude specific resource types from the removal policy. + * You can specify either the CloudFormation resource type (e.g., 'AWS::S3::Bucket') + * or the CDK resource class (e.g., CfnBucket). + * @default - no exclusions + */ + readonly excludeResourceTypes?: Array; +} + +/** + * The RemovalPolicyAspect handles applying a removal policy to resources + */ +class RemovalPolicyAspect implements IAspect { + constructor( + private readonly policy: RemovalPolicy, + private readonly props: RemovalPolicyProps = {}, + ) {} + + private getResourceTypeFromClass(resourceClass: { new (...args: any[]): CfnResource }): string { + // Create a prototype instance to get the type without instantiating + const prototype = resourceClass.prototype; + if ('cfnResourceType' in prototype) { + return prototype.cfnResourceType; + } + // Fallback to checking constructor properties + const instance = Object.create(prototype); + return instance.constructor.CFN_RESOURCE_TYPE_NAME ?? ''; + } + + private matchesResourceType(resourceType: string, pattern: string | { new (...args: any[]): CfnResource }): boolean { + if (typeof pattern === 'string') { + return resourceType === pattern; + } + return resourceType === this.getResourceTypeFromClass(pattern); + } + + public visit(node: IConstruct): void { + if (!CfnResource.isCfnResource(node)) { + return; + } + + const cfnResource = node as CfnResource; + const resourceType = cfnResource.cfnResourceType; + + // Skip if resource type is excluded + if (this.props.excludeResourceTypes?.some(pattern => this.matchesResourceType(resourceType, pattern))) { + return; + } + + // Skip if specific resource types are specified and this one isn't included + if (this.props.applyToResourceTypes?.length && + !this.props.applyToResourceTypes.some(pattern => this.matchesResourceType(resourceType, pattern))) { + return; + } + + // Apply the removal policy + cfnResource.applyRemovalPolicy(this.policy); + } +} + +/** + * Manages removal policies for all resources within a construct scope + */ +export class RemovalPolicys { + /** + * Returns the removal policies API for the given scope + * @param scope The scope + */ + public static of(scope: IConstruct): RemovalPolicys { + return new RemovalPolicys(scope); + } + + private constructor(private readonly scope: IConstruct) {} + + /** + * Apply a removal policy to all resources within this scope + * + * @param policy The removal policy to apply + * @param props Configuration options + */ + public apply(policy: RemovalPolicy, props: RemovalPolicyProps = {}) { + Aspects.of(this.scope).add(new RemovalPolicyAspect(policy, props)); + } + + /** + * Apply DESTROY removal policy to all resources within this scope + * + * @param props Configuration options + */ + public destroy(props: RemovalPolicyProps = {}) { + this.apply(RemovalPolicy.DESTROY, props); + } + + /** + * Apply RETAIN removal policy to all resources within this scope + * + * @param props Configuration options + */ + public retain(props: RemovalPolicyProps = {}) { + this.apply(RemovalPolicy.RETAIN, props); + } + + /** + * Apply SNAPSHOT removal policy to all resources within this scope + * + * @param props Configuration options + */ + public snapshot(props: RemovalPolicyProps = {}) { + this.apply(RemovalPolicy.SNAPSHOT, props); + } + + /** + * Apply RETAIN_ON_UPDATE_OR_DELETE removal policy to all resources within this scope + * + * @param props Configuration options + */ + public retainOnUpdateOrDelete(props: RemovalPolicyProps = {}) { + this.apply(RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE, props); + } +} diff --git a/packages/aws-cdk-lib/core/test/removal-policys.test.ts b/packages/aws-cdk-lib/core/test/removal-policys.test.ts new file mode 100644 index 0000000000000..b574ab58aecbb --- /dev/null +++ b/packages/aws-cdk-lib/core/test/removal-policys.test.ts @@ -0,0 +1,207 @@ +import { Construct } from 'constructs'; +import { CfnResource, RemovalPolicy, Stack, Aspects } from '../lib'; +import { synthesize } from '../lib/private/synthesis'; +import { RemovalPolicys } from '../lib/removal-policys'; + +class TestResource extends CfnResource { + public static readonly CFN_RESOURCE_TYPE_NAME = 'AWS::Test::Resource'; + + constructor(scope: Construct, id: string) { + super(scope, id, { + type: TestResource.CFN_RESOURCE_TYPE_NAME, + }); + } +} + +class TestBucketResource extends CfnResource { + public static readonly CFN_RESOURCE_TYPE_NAME = 'AWS::S3::Bucket'; + + constructor(scope: Construct, id: string) { + super(scope, id, { + type: TestBucketResource.CFN_RESOURCE_TYPE_NAME, + }); + } +} + +class TestTableResource extends CfnResource { + public static readonly CFN_RESOURCE_TYPE_NAME = 'AWS::DynamoDB::Table'; + + constructor(scope: Construct, id: string) { + super(scope, id, { + type: TestTableResource.CFN_RESOURCE_TYPE_NAME, + }); + } +} + +describe('removal-policys', () => { + test('applies removal policy to all resources in scope', () => { + // GIVEN + const stack = new Stack(); + const parent = new Construct(stack, 'Parent'); + const resource1 = new TestResource(parent, 'Resource1'); + const resource2 = new TestResource(parent, 'Resource2'); + + // WHEN + RemovalPolicys.of(parent).destroy(); + + // THEN + synthesize(stack); + expect(resource1.cfnOptions.deletionPolicy).toBe('Delete'); + expect(resource2.cfnOptions.deletionPolicy).toBe('Delete'); + }); + + test('applies removal policy only to specified resource types using strings', () => { + // GIVEN + const stack = new Stack(); + const parent = new Construct(stack, 'Parent'); + const bucket = new TestBucketResource(parent, 'Bucket'); + const table = new TestTableResource(parent, 'Table'); + const resource = new TestResource(parent, 'Resource'); + + // WHEN + RemovalPolicys.of(parent).retain({ + applyToResourceTypes: ['AWS::S3::Bucket', 'AWS::DynamoDB::Table'], + }); + + // THEN + synthesize(stack); + expect(bucket.cfnOptions.deletionPolicy).toBe('Retain'); + expect(table.cfnOptions.deletionPolicy).toBe('Retain'); + expect(resource.cfnOptions.deletionPolicy).toBeUndefined(); + }); + + test('applies removal policy only to specified resource types using classes', () => { + // GIVEN + const stack = new Stack(); + const parent = new Construct(stack, 'Parent'); + const bucket = new TestBucketResource(parent, 'Bucket'); + const table = new TestTableResource(parent, 'Table'); + const resource = new TestResource(parent, 'Resource'); + + // WHEN + RemovalPolicys.of(parent).retain({ + applyToResourceTypes: [TestBucketResource, TestTableResource], + }); + + // THEN + synthesize(stack); + expect(bucket.cfnOptions.deletionPolicy).toBe('Retain'); + expect(table.cfnOptions.deletionPolicy).toBe('Retain'); + expect(resource.cfnOptions.deletionPolicy).toBeUndefined(); + }); + + test('excludes specified resource types using strings', () => { + // GIVEN + const stack = new Stack(); + const parent = new Construct(stack, 'Parent'); + const bucket = new TestBucketResource(parent, 'Bucket'); + const table = new TestTableResource(parent, 'Table'); + const resource = new TestResource(parent, 'Resource'); + + // WHEN + RemovalPolicys.of(parent).snapshot({ + excludeResourceTypes: ['AWS::Test::Resource'], + }); + + // THEN + synthesize(stack); + expect(bucket.cfnOptions.deletionPolicy).toBe('Snapshot'); + expect(table.cfnOptions.deletionPolicy).toBe('Snapshot'); + expect(resource.cfnOptions.deletionPolicy).toBeUndefined(); + }); + + test('excludes specified resource types using classes', () => { + // GIVEN + const stack = new Stack(); + const parent = new Construct(stack, 'Parent'); + const bucket = new TestBucketResource(parent, 'Bucket'); + const table = new TestTableResource(parent, 'Table'); + const resource = new TestResource(parent, 'Resource'); + + // WHEN + RemovalPolicys.of(parent).snapshot({ + excludeResourceTypes: [TestResource], + }); + + // THEN + synthesize(stack); + expect(bucket.cfnOptions.deletionPolicy).toBe('Snapshot'); + expect(table.cfnOptions.deletionPolicy).toBe('Snapshot'); + expect(resource.cfnOptions.deletionPolicy).toBeUndefined(); + }); + + test('applies different removal policies', () => { + // GIVEN + const stack = new Stack(); + const destroy = new TestResource(stack, 'DestroyResource'); + const retain = new TestResource(stack, 'RetainResource'); + const snapshot = new TestResource(stack, 'SnapshotResource'); + const retainOnUpdate = new TestResource(stack, 'RetainOnUpdateResource'); + + // WHEN + RemovalPolicys.of(destroy).destroy(); + RemovalPolicys.of(retain).retain(); + RemovalPolicys.of(snapshot).snapshot(); + RemovalPolicys.of(retainOnUpdate).retainOnUpdateOrDelete(); + + // THEN + synthesize(stack); + expect(destroy.cfnOptions.deletionPolicy).toBe('Delete'); + expect(retain.cfnOptions.deletionPolicy).toBe('Retain'); + expect(snapshot.cfnOptions.deletionPolicy).toBe('Snapshot'); + expect(retainOnUpdate.cfnOptions.deletionPolicy).toBe('RetainExceptOnCreate'); + }); + + test('last applied removal policy takes precedence', () => { + // GIVEN + const stack = new Stack(); + const resource = new TestResource(stack, 'Resource'); + + // WHEN + RemovalPolicys.of(resource).destroy(); + RemovalPolicys.of(resource).retain(); + RemovalPolicys.of(resource).snapshot(); + + // THEN + synthesize(stack); + expect(resource.cfnOptions.deletionPolicy).toBe('Snapshot'); + }); + + test('child scope can override parent scope removal policy', () => { + // GIVEN + const stack = new Stack(); + const parent = new Construct(stack, 'Parent'); + const child = new Construct(parent, 'Child'); + const parentResource = new TestResource(parent, 'ParentResource'); + const childResource = new TestResource(child, 'ChildResource'); + + // WHEN + RemovalPolicys.of(parent).destroy(); + RemovalPolicys.of(child).retain(); + + // THEN + synthesize(stack); + expect(parentResource.cfnOptions.deletionPolicy).toBe('Delete'); + expect(childResource.cfnOptions.deletionPolicy).toBe('Retain'); + }); + + test('can mix string and class resource type specifications', () => { + // GIVEN + const stack = new Stack(); + const parent = new Construct(stack, 'Parent'); + const bucket = new TestBucketResource(parent, 'Bucket'); + const table = new TestTableResource(parent, 'Table'); + const resource = new TestResource(parent, 'Resource'); + + // WHEN + RemovalPolicys.of(parent).retain({ + applyToResourceTypes: [TestBucketResource, 'AWS::DynamoDB::Table'], + }); + + // THEN + synthesize(stack); + expect(bucket.cfnOptions.deletionPolicy).toBe('Retain'); + expect(table.cfnOptions.deletionPolicy).toBe('Retain'); + expect(resource.cfnOptions.deletionPolicy).toBeUndefined(); + }); +}); From 47a41f12f11a742f33eeee50da286ce8dfa15d1b Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Thu, 19 Dec 2024 09:29:57 +0000 Subject: [PATCH 02/20] integ first --- .../test/core/test/integ.removal-policys.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.ts diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.ts b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.ts new file mode 100644 index 0000000000000..7ba0fc066b42c --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.ts @@ -0,0 +1,41 @@ +import { App, Stack } from 'aws-cdk-lib'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import { RemovalPolicys } from 'aws-cdk-lib/core/lib/removal-policys'; + +const app = new App(); +const stack = new Stack(app, 'TestStack'); + +// Create resources +new s3.Bucket(stack, 'TestBucket'); +new dynamodb.Table(stack, 'TestTable', { + partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, +}); + +// Apply different removal policies to demonstrate functionality +const scope1 = new Stack(app, 'Scope1'); +new s3.Bucket(scope1, 'Bucket1'); +new dynamodb.Table(scope1, 'Table1', { + partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, +}); +RemovalPolicys.of(scope1).destroy(); + +const scope2 = new Stack(app, 'Scope2'); +const bucket2 = new s3.Bucket(scope2, 'Bucket2'); +const table2 = new dynamodb.Table(scope2, 'Table2', { + partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, +}); +RemovalPolicys.of(scope2).retain({ + applyToResourceTypes: ['AWS::S3::Bucket'], +}); + +const scope3 = new Stack(app, 'Scope3'); +new s3.Bucket(scope3, 'Bucket3'); +new dynamodb.Table(scope3, 'Table3', { + partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, +}); +RemovalPolicys.of(scope3).snapshot({ + excludeResourceTypes: ['AWS::DynamoDB::Table'], +}); + +app.synth(); From 5d76562d291cb7e5bcd3d165287575d01b21e4d8 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:33:39 +0000 Subject: [PATCH 03/20] integ --- ...efaultTestDeployAssertF0ACFC0A.assets.json | 19 ++ ...aultTestDeployAssertF0ACFC0A.template.json | 36 ++++ .../TestStack.assets.json | 19 ++ .../TestStack.template.json | 71 +++++++ .../integ.removal-policys.js.snapshot/cdk.out | 1 + .../integ.json | 12 ++ .../manifest.json | 125 ++++++++++++ .../tree.json | 192 ++++++++++++++++++ .../test/core/test/integ.removal-policys.ts | 35 +--- packages/aws-cdk-lib/core/lib/index.ts | 1 + .../aws-cdk-lib/core/lib/removal-policys.ts | 36 +--- 11 files changed, 495 insertions(+), 52 deletions(-) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/integ.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/tree.json diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.assets.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.assets.json new file mode 100644 index 0000000000000..1f9c654c913b5 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.assets.json @@ -0,0 +1,19 @@ +{ + "version": "38.0.1", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.template.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.assets.json new file mode 100644 index 0000000000000..67d9c870ac571 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.assets.json @@ -0,0 +1,19 @@ +{ + "version": "38.0.1", + "files": { + "54e69c890c5de1029ae9f50f794fb043559a80e79bba4440c37425823d80a188": { + "source": { + "path": "TestStack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "54e69c890c5de1029ae9f50f794fb043559a80e79bba4440c37425823d80a188.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.template.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.template.json new file mode 100644 index 0000000000000..b3152b2a642c3 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.template.json @@ -0,0 +1,71 @@ +{ + "Resources": { + "TestBucket560B80BC": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "TestTable5769773A": { + "Type": "AWS::DynamoDB::Table", + "Properties": { + "AttributeDefinitions": [ + { + "AttributeName": "id", + "AttributeType": "S" + } + ], + "KeySchema": [ + { + "AttributeName": "id", + "KeyType": "HASH" + } + ], + "ProvisionedThroughput": { + "ReadCapacityUnits": 5, + "WriteCapacityUnits": 5 + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "TestUser6A619381": { + "Type": "AWS::IAM::User", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/cdk.out new file mode 100644 index 0000000000000..c6e612584e352 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"38.0.1"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/integ.json new file mode 100644 index 0000000000000..4075b7e151eac --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "38.0.1", + "testCases": { + "RemovalPoliciesTest/DefaultTest": { + "stacks": [ + "TestStack" + ], + "assertionStack": "RemovalPoliciesTest/DefaultTest/DeployAssert", + "assertionStackName": "RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/manifest.json new file mode 100644 index 0000000000000..2c9b0fb202b47 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/manifest.json @@ -0,0 +1,125 @@ +{ + "version": "38.0.1", + "artifacts": { + "TestStack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "TestStack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "TestStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "TestStack.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/54e69c890c5de1029ae9f50f794fb043559a80e79bba4440c37425823d80a188.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "TestStack.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "TestStack.assets" + ], + "metadata": { + "/TestStack/TestBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TestBucket560B80BC" + } + ], + "/TestStack/TestTable/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TestTable5769773A" + } + ], + "/TestStack/TestUser/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "TestUser6A619381" + } + ], + "/TestStack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/TestStack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "TestStack" + }, + "RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.assets" + ], + "metadata": { + "/RemovalPoliciesTest/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/RemovalPoliciesTest/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "RemovalPoliciesTest/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/tree.json new file mode 100644 index 0000000000000..2474990be0e3e --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/tree.json @@ -0,0 +1,192 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "TestStack": { + "id": "TestStack", + "path": "TestStack", + "children": { + "TestBucket": { + "id": "TestBucket", + "path": "TestStack/TestBucket", + "children": { + "Resource": { + "id": "Resource", + "path": "TestStack/TestBucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.CfnBucket", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.Bucket", + "version": "0.0.0" + } + }, + "TestTable": { + "id": "TestTable", + "path": "TestStack/TestTable", + "children": { + "Resource": { + "id": "Resource", + "path": "TestStack/TestTable/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::DynamoDB::Table", + "aws:cdk:cloudformation:props": { + "attributeDefinitions": [ + { + "attributeName": "id", + "attributeType": "S" + } + ], + "keySchema": [ + { + "attributeName": "id", + "keyType": "HASH" + } + ], + "provisionedThroughput": { + "readCapacityUnits": 5, + "writeCapacityUnits": 5 + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_dynamodb.CfnTable", + "version": "0.0.0" + } + }, + "ScalingRole": { + "id": "ScalingRole", + "path": "TestStack/TestTable/ScalingRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_dynamodb.Table", + "version": "0.0.0" + } + }, + "TestUser": { + "id": "TestUser", + "path": "TestStack/TestUser", + "children": { + "Resource": { + "id": "Resource", + "path": "TestStack/TestUser/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::User", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnUser", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.User", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "TestStack/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "TestStack/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "RemovalPoliciesTest": { + "id": "RemovalPoliciesTest", + "path": "RemovalPoliciesTest", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "RemovalPoliciesTest/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "RemovalPoliciesTest/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "RemovalPoliciesTest/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "RemovalPoliciesTest/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "RemovalPoliciesTest/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.4.2" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.ts b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.ts index 7ba0fc066b42c..d1a00b0b371a2 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.ts @@ -1,7 +1,8 @@ -import { App, Stack } from 'aws-cdk-lib'; +import { App, RemovalPolicys, Stack } from 'aws-cdk-lib'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as iam from 'aws-cdk-lib/aws-iam'; import * as s3 from 'aws-cdk-lib/aws-s3'; -import { RemovalPolicys } from 'aws-cdk-lib/core/lib/removal-policys'; +import * as integ from '@aws-cdk/integ-tests-alpha'; const app = new App(); const stack = new Stack(app, 'TestStack'); @@ -11,31 +12,11 @@ new s3.Bucket(stack, 'TestBucket'); new dynamodb.Table(stack, 'TestTable', { partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, }); +new iam.User(stack, 'TestUser'); // Apply different removal policies to demonstrate functionality -const scope1 = new Stack(app, 'Scope1'); -new s3.Bucket(scope1, 'Bucket1'); -new dynamodb.Table(scope1, 'Table1', { - partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, -}); -RemovalPolicys.of(scope1).destroy(); - -const scope2 = new Stack(app, 'Scope2'); -const bucket2 = new s3.Bucket(scope2, 'Bucket2'); -const table2 = new dynamodb.Table(scope2, 'Table2', { - partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, -}); -RemovalPolicys.of(scope2).retain({ - applyToResourceTypes: ['AWS::S3::Bucket'], -}); - -const scope3 = new Stack(app, 'Scope3'); -new s3.Bucket(scope3, 'Bucket3'); -new dynamodb.Table(scope3, 'Table3', { - partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, -}); -RemovalPolicys.of(scope3).snapshot({ - excludeResourceTypes: ['AWS::DynamoDB::Table'], -}); +RemovalPolicys.of(stack).destroy(); -app.synth(); +new integ.IntegTest(app, 'RemovalPoliciesTest', { + testCases: [stack], +}); \ No newline at end of file diff --git a/packages/aws-cdk-lib/core/lib/index.ts b/packages/aws-cdk-lib/core/lib/index.ts index b35e89c0e59e4..1877978c9af23 100644 --- a/packages/aws-cdk-lib/core/lib/index.ts +++ b/packages/aws-cdk-lib/core/lib/index.ts @@ -29,6 +29,7 @@ export * from './cfn-dynamic-reference'; export * from './cfn-tag'; export * from './cfn-json'; export * from './removal-policy'; +export * from './removal-policys'; export * from './arn'; export * from './duration'; export * from './expiration'; diff --git a/packages/aws-cdk-lib/core/lib/removal-policys.ts b/packages/aws-cdk-lib/core/lib/removal-policys.ts index 34af289141300..1d92e10049fd7 100644 --- a/packages/aws-cdk-lib/core/lib/removal-policys.ts +++ b/packages/aws-cdk-lib/core/lib/removal-policys.ts @@ -9,19 +9,17 @@ import { RemovalPolicy } from './removal-policy'; export interface RemovalPolicyProps { /** * Apply the removal policy only to specific resource types. - * You can specify either the CloudFormation resource type (e.g., 'AWS::S3::Bucket') - * or the CDK resource class (e.g., CfnBucket). + * Specify the CloudFormation resource type (e.g., 'AWS::S3::Bucket'). * @default - apply to all resources */ - readonly applyToResourceTypes?: Array; + readonly applyToResourceTypes?: string[]; /** * Exclude specific resource types from the removal policy. - * You can specify either the CloudFormation resource type (e.g., 'AWS::S3::Bucket') - * or the CDK resource class (e.g., CfnBucket). + * Specify the CloudFormation resource type (e.g., 'AWS::S3::Bucket'). * @default - no exclusions */ - readonly excludeResourceTypes?: Array; + readonly excludeResourceTypes?: string[]; } /** @@ -33,22 +31,8 @@ class RemovalPolicyAspect implements IAspect { private readonly props: RemovalPolicyProps = {}, ) {} - private getResourceTypeFromClass(resourceClass: { new (...args: any[]): CfnResource }): string { - // Create a prototype instance to get the type without instantiating - const prototype = resourceClass.prototype; - if ('cfnResourceType' in prototype) { - return prototype.cfnResourceType; - } - // Fallback to checking constructor properties - const instance = Object.create(prototype); - return instance.constructor.CFN_RESOURCE_TYPE_NAME ?? ''; - } - - private matchesResourceType(resourceType: string, pattern: string | { new (...args: any[]): CfnResource }): boolean { - if (typeof pattern === 'string') { - return resourceType === pattern; - } - return resourceType === this.getResourceTypeFromClass(pattern); + private matchesResourceType(resourceType: string, patterns?: string[]): boolean { + return patterns ? patterns.includes(resourceType) : false; } public visit(node: IConstruct): void { @@ -60,13 +44,15 @@ class RemovalPolicyAspect implements IAspect { const resourceType = cfnResource.cfnResourceType; // Skip if resource type is excluded - if (this.props.excludeResourceTypes?.some(pattern => this.matchesResourceType(resourceType, pattern))) { + if (this.matchesResourceType(resourceType, this.props.excludeResourceTypes)) { return; } // Skip if specific resource types are specified and this one isn't included - if (this.props.applyToResourceTypes?.length && - !this.props.applyToResourceTypes.some(pattern => this.matchesResourceType(resourceType, pattern))) { + if ( + this.props.applyToResourceTypes?.length && + !this.matchesResourceType(resourceType, this.props.applyToResourceTypes) + ) { return; } From 3770eb84242a5c495cc20854db0c4eb4c694f653 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:50:13 +0000 Subject: [PATCH 04/20] readme --- packages/aws-cdk-lib/core/README.md | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/packages/aws-cdk-lib/core/README.md b/packages/aws-cdk-lib/core/README.md index ecec3322f5351..7f72aa5cd3794 100644 --- a/packages/aws-cdk-lib/core/README.md +++ b/packages/aws-cdk-lib/core/README.md @@ -1650,4 +1650,43 @@ warning by the `id`. Annotations.of(this).acknowledgeWarning('IAM:Group:MaxPoliciesExceeded', 'Account has quota increased to 20'); ``` +## RemovalPolicys + +The `RemovalPolicys` class provides a convenient way to manage removal policies for AWS CDK resources within a construct scope. It allows you to apply removal policies to multiple resources at once, with options to include or exclude specific resource types. + +### Usage + +```typescript +import { RemovalPolicys } from 'aws-cdk-lib'; + +// Apply DESTROY policy to all resources in a scope +RemovalPolicys.of(scope).destroy(); + +// Apply RETAIN policy only to specific resource types +RemovalPolicys.of(scope).retain({ + applyToResourceTypes: ['AWS::S3::Bucket', 'AWS::DynamoDB::Table'], +}); + +// Apply SNAPSHOT policy excluding specific resource types +RemovalPolicys.of(scope).snapshot({ + excludeResourceTypes: ['AWS::Test::Resource'], +}); + +// Apply RETAIN_ON_UPDATE_OR_DELETE policy +RemovalPolicys.of(scope).retainOnUpdateOrDelete(); +``` + +### RemovalPolicys.of(scope) + +Creates a new instance of RemovalPolicys for the given scope. + +#### Methods + +- `apply(policy: RemovalPolicy, props?: RemovalPolicyProps)`: Apply a custom removal policy +- `destroy(props?: RemovalPolicyProps)`: Apply DESTROY removal policy +- `retain(props?: RemovalPolicyProps)`: Apply RETAIN removal policy +- `snapshot(props?: RemovalPolicyProps)`: Apply SNAPSHOT removal policy +- `retainOnUpdateOrDelete(props?: RemovalPolicyProps)`: Apply RETAIN_ON_UPDATE_OR_DELETE removal policy + + From d4a2db34cd5e4604b1c158bcfd507d953c0ca960 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:13:21 +0000 Subject: [PATCH 05/20] types --- .../aws-cdk-lib/core/lib/removal-policys.ts | 52 +++++++++++++++---- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/packages/aws-cdk-lib/core/lib/removal-policys.ts b/packages/aws-cdk-lib/core/lib/removal-policys.ts index 1d92e10049fd7..898fcdc144bff 100644 --- a/packages/aws-cdk-lib/core/lib/removal-policys.ts +++ b/packages/aws-cdk-lib/core/lib/removal-policys.ts @@ -9,17 +9,19 @@ import { RemovalPolicy } from './removal-policy'; export interface RemovalPolicyProps { /** * Apply the removal policy only to specific resource types. - * Specify the CloudFormation resource type (e.g., 'AWS::S3::Bucket'). + * Can be a CloudFormation resource type string (e.g., 'AWS::S3::Bucket') + * or a class that extends CfnResource (e.g., TestBucketResource). * @default - apply to all resources */ - readonly applyToResourceTypes?: string[]; + readonly applyToResourceTypes?: (string | typeof CfnResource)[]; /** * Exclude specific resource types from the removal policy. - * Specify the CloudFormation resource type (e.g., 'AWS::S3::Bucket'). + * Can be a CloudFormation resource type string (e.g., 'AWS::S3::Bucket') + * or a class that extends CfnResource. * @default - no exclusions */ - readonly excludeResourceTypes?: string[]; + readonly excludeResourceTypes?: (string | typeof CfnResource)[]; } /** @@ -31,8 +33,31 @@ class RemovalPolicyAspect implements IAspect { private readonly props: RemovalPolicyProps = {}, ) {} - private matchesResourceType(resourceType: string, patterns?: string[]): boolean { - return patterns ? patterns.includes(resourceType) : false; + /** + * Converts pattern (string or class) to resource type string + */ + private convertPatternToResourceType(pattern: string | typeof CfnResource): string { + if (typeof pattern === 'string') { + return pattern; + } else { + const cfnType = (pattern as any).CFN_RESOURCE_TYPE_NAME; + if (typeof cfnType !== 'string') { + throw new Error(`Class ${pattern.name} must have a static CFN_RESOURCE_TYPE_NAME property.`); + } + return cfnType; + } + } + + /** + * Checks if the given resource type matches any of the patterns + */ + private resourceTypeMatchesPatterns(resourceType: string, patterns?: (string | typeof CfnResource)[]): boolean { + if (!patterns || patterns.length === 0) { + return false; + } + + const resourceTypeStrings = patterns.map(pattern => this.convertPatternToResourceType(pattern)); + return resourceTypeStrings.includes(resourceType); } public visit(node: IConstruct): void { @@ -44,20 +69,27 @@ class RemovalPolicyAspect implements IAspect { const resourceType = cfnResource.cfnResourceType; // Skip if resource type is excluded - if (this.matchesResourceType(resourceType, this.props.excludeResourceTypes)) { + if (this.resourceTypeMatchesPatterns(resourceType, this.props.excludeResourceTypes)) { return; } // Skip if specific resource types are specified and this one isn't included if ( - this.props.applyToResourceTypes?.length && - !this.matchesResourceType(resourceType, this.props.applyToResourceTypes) + this.props.applyToResourceTypes && + this.props.applyToResourceTypes.length > 0 && + !this.resourceTypeMatchesPatterns(resourceType, this.props.applyToResourceTypes) ) { return; } // Apply the removal policy - cfnResource.applyRemovalPolicy(this.policy); + try { + cfnResource.applyRemovalPolicy(this.policy); + } catch (error) { + // 例えば、Snapshot がサポートされていないリソースに対して適用しようとした場合 + // エラーメッセージを詳細にする + throw new Error(`Failed to apply removal policy to resource type ${resourceType}: ${error.message}`); + } } } From 2437d3cc19b4676ebafb5b9c7e9284244010b52d Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:03:21 +0000 Subject: [PATCH 06/20] exit resource --- packages/aws-cdk-lib/core/README.md | 22 +++++++++++++ .../aws-cdk-lib/core/lib/removal-policys.ts | 20 +++++++++--- .../core/test/removal-policys.test.ts | 32 +++++++++++-------- 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/packages/aws-cdk-lib/core/README.md b/packages/aws-cdk-lib/core/README.md index 7f72aa5cd3794..6dc16ad6379d5 100644 --- a/packages/aws-cdk-lib/core/README.md +++ b/packages/aws-cdk-lib/core/README.md @@ -1688,5 +1688,27 @@ Creates a new instance of RemovalPolicys for the given scope. - `snapshot(props?: RemovalPolicyProps)`: Apply SNAPSHOT removal policy - `retainOnUpdateOrDelete(props?: RemovalPolicyProps)`: Apply RETAIN_ON_UPDATE_OR_DELETE removal policy +#### `RemovalPolicyProps` Interface + +Additional configuration options for applying removal policies. + +- **`applyToResourceTypes?`**: _(optional)_ + Array of CloudFormation resource types (e.g., `'AWS::S3::Bucket'`) to which the removal policy should be applied. + Defaults to applying to all resources. + +- **`excludeResourceTypes?`**: _(optional)_ + Array of CloudFormation resource types to exclude from applying the removal policy. + Defaults to no exclusions. + +- **`overwrite?`**: _(optional)_ + If `true`, the removal policy will overwrite any existing policy already set on the resource. Defaults to `false`. + +#### Behavior Summary + +- When `overwrite` is `false` (default): + - Existing `removalPolicy` set by the user is preserved. The aspect will skip applying the policy to such resources. + +- When `overwrite` is `true`: + - The existing `removalPolicy` is ignored, and the specified policy is applied unconditionally. diff --git a/packages/aws-cdk-lib/core/lib/removal-policys.ts b/packages/aws-cdk-lib/core/lib/removal-policys.ts index 898fcdc144bff..c4a534547898a 100644 --- a/packages/aws-cdk-lib/core/lib/removal-policys.ts +++ b/packages/aws-cdk-lib/core/lib/removal-policys.ts @@ -22,6 +22,14 @@ export interface RemovalPolicyProps { * @default - no exclusions */ readonly excludeResourceTypes?: (string | typeof CfnResource)[]; + + /** + * If true, overwrite any user-specified removal policy that has been previously set. + * This means even if the user has already called `applyRemovalPolicy()` on the resource, + * this aspect will override it. + * @default false - do not overwrite user-specified policies + */ + readonly overwrite?: boolean; } /** @@ -68,15 +76,19 @@ class RemovalPolicyAspect implements IAspect { const cfnResource = node as CfnResource; const resourceType = cfnResource.cfnResourceType; - // Skip if resource type is excluded + const userAlreadySetPolicy = cfnResource.cfnOptions.deletionPolicy !== undefined || + cfnResource.cfnOptions.updateReplacePolicy !== undefined; + + if (!this.props.overwrite && userAlreadySetPolicy) { + return; + } + if (this.resourceTypeMatchesPatterns(resourceType, this.props.excludeResourceTypes)) { return; } - // Skip if specific resource types are specified and this one isn't included if ( - this.props.applyToResourceTypes && - this.props.applyToResourceTypes.length > 0 && + this.props.applyToResourceTypes?.length && !this.resourceTypeMatchesPatterns(resourceType, this.props.applyToResourceTypes) ) { return; diff --git a/packages/aws-cdk-lib/core/test/removal-policys.test.ts b/packages/aws-cdk-lib/core/test/removal-policys.test.ts index b574ab58aecbb..c75a6d7140305 100644 --- a/packages/aws-cdk-lib/core/test/removal-policys.test.ts +++ b/packages/aws-cdk-lib/core/test/removal-policys.test.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { CfnResource, RemovalPolicy, Stack, Aspects } from '../lib'; +import { CfnResource, CfnDeletionPolicy, Stack } from '../lib'; import { synthesize } from '../lib/private/synthesis'; import { RemovalPolicys } from '../lib/removal-policys'; @@ -159,10 +159,14 @@ describe('removal-policys', () => { // WHEN RemovalPolicys.of(resource).destroy(); + synthesize(stack); + expect(resource.cfnOptions.deletionPolicy).toBe('Delete'); + RemovalPolicys.of(resource).retain(); - RemovalPolicys.of(resource).snapshot(); + synthesize(stack); + expect(resource.cfnOptions.deletionPolicy).toBe('Delete'); - // THEN + RemovalPolicys.of(resource).snapshot({ overwrite: true }); synthesize(stack); expect(resource.cfnOptions.deletionPolicy).toBe('Snapshot'); }); @@ -182,26 +186,28 @@ describe('removal-policys', () => { // THEN synthesize(stack); expect(parentResource.cfnOptions.deletionPolicy).toBe('Delete'); + expect(childResource.cfnOptions.deletionPolicy).toBe('Delete'); + + RemovalPolicys.of(child).retain({ overwrite: true }); + synthesize(stack); expect(childResource.cfnOptions.deletionPolicy).toBe('Retain'); }); - test('can mix string and class resource type specifications', () => { + test('exist removalPolicy', () => { // GIVEN const stack = new Stack(); const parent = new Construct(stack, 'Parent'); const bucket = new TestBucketResource(parent, 'Bucket'); - const table = new TestTableResource(parent, 'Table'); - const resource = new TestResource(parent, 'Resource'); + bucket.cfnOptions.deletionPolicy = CfnDeletionPolicy.RETAIN; - // WHEN - RemovalPolicys.of(parent).retain({ - applyToResourceTypes: [TestBucketResource, 'AWS::DynamoDB::Table'], - }); + synthesize(stack); + expect(bucket.cfnOptions.deletionPolicy).toBe('Retain'); + + const table = new TestTableResource(parent, 'Table'); + RemovalPolicys.of(parent).retainOnUpdateOrDelete(); - // THEN synthesize(stack); expect(bucket.cfnOptions.deletionPolicy).toBe('Retain'); - expect(table.cfnOptions.deletionPolicy).toBe('Retain'); - expect(resource.cfnOptions.deletionPolicy).toBeUndefined(); + expect(table.cfnOptions.deletionPolicy).toBe('RetainExceptOnCreate'); }); }); From 3ca49c8e645996a965841e9768a4032b3c25ee09 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Fri, 20 Dec 2024 05:24:01 +0000 Subject: [PATCH 07/20] type check --- packages/aws-cdk-lib/core/lib/removal-policys.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/aws-cdk-lib/core/lib/removal-policys.ts b/packages/aws-cdk-lib/core/lib/removal-policys.ts index c4a534547898a..1ca05b7bde330 100644 --- a/packages/aws-cdk-lib/core/lib/removal-policys.ts +++ b/packages/aws-cdk-lib/core/lib/removal-policys.ts @@ -98,9 +98,13 @@ class RemovalPolicyAspect implements IAspect { try { cfnResource.applyRemovalPolicy(this.policy); } catch (error) { - // 例えば、Snapshot がサポートされていないリソースに対して適用しようとした場合 - // エラーメッセージを詳細にする - throw new Error(`Failed to apply removal policy to resource type ${resourceType}: ${error.message}`); + // Check if the error is an instance of the built-in Error class + if (error instanceof Error) { + throw new Error(`Failed to apply removal policy to resource type ${resourceType}: ${error.message}`); + } else { + // If it's not an Error instance, convert it to a string for the message + throw new Error(`Failed to apply removal policy to resource type ${resourceType}: ${String(error)}`); + } } } } From e0db4c83da9f9edde2e974f01c1c8afbd407d455 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Fri, 20 Dec 2024 06:34:07 +0000 Subject: [PATCH 08/20] integ --- .../TestStack.assets.json | 4 ++-- .../TestStack.template.json | 8 ++++---- .../test/integ.removal-policys.js.snapshot/manifest.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.assets.json index 67d9c870ac571..ffb588969b729 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.assets.json @@ -1,7 +1,7 @@ { "version": "38.0.1", "files": { - "54e69c890c5de1029ae9f50f794fb043559a80e79bba4440c37425823d80a188": { + "728f2337a808b3a5ac83ceabdf43987b28de0c8b451766c4944281da2a516767": { "source": { "path": "TestStack.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "54e69c890c5de1029ae9f50f794fb043559a80e79bba4440c37425823d80a188.json", + "objectKey": "728f2337a808b3a5ac83ceabdf43987b28de0c8b451766c4944281da2a516767.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.template.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.template.json index b3152b2a642c3..e0eba203115ca 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.template.json @@ -2,8 +2,8 @@ "Resources": { "TestBucket560B80BC": { "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" }, "TestTable5769773A": { "Type": "AWS::DynamoDB::Table", @@ -25,8 +25,8 @@ "WriteCapacityUnits": 5 } }, - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" }, "TestUser6A619381": { "Type": "AWS::IAM::User", diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/manifest.json index 2c9b0fb202b47..38a869df9c42c 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/54e69c890c5de1029ae9f50f794fb043559a80e79bba4440c37425823d80a188.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/728f2337a808b3a5ac83ceabdf43987b28de0c8b451766c4944281da2a516767.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ From 42b82fd3dc92740b0b553082e15d797120c2419b Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Sat, 21 Dec 2024 10:47:41 +0000 Subject: [PATCH 09/20] rename --- .../test/{integ.removal-policys.ts => integ.removal-policies.ts} | 0 .../core/lib/{removal-policys.ts => removal-policies.ts} | 0 .../test/{removal-policys.test.ts => removal-policies.test.ts} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename packages/@aws-cdk-testing/framework-integ/test/core/test/{integ.removal-policys.ts => integ.removal-policies.ts} (100%) rename packages/aws-cdk-lib/core/lib/{removal-policys.ts => removal-policies.ts} (100%) rename packages/aws-cdk-lib/core/test/{removal-policys.test.ts => removal-policies.test.ts} (100%) diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.ts b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.ts similarity index 100% rename from packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.ts rename to packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.ts diff --git a/packages/aws-cdk-lib/core/lib/removal-policys.ts b/packages/aws-cdk-lib/core/lib/removal-policies.ts similarity index 100% rename from packages/aws-cdk-lib/core/lib/removal-policys.ts rename to packages/aws-cdk-lib/core/lib/removal-policies.ts diff --git a/packages/aws-cdk-lib/core/test/removal-policys.test.ts b/packages/aws-cdk-lib/core/test/removal-policies.test.ts similarity index 100% rename from packages/aws-cdk-lib/core/test/removal-policys.test.ts rename to packages/aws-cdk-lib/core/test/removal-policies.test.ts From 710c35eb5ad5ce0fd180a35ec480f6a847acea45 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Sat, 21 Dec 2024 10:52:32 +0000 Subject: [PATCH 10/20] RemovalPolicys to RemovalPolicies --- .../test/core/test/integ.removal-policies.ts | 4 +-- packages/aws-cdk-lib/core/lib/index.ts | 2 +- .../aws-cdk-lib/core/lib/removal-policies.ts | 6 ++-- .../core/test/removal-policies.test.ts | 34 +++++++++---------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.ts b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.ts index d1a00b0b371a2..3c2d846d0bfcf 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.ts @@ -1,4 +1,4 @@ -import { App, RemovalPolicys, Stack } from 'aws-cdk-lib'; +import { App, RemovalPolicies, Stack } from 'aws-cdk-lib'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as s3 from 'aws-cdk-lib/aws-s3'; @@ -15,7 +15,7 @@ new dynamodb.Table(stack, 'TestTable', { new iam.User(stack, 'TestUser'); // Apply different removal policies to demonstrate functionality -RemovalPolicys.of(stack).destroy(); +RemovalPolicies.of(stack).destroy(); new integ.IntegTest(app, 'RemovalPoliciesTest', { testCases: [stack], diff --git a/packages/aws-cdk-lib/core/lib/index.ts b/packages/aws-cdk-lib/core/lib/index.ts index 1877978c9af23..ac6ec20770bc2 100644 --- a/packages/aws-cdk-lib/core/lib/index.ts +++ b/packages/aws-cdk-lib/core/lib/index.ts @@ -29,7 +29,7 @@ export * from './cfn-dynamic-reference'; export * from './cfn-tag'; export * from './cfn-json'; export * from './removal-policy'; -export * from './removal-policys'; +export * from './removal-policies'; export * from './arn'; export * from './duration'; export * from './expiration'; diff --git a/packages/aws-cdk-lib/core/lib/removal-policies.ts b/packages/aws-cdk-lib/core/lib/removal-policies.ts index 1ca05b7bde330..5665e3dd1690c 100644 --- a/packages/aws-cdk-lib/core/lib/removal-policies.ts +++ b/packages/aws-cdk-lib/core/lib/removal-policies.ts @@ -112,13 +112,13 @@ class RemovalPolicyAspect implements IAspect { /** * Manages removal policies for all resources within a construct scope */ -export class RemovalPolicys { +export class RemovalPolicies { /** * Returns the removal policies API for the given scope * @param scope The scope */ - public static of(scope: IConstruct): RemovalPolicys { - return new RemovalPolicys(scope); + public static of(scope: IConstruct): RemovalPolicies { + return new RemovalPolicies(scope); } private constructor(private readonly scope: IConstruct) {} diff --git a/packages/aws-cdk-lib/core/test/removal-policies.test.ts b/packages/aws-cdk-lib/core/test/removal-policies.test.ts index c75a6d7140305..38a95fe05015e 100644 --- a/packages/aws-cdk-lib/core/test/removal-policies.test.ts +++ b/packages/aws-cdk-lib/core/test/removal-policies.test.ts @@ -1,7 +1,7 @@ import { Construct } from 'constructs'; import { CfnResource, CfnDeletionPolicy, Stack } from '../lib'; import { synthesize } from '../lib/private/synthesis'; -import { RemovalPolicys } from '../lib/removal-policys'; +import { RemovalPolicies } from '../lib/removal-policies'; class TestResource extends CfnResource { public static readonly CFN_RESOURCE_TYPE_NAME = 'AWS::Test::Resource'; @@ -42,7 +42,7 @@ describe('removal-policys', () => { const resource2 = new TestResource(parent, 'Resource2'); // WHEN - RemovalPolicys.of(parent).destroy(); + RemovalPolicies.of(parent).destroy(); // THEN synthesize(stack); @@ -59,7 +59,7 @@ describe('removal-policys', () => { const resource = new TestResource(parent, 'Resource'); // WHEN - RemovalPolicys.of(parent).retain({ + RemovalPolicies.of(parent).retain({ applyToResourceTypes: ['AWS::S3::Bucket', 'AWS::DynamoDB::Table'], }); @@ -79,7 +79,7 @@ describe('removal-policys', () => { const resource = new TestResource(parent, 'Resource'); // WHEN - RemovalPolicys.of(parent).retain({ + RemovalPolicies.of(parent).retain({ applyToResourceTypes: [TestBucketResource, TestTableResource], }); @@ -99,7 +99,7 @@ describe('removal-policys', () => { const resource = new TestResource(parent, 'Resource'); // WHEN - RemovalPolicys.of(parent).snapshot({ + RemovalPolicies.of(parent).snapshot({ excludeResourceTypes: ['AWS::Test::Resource'], }); @@ -119,7 +119,7 @@ describe('removal-policys', () => { const resource = new TestResource(parent, 'Resource'); // WHEN - RemovalPolicys.of(parent).snapshot({ + RemovalPolicies.of(parent).snapshot({ excludeResourceTypes: [TestResource], }); @@ -139,10 +139,10 @@ describe('removal-policys', () => { const retainOnUpdate = new TestResource(stack, 'RetainOnUpdateResource'); // WHEN - RemovalPolicys.of(destroy).destroy(); - RemovalPolicys.of(retain).retain(); - RemovalPolicys.of(snapshot).snapshot(); - RemovalPolicys.of(retainOnUpdate).retainOnUpdateOrDelete(); + RemovalPolicies.of(destroy).destroy(); + RemovalPolicies.of(retain).retain(); + RemovalPolicies.of(snapshot).snapshot(); + RemovalPolicies.of(retainOnUpdate).retainOnUpdateOrDelete(); // THEN synthesize(stack); @@ -158,15 +158,15 @@ describe('removal-policys', () => { const resource = new TestResource(stack, 'Resource'); // WHEN - RemovalPolicys.of(resource).destroy(); + RemovalPolicies.of(resource).destroy(); synthesize(stack); expect(resource.cfnOptions.deletionPolicy).toBe('Delete'); - RemovalPolicys.of(resource).retain(); + RemovalPolicies.of(resource).retain(); synthesize(stack); expect(resource.cfnOptions.deletionPolicy).toBe('Delete'); - RemovalPolicys.of(resource).snapshot({ overwrite: true }); + RemovalPolicies.of(resource).snapshot({ overwrite: true }); synthesize(stack); expect(resource.cfnOptions.deletionPolicy).toBe('Snapshot'); }); @@ -180,15 +180,15 @@ describe('removal-policys', () => { const childResource = new TestResource(child, 'ChildResource'); // WHEN - RemovalPolicys.of(parent).destroy(); - RemovalPolicys.of(child).retain(); + RemovalPolicies.of(parent).destroy(); + RemovalPolicies.of(child).retain(); // THEN synthesize(stack); expect(parentResource.cfnOptions.deletionPolicy).toBe('Delete'); expect(childResource.cfnOptions.deletionPolicy).toBe('Delete'); - RemovalPolicys.of(child).retain({ overwrite: true }); + RemovalPolicies.of(child).retain({ overwrite: true }); synthesize(stack); expect(childResource.cfnOptions.deletionPolicy).toBe('Retain'); }); @@ -204,7 +204,7 @@ describe('removal-policys', () => { expect(bucket.cfnOptions.deletionPolicy).toBe('Retain'); const table = new TestTableResource(parent, 'Table'); - RemovalPolicys.of(parent).retainOnUpdateOrDelete(); + RemovalPolicies.of(parent).retainOnUpdateOrDelete(); synthesize(stack); expect(bucket.cfnOptions.deletionPolicy).toBe('Retain'); From 2d90ae740eaf9d9715f93811039f552b0abbb6a9 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Sat, 21 Dec 2024 11:13:08 +0000 Subject: [PATCH 11/20] integ --- ...RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.assets.json | 0 ...movalPoliciesTestDefaultTestDeployAssertF0ACFC0A.template.json | 0 .../TestStack.assets.json | 0 .../TestStack.template.json | 0 .../cdk.out | 0 .../integ.json | 0 .../manifest.json | 0 .../tree.json | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename packages/@aws-cdk-testing/framework-integ/test/core/test/{integ.removal-policys.js.snapshot => integ.removal-policies.js.snapshot}/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.assets.json (100%) rename packages/@aws-cdk-testing/framework-integ/test/core/test/{integ.removal-policys.js.snapshot => integ.removal-policies.js.snapshot}/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.template.json (100%) rename packages/@aws-cdk-testing/framework-integ/test/core/test/{integ.removal-policys.js.snapshot => integ.removal-policies.js.snapshot}/TestStack.assets.json (100%) rename packages/@aws-cdk-testing/framework-integ/test/core/test/{integ.removal-policys.js.snapshot => integ.removal-policies.js.snapshot}/TestStack.template.json (100%) rename packages/@aws-cdk-testing/framework-integ/test/core/test/{integ.removal-policys.js.snapshot => integ.removal-policies.js.snapshot}/cdk.out (100%) rename packages/@aws-cdk-testing/framework-integ/test/core/test/{integ.removal-policys.js.snapshot => integ.removal-policies.js.snapshot}/integ.json (100%) rename packages/@aws-cdk-testing/framework-integ/test/core/test/{integ.removal-policys.js.snapshot => integ.removal-policies.js.snapshot}/manifest.json (100%) rename packages/@aws-cdk-testing/framework-integ/test/core/test/{integ.removal-policys.js.snapshot => integ.removal-policies.js.snapshot}/tree.json (100%) diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.assets.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.assets.json similarity index 100% rename from packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.assets.json rename to packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.assets.json diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.template.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.template.json similarity index 100% rename from packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.template.json rename to packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/RemovalPoliciesTestDefaultTestDeployAssertF0ACFC0A.template.json diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.assets.json similarity index 100% rename from packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.assets.json rename to packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.assets.json diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.template.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.template.json similarity index 100% rename from packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/TestStack.template.json rename to packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.template.json diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/cdk.out similarity index 100% rename from packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/cdk.out rename to packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/cdk.out diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/integ.json similarity index 100% rename from packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/integ.json rename to packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/integ.json diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/manifest.json similarity index 100% rename from packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/manifest.json rename to packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/manifest.json diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/tree.json similarity index 100% rename from packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policys.js.snapshot/tree.json rename to packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/tree.json From f8d17342b79d2f72c00f7ef90793fe415c89df83 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Sat, 21 Dec 2024 11:26:37 +0000 Subject: [PATCH 12/20] enum like class --- packages/aws-cdk-lib/core/README.md | 9 +++-- .../aws-cdk-lib/core/lib/removal-policies.ts | 35 +++++-------------- .../core/test/removal-policies.test.ts | 18 +++++++--- 3 files changed, 30 insertions(+), 32 deletions(-) diff --git a/packages/aws-cdk-lib/core/README.md b/packages/aws-cdk-lib/core/README.md index 6dc16ad6379d5..a9292494c3496 100644 --- a/packages/aws-cdk-lib/core/README.md +++ b/packages/aws-cdk-lib/core/README.md @@ -1663,10 +1663,15 @@ import { RemovalPolicys } from 'aws-cdk-lib'; RemovalPolicys.of(scope).destroy(); // Apply RETAIN policy only to specific resource types -RemovalPolicys.of(scope).retain({ - applyToResourceTypes: ['AWS::S3::Bucket', 'AWS::DynamoDB::Table'], +RemovalPolicies.of(parent).retain({ + applyToResourceTypes: [ + 'AWS::DynamoDB::Table', + bucket.cfnResourceType, // 'AWS::S3::Bucket' + ], }); + + // Apply SNAPSHOT policy excluding specific resource types RemovalPolicys.of(scope).snapshot({ excludeResourceTypes: ['AWS::Test::Resource'], diff --git a/packages/aws-cdk-lib/core/lib/removal-policies.ts b/packages/aws-cdk-lib/core/lib/removal-policies.ts index 5665e3dd1690c..09e002e9c9602 100644 --- a/packages/aws-cdk-lib/core/lib/removal-policies.ts +++ b/packages/aws-cdk-lib/core/lib/removal-policies.ts @@ -9,19 +9,17 @@ import { RemovalPolicy } from './removal-policy'; export interface RemovalPolicyProps { /** * Apply the removal policy only to specific resource types. - * Can be a CloudFormation resource type string (e.g., 'AWS::S3::Bucket') - * or a class that extends CfnResource (e.g., TestBucketResource). + * Can be a CloudFormation resource type string (e.g., 'AWS::S3::Bucket'). * @default - apply to all resources */ - readonly applyToResourceTypes?: (string | typeof CfnResource)[]; + readonly applyToResourceTypes?: string[]; /** * Exclude specific resource types from the removal policy. - * Can be a CloudFormation resource type string (e.g., 'AWS::S3::Bucket') - * or a class that extends CfnResource. + * Can be a CloudFormation resource type string (e.g., 'AWS::S3::Bucket'). * @default - no exclusions */ - readonly excludeResourceTypes?: (string | typeof CfnResource)[]; + readonly excludeResourceTypes?: string[]; /** * If true, overwrite any user-specified removal policy that has been previously set. @@ -41,31 +39,15 @@ class RemovalPolicyAspect implements IAspect { private readonly props: RemovalPolicyProps = {}, ) {} - /** - * Converts pattern (string or class) to resource type string - */ - private convertPatternToResourceType(pattern: string | typeof CfnResource): string { - if (typeof pattern === 'string') { - return pattern; - } else { - const cfnType = (pattern as any).CFN_RESOURCE_TYPE_NAME; - if (typeof cfnType !== 'string') { - throw new Error(`Class ${pattern.name} must have a static CFN_RESOURCE_TYPE_NAME property.`); - } - return cfnType; - } - } - /** * Checks if the given resource type matches any of the patterns */ - private resourceTypeMatchesPatterns(resourceType: string, patterns?: (string | typeof CfnResource)[]): boolean { + private resourceTypeMatchesPatterns(resourceType: string, patterns?: string[]): boolean { if (!patterns || patterns.length === 0) { return false; } - const resourceTypeStrings = patterns.map(pattern => this.convertPatternToResourceType(pattern)); - return resourceTypeStrings.includes(resourceType); + return patterns.includes(resourceType); } public visit(node: IConstruct): void { @@ -76,8 +58,9 @@ class RemovalPolicyAspect implements IAspect { const cfnResource = node as CfnResource; const resourceType = cfnResource.cfnResourceType; - const userAlreadySetPolicy = cfnResource.cfnOptions.deletionPolicy !== undefined || - cfnResource.cfnOptions.updateReplacePolicy !== undefined; + const userAlreadySetPolicy = + cfnResource.cfnOptions.deletionPolicy !== undefined || + cfnResource.cfnOptions.updateReplacePolicy !== undefined; if (!this.props.overwrite && userAlreadySetPolicy) { return; diff --git a/packages/aws-cdk-lib/core/test/removal-policies.test.ts b/packages/aws-cdk-lib/core/test/removal-policies.test.ts index 38a95fe05015e..eb096c99119da 100644 --- a/packages/aws-cdk-lib/core/test/removal-policies.test.ts +++ b/packages/aws-cdk-lib/core/test/removal-policies.test.ts @@ -60,7 +60,10 @@ describe('removal-policys', () => { // WHEN RemovalPolicies.of(parent).retain({ - applyToResourceTypes: ['AWS::S3::Bucket', 'AWS::DynamoDB::Table'], + applyToResourceTypes: [ + bucket.cfnResourceType, // 'AWS::S3::Bucket' + TestTableResource.CFN_RESOURCE_TYPE_NAME, // 'AWS::DynamoDB::Table' + ], }); // THEN @@ -80,7 +83,10 @@ describe('removal-policys', () => { // WHEN RemovalPolicies.of(parent).retain({ - applyToResourceTypes: [TestBucketResource, TestTableResource], + applyToResourceTypes: [ + TestBucketResource.CFN_RESOURCE_TYPE_NAME, // 'AWS::S3::Bucket' + table.cfnResourceType, // 'AWS::DynamoDB::Table' + ], }); // THEN @@ -100,7 +106,9 @@ describe('removal-policys', () => { // WHEN RemovalPolicies.of(parent).snapshot({ - excludeResourceTypes: ['AWS::Test::Resource'], + excludeResourceTypes: [ + TestResource.CFN_RESOURCE_TYPE_NAME, // 'AWS::Test::Resource' + ], }); // THEN @@ -120,7 +128,9 @@ describe('removal-policys', () => { // WHEN RemovalPolicies.of(parent).snapshot({ - excludeResourceTypes: [TestResource], + excludeResourceTypes: [ + resource.cfnResourceType, + ], }); // THEN From af4e0ff9150b4872ea569cd60bd1432d0a851c26 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Sun, 22 Dec 2024 02:19:48 +0000 Subject: [PATCH 13/20] miss spell --- packages/aws-cdk-lib/core/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/aws-cdk-lib/core/README.md b/packages/aws-cdk-lib/core/README.md index a9292494c3496..82dc53a2edd0b 100644 --- a/packages/aws-cdk-lib/core/README.md +++ b/packages/aws-cdk-lib/core/README.md @@ -1650,17 +1650,17 @@ warning by the `id`. Annotations.of(this).acknowledgeWarning('IAM:Group:MaxPoliciesExceeded', 'Account has quota increased to 20'); ``` -## RemovalPolicys +## RemovalPolicies -The `RemovalPolicys` class provides a convenient way to manage removal policies for AWS CDK resources within a construct scope. It allows you to apply removal policies to multiple resources at once, with options to include or exclude specific resource types. +The `RemovalPolicies` class provides a convenient way to manage removal policies for AWS CDK resources within a construct scope. It allows you to apply removal policies to multiple resources at once, with options to include or exclude specific resource types. ### Usage ```typescript -import { RemovalPolicys } from 'aws-cdk-lib'; +import { RemovalPolicies } from 'aws-cdk-lib'; // Apply DESTROY policy to all resources in a scope -RemovalPolicys.of(scope).destroy(); +RemovalPolicies.of(scope).destroy(); // Apply RETAIN policy only to specific resource types RemovalPolicies.of(parent).retain({ @@ -1673,17 +1673,17 @@ RemovalPolicies.of(parent).retain({ // Apply SNAPSHOT policy excluding specific resource types -RemovalPolicys.of(scope).snapshot({ +RemovalPolicies.of(scope).snapshot({ excludeResourceTypes: ['AWS::Test::Resource'], }); // Apply RETAIN_ON_UPDATE_OR_DELETE policy -RemovalPolicys.of(scope).retainOnUpdateOrDelete(); +RemovalPolicies.of(scope).retainOnUpdateOrDelete(); ``` -### RemovalPolicys.of(scope) +### RemovalPolicies.of(scope) -Creates a new instance of RemovalPolicys for the given scope. +Creates a new instance of RemovalPolicies for the given scope. #### Methods From 5f6197c18c209440b83bfde5927adf643a71c468 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Sun, 22 Dec 2024 02:27:45 +0000 Subject: [PATCH 14/20] adding `anyL1Type).CFN_RESOURCE_TYPE_NAME` --- packages/aws-cdk-lib/core/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/aws-cdk-lib/core/README.md b/packages/aws-cdk-lib/core/README.md index 82dc53a2edd0b..83fa462620b99 100644 --- a/packages/aws-cdk-lib/core/README.md +++ b/packages/aws-cdk-lib/core/README.md @@ -1667,6 +1667,7 @@ RemovalPolicies.of(parent).retain({ applyToResourceTypes: [ 'AWS::DynamoDB::Table', bucket.cfnResourceType, // 'AWS::S3::Bucket' + CfnDBInstance.CFN_RESOURCE_TYPE_NAME, // 'AWS::RDS::DBInstance' ], }); From c0d4fd7e42779a50c0869c8950d4080f1fd9e1c6 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Sun, 22 Dec 2024 04:45:44 +0000 Subject: [PATCH 15/20] remove try-catch --- packages/aws-cdk-lib/core/lib/removal-policies.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/aws-cdk-lib/core/lib/removal-policies.ts b/packages/aws-cdk-lib/core/lib/removal-policies.ts index 09e002e9c9602..4cc802113a132 100644 --- a/packages/aws-cdk-lib/core/lib/removal-policies.ts +++ b/packages/aws-cdk-lib/core/lib/removal-policies.ts @@ -78,17 +78,7 @@ class RemovalPolicyAspect implements IAspect { } // Apply the removal policy - try { - cfnResource.applyRemovalPolicy(this.policy); - } catch (error) { - // Check if the error is an instance of the built-in Error class - if (error instanceof Error) { - throw new Error(`Failed to apply removal policy to resource type ${resourceType}: ${error.message}`); - } else { - // If it's not an Error instance, convert it to a string for the message - throw new Error(`Failed to apply removal policy to resource type ${resourceType}: ${String(error)}`); - } - } + cfnResource.applyRemovalPolicy(this.policy); } } From dec21e2e6e12df733f5e2ef70dd98b871bab2722 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Thu, 26 Dec 2024 02:26:28 +0000 Subject: [PATCH 16/20] AspectPriority --- .../aws-cdk-lib/core/lib/removal-policies.ts | 18 ++++++++++++------ .../core/test/removal-policies.test.ts | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/aws-cdk-lib/core/lib/removal-policies.ts b/packages/aws-cdk-lib/core/lib/removal-policies.ts index 4cc802113a132..f31434f3bf7fd 100644 --- a/packages/aws-cdk-lib/core/lib/removal-policies.ts +++ b/packages/aws-cdk-lib/core/lib/removal-policies.ts @@ -1,5 +1,5 @@ import { IConstruct } from 'constructs'; -import { Aspects, IAspect } from './aspect'; +import { Aspects, IAspect, AspectPriority } from './aspect'; import { CfnResource } from './cfn-resource'; import { RemovalPolicy } from './removal-policy'; @@ -28,11 +28,16 @@ export interface RemovalPolicyProps { * @default false - do not overwrite user-specified policies */ readonly overwrite?: boolean; + + /** + * The priority to use when applying this aspect. + * If multiple aspects apply conflicting settings, the one with the higher priority wins. + * + * @default - AspectPriority.MUTATING + */ + readonly priority?: number; } -/** - * The RemovalPolicyAspect handles applying a removal policy to resources - */ class RemovalPolicyAspect implements IAspect { constructor( private readonly policy: RemovalPolicy, @@ -46,7 +51,6 @@ class RemovalPolicyAspect implements IAspect { if (!patterns || patterns.length === 0) { return false; } - return patterns.includes(resourceType); } @@ -103,7 +107,9 @@ export class RemovalPolicies { * @param props Configuration options */ public apply(policy: RemovalPolicy, props: RemovalPolicyProps = {}) { - Aspects.of(this.scope).add(new RemovalPolicyAspect(policy, props)); + Aspects.of(this.scope).add(new RemovalPolicyAspect(policy, props), { + priority: props.priority ?? AspectPriority.MUTATING, + }); } /** diff --git a/packages/aws-cdk-lib/core/test/removal-policies.test.ts b/packages/aws-cdk-lib/core/test/removal-policies.test.ts index eb096c99119da..09449905123ec 100644 --- a/packages/aws-cdk-lib/core/test/removal-policies.test.ts +++ b/packages/aws-cdk-lib/core/test/removal-policies.test.ts @@ -220,4 +220,18 @@ describe('removal-policys', () => { expect(bucket.cfnOptions.deletionPolicy).toBe('Retain'); expect(table.cfnOptions.deletionPolicy).toBe('RetainExceptOnCreate'); }); + + test('higher priority removal policy overrides lower priority removal policy', () => { + // GIVEN + const stack = new Stack(); + const resource = new TestResource(stack, 'PriorityResource'); + + // WHEN + RemovalPolicies.of(stack).retainOnUpdateOrDelete({ priority: 250 }); + RemovalPolicies.of(stack).destroy({ priority: 10 }); + + // THEN + synthesize(stack); + expect(resource.cfnOptions.deletionPolicy).toBe('Delete'); + }); }); From da6b4c59406bca4d068fb60747b1a7616bcc9081 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Thu, 26 Dec 2024 02:41:48 +0000 Subject: [PATCH 17/20] readme updated --- packages/aws-cdk-lib/core/README.md | 42 +++++-------------- .../aws-cdk-lib/core/lib/removal-policies.ts | 3 ++ 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/packages/aws-cdk-lib/core/README.md b/packages/aws-cdk-lib/core/README.md index 177b901cec903..14f8bfa646eb4 100644 --- a/packages/aws-cdk-lib/core/README.md +++ b/packages/aws-cdk-lib/core/README.md @@ -1798,12 +1798,22 @@ The `RemovalPolicies` class provides a convenient way to manage removal policies ### Usage +Creates a new instance of RemovalPolicies for the given scope. + ```typescript import { RemovalPolicies } from 'aws-cdk-lib'; // Apply DESTROY policy to all resources in a scope RemovalPolicies.of(scope).destroy(); +// Apply DESTROY policy (overwrited) +RemovalPolicies.of(scope).snapshot(); +RemovalPolicies.of(scope).destroy({ overwrite: true })); + +// Apply DESTROY policy (priority) +RemovalPolicies.of(stack).retainOnUpdateOrDelete({ priority: 250 }); +RemovalPolicies.of(stack).destroy({ priority: 10 }); + // Apply RETAIN policy only to specific resource types RemovalPolicies.of(parent).retain({ applyToResourceTypes: [ @@ -1813,44 +1823,12 @@ RemovalPolicies.of(parent).retain({ ], }); - - // Apply SNAPSHOT policy excluding specific resource types RemovalPolicies.of(scope).snapshot({ excludeResourceTypes: ['AWS::Test::Resource'], }); - -// Apply RETAIN_ON_UPDATE_OR_DELETE policy -RemovalPolicies.of(scope).retainOnUpdateOrDelete(); ``` -### RemovalPolicies.of(scope) - -Creates a new instance of RemovalPolicies for the given scope. - -#### Methods - -- `apply(policy: RemovalPolicy, props?: RemovalPolicyProps)`: Apply a custom removal policy -- `destroy(props?: RemovalPolicyProps)`: Apply DESTROY removal policy -- `retain(props?: RemovalPolicyProps)`: Apply RETAIN removal policy -- `snapshot(props?: RemovalPolicyProps)`: Apply SNAPSHOT removal policy -- `retainOnUpdateOrDelete(props?: RemovalPolicyProps)`: Apply RETAIN_ON_UPDATE_OR_DELETE removal policy - -#### `RemovalPolicyProps` Interface - -Additional configuration options for applying removal policies. - -- **`applyToResourceTypes?`**: _(optional)_ - Array of CloudFormation resource types (e.g., `'AWS::S3::Bucket'`) to which the removal policy should be applied. - Defaults to applying to all resources. - -- **`excludeResourceTypes?`**: _(optional)_ - Array of CloudFormation resource types to exclude from applying the removal policy. - Defaults to no exclusions. - -- **`overwrite?`**: _(optional)_ - If `true`, the removal policy will overwrite any existing policy already set on the resource. Defaults to `false`. - #### Behavior Summary - When `overwrite` is `false` (default): diff --git a/packages/aws-cdk-lib/core/lib/removal-policies.ts b/packages/aws-cdk-lib/core/lib/removal-policies.ts index f31434f3bf7fd..013ee66959a8e 100644 --- a/packages/aws-cdk-lib/core/lib/removal-policies.ts +++ b/packages/aws-cdk-lib/core/lib/removal-policies.ts @@ -38,6 +38,9 @@ export interface RemovalPolicyProps { readonly priority?: number; } +/** + * The RemovalPolicyAspect handles applying a removal policy to resources + */ class RemovalPolicyAspect implements IAspect { constructor( private readonly policy: RemovalPolicy, From 467ebbde93e4eb13b194e9cd0090e697d445eea6 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Thu, 26 Dec 2024 14:28:12 +0000 Subject: [PATCH 18/20] integ --- .../TestStack.assets.json | 4 ++-- .../TestStack.template.json | 4 ++-- .../test/integ.removal-policies.js.snapshot/manifest.json | 2 +- .../test/core/test/integ.removal-policies.ts | 7 ++++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.assets.json index ffb588969b729..c2041a6d00af9 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.assets.json @@ -1,7 +1,7 @@ { "version": "38.0.1", "files": { - "728f2337a808b3a5ac83ceabdf43987b28de0c8b451766c4944281da2a516767": { + "ff06a4c258e6b54d1b288229140ba9546fd8b36f0802a6743b8ccd43fb8927c3": { "source": { "path": "TestStack.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "728f2337a808b3a5ac83ceabdf43987b28de0c8b451766c4944281da2a516767.json", + "objectKey": "ff06a4c258e6b54d1b288229140ba9546fd8b36f0802a6743b8ccd43fb8927c3.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.template.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.template.json index e0eba203115ca..4c89e43cb7b5a 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.template.json @@ -30,8 +30,8 @@ }, "TestUser6A619381": { "Type": "AWS::IAM::User", - "UpdateReplacePolicy": "Delete", - "DeletionPolicy": "Delete" + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" } }, "Parameters": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/manifest.json index 38a869df9c42c..2a84f9725cd6f 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/728f2337a808b3a5ac83ceabdf43987b28de0c8b451766c4944281da2a516767.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/ff06a4c258e6b54d1b288229140ba9546fd8b36f0802a6743b8ccd43fb8927c3.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.ts b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.ts index 3c2d846d0bfcf..3d00f64cdacaa 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.ts @@ -1,4 +1,4 @@ -import { App, RemovalPolicies, Stack } from 'aws-cdk-lib'; +import { App, RemovalPolicy, RemovalPolicies, Stack } from 'aws-cdk-lib'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as s3 from 'aws-cdk-lib/aws-s3'; @@ -12,11 +12,12 @@ new s3.Bucket(stack, 'TestBucket'); new dynamodb.Table(stack, 'TestTable', { partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, }); -new iam.User(stack, 'TestUser'); +const user = new iam.User(stack, 'TestUser'); +user.applyRemovalPolicy(RemovalPolicy.RETAIN); // Apply different removal policies to demonstrate functionality RemovalPolicies.of(stack).destroy(); new integ.IntegTest(app, 'RemovalPoliciesTest', { testCases: [stack], -}); \ No newline at end of file +}); From c0733be9d9c47429b048efc2dfe1887f2bb2752d Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Fri, 27 Dec 2024 12:00:30 +0000 Subject: [PATCH 19/20] adding overwrite and priority --- .../core/test/removal-policies.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/aws-cdk-lib/core/test/removal-policies.test.ts b/packages/aws-cdk-lib/core/test/removal-policies.test.ts index 09449905123ec..d9f3e7fde2a34 100644 --- a/packages/aws-cdk-lib/core/test/removal-policies.test.ts +++ b/packages/aws-cdk-lib/core/test/removal-policies.test.ts @@ -234,4 +234,19 @@ describe('removal-policys', () => { synthesize(stack); expect(resource.cfnOptions.deletionPolicy).toBe('Delete'); }); + + test('higher priority removal policy with overwrite set to true', () => { + // GIVEN + const stack = new Stack(); + const resource = new TestResource(stack, 'PriorityResource'); + + // WHEN + RemovalPolicies.of(stack).retainOnUpdateOrDelete({ priority: 10 }); + RemovalPolicies.of(stack).destroy({ priority: 250, overwrite: true }); + + // THEN + synthesize(stack); + expect(resource.cfnOptions.deletionPolicy).toBe('Delete'); + }); + }); From bf5eff9f6e8e2f10068f954c280b8645a0862559 Mon Sep 17 00:00:00 2001 From: watany <76135106+watany-dev@users.noreply.github.com> Date: Fri, 27 Dec 2024 15:46:50 +0000 Subject: [PATCH 20/20] integ --- .../TestStack.assets.json | 4 ++-- .../TestStack.template.json | 5 +++++ .../manifest.json | 8 ++++++- .../tree.json | 22 +++++++++++++++++++ .../test/core/test/integ.removal-policies.ts | 8 +++++-- 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.assets.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.assets.json index c2041a6d00af9..329238c1350be 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.assets.json @@ -1,7 +1,7 @@ { "version": "38.0.1", "files": { - "ff06a4c258e6b54d1b288229140ba9546fd8b36f0802a6743b8ccd43fb8927c3": { + "f317d1d8eb0f6b3d1c8990bc82107ef6959bc53324190e036a7734ea3b5189a8": { "source": { "path": "TestStack.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "ff06a4c258e6b54d1b288229140ba9546fd8b36f0802a6743b8ccd43fb8927c3.json", + "objectKey": "f317d1d8eb0f6b3d1c8990bc82107ef6959bc53324190e036a7734ea3b5189a8.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.template.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.template.json index 4c89e43cb7b5a..7e3859e62e83b 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/TestStack.template.json @@ -32,6 +32,11 @@ "Type": "AWS::IAM::User", "UpdateReplacePolicy": "Retain", "DeletionPolicy": "Retain" + }, + "DestroyBucket924C7F03": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" } }, "Parameters": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/manifest.json index 2a84f9725cd6f..79f3737f773e8 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/ff06a4c258e6b54d1b288229140ba9546fd8b36f0802a6743b8ccd43fb8927c3.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/f317d1d8eb0f6b3d1c8990bc82107ef6959bc53324190e036a7734ea3b5189a8.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -52,6 +52,12 @@ "data": "TestUser6A619381" } ], + "/TestStack/DestroyBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "DestroyBucket924C7F03" + } + ], "/TestStack/BootstrapVersion": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/tree.json index 2474990be0e3e..d46895344354b 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.js.snapshot/tree.json @@ -99,6 +99,28 @@ "version": "0.0.0" } }, + "DestroyBucket": { + "id": "DestroyBucket", + "path": "TestStack/DestroyBucket", + "children": { + "Resource": { + "id": "Resource", + "path": "TestStack/DestroyBucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.CfnBucket", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.Bucket", + "version": "0.0.0" + } + }, "BootstrapVersion": { "id": "BootstrapVersion", "path": "TestStack/BootstrapVersion", diff --git a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.ts b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.ts index 3d00f64cdacaa..cd167ccf1971b 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/core/test/integ.removal-policies.ts @@ -7,15 +7,19 @@ import * as integ from '@aws-cdk/integ-tests-alpha'; const app = new App(); const stack = new Stack(app, 'TestStack'); -// Create resources new s3.Bucket(stack, 'TestBucket'); + new dynamodb.Table(stack, 'TestTable', { partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, }); + const user = new iam.User(stack, 'TestUser'); user.applyRemovalPolicy(RemovalPolicy.RETAIN); -// Apply different removal policies to demonstrate functionality +new s3.Bucket(stack, 'DestroyBucket', { + removalPolicy: RemovalPolicy.DESTROY, +}); + RemovalPolicies.of(stack).destroy(); new integ.IntegTest(app, 'RemovalPoliciesTest', {