From ffb1c52763a4fe49519f3dbe5447b5d17eae28e9 Mon Sep 17 00:00:00 2001 From: c19yamahar Date: Sun, 30 Nov 2025 16:22:49 +0900 Subject: [PATCH 1/3] :sparkles:feat: update --- .../aws-cdk-lib/aws-ecr/lib/repository.ts | 111 ++++++++++-------- 1 file changed, 61 insertions(+), 50 deletions(-) diff --git a/packages/aws-cdk-lib/aws-ecr/lib/repository.ts b/packages/aws-cdk-lib/aws-ecr/lib/repository.ts index 9a8c70477eee6..383c835c79520 100644 --- a/packages/aws-cdk-lib/aws-ecr/lib/repository.ts +++ b/packages/aws-cdk-lib/aws-ecr/lib/repository.ts @@ -23,6 +23,7 @@ import { Arn, ValidationError, UnscopedValidationError, + CfnDeletionPolicy, } from '../../core'; import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource'; import { propertyInjectable } from '../../core/lib/prop-injectable'; @@ -841,12 +842,7 @@ export class Repository extends RepositoryBase { resourceName: this.physicalName, }); - if (props.emptyOnDelete && props.removalPolicy !== RemovalPolicy.DESTROY) { - throw new ValidationError('Cannot use \'emptyOnDelete\' property on a repository without setting removal policy to \'DESTROY\'.', this); - } else if (props.emptyOnDelete == undefined && props.autoDeleteImages) { - if (props.removalPolicy !== RemovalPolicy.DESTROY) { - throw new ValidationError('Cannot use \'autoDeleteImages\' property on a repository without setting removal policy to \'DESTROY\'.', this); - } + if (props.emptyOnDelete ||(props.emptyOnDelete == undefined && props.autoDeleteImages)) { this.enableAutoDeleteImages(); } @@ -916,6 +912,65 @@ export class Repository extends RepositoryBase { this.lifecycleRules.push({ ...rule }); } + /** + * Enable automatic deletion of images in the repository when it is removed from the stack. + * This requires the repository's removal policy to be set to DESTROY. + */ + @MethodMetadata() + public enableAutoDeleteImages() { + // Validate that the removal policy is DESTROY + if (this._resource.cfnOptions.deletionPolicy !== CfnDeletionPolicy.DELETE + || this._resource.cfnOptions.updateReplacePolicy !== CfnDeletionPolicy.DELETE) { + throw new ValidationError('Cannot use auto-delete images on a repository without setting removal policy to \'DESTROY\'.', this); + } + + const firstTime = Stack.of(this).node.tryFindChild(`${AUTO_DELETE_IMAGES_RESOURCE_TYPE}CustomResourceProvider`) === undefined; + if (!firstTime) { + // Provider already exists, so we only need to tag this repository + Tags.of(this._resource).add(AUTO_DELETE_IMAGES_TAG, 'true'); + return; + } + + const provider = AutoDeleteImagesProvider.getOrCreateProvider(this, AUTO_DELETE_IMAGES_RESOURCE_TYPE, { + useCfnResponseWrapper: false, + description: `Lambda function for auto-deleting images in ${this.repositoryName} repository.`, + }); + + // Use a iam policy to allow the custom resource to list & delete + // images in the repository and the ability to get all repositories to find the arn needed on delete. + provider.addToRolePolicy({ + Effect: 'Allow', + Action: [ + 'ecr:BatchDeleteImage', + 'ecr:DescribeRepositories', + 'ecr:ListImages', + 'ecr:ListTagsForResource', + ], + Resource: [`arn:${Aws.PARTITION}:ecr:${Stack.of(this).region}:${Stack.of(this).account}:repository/*`], + Condition: { + StringEquals: { + ['ecr:ResourceTag/' + AUTO_DELETE_IMAGES_TAG]: 'true', + }, + }, + }); + + const customResource = new CustomResource(this, 'AutoDeleteImagesCustomResource', { + resourceType: AUTO_DELETE_IMAGES_RESOURCE_TYPE, + serviceToken: provider.serviceToken, + properties: { + RepositoryName: this.repositoryName, + }, + }); + customResource.node.addDependency(this); + + // We also tag the repository to record the fact that we want it autodeleted. + // The custom resource will check this tag before actually doing the delete. + // Because tagging and untagging will ALWAYS happen before the CR is deleted, + // we can set `autoDeleteImages: false` without the removal of the CR emptying + // the repository as a side effect. + Tags.of(this._resource).add(AUTO_DELETE_IMAGES_TAG, 'true'); + } + private validateTagMutability(tagMutability?: TagMutability, exclusionFilters?: ImageTagMutabilityExclusionFilter[]): void { const EXCLUSION_REQUIRED_TAG_MUTABILITY = [ TagMutability.IMMUTABLE_WITH_EXCLUSION, @@ -1025,50 +1080,6 @@ export class Repository extends RepositoryBase { throw new ValidationError(`Unexpected 'encryptionType': ${encryptionType}`, this); } - - private enableAutoDeleteImages() { - const firstTime = Stack.of(this).node.tryFindChild(`${AUTO_DELETE_IMAGES_RESOURCE_TYPE}CustomResourceProvider`) === undefined; - const provider = AutoDeleteImagesProvider.getOrCreateProvider(this, AUTO_DELETE_IMAGES_RESOURCE_TYPE, { - useCfnResponseWrapper: false, - description: `Lambda function for auto-deleting images in ${this.repositoryName} repository.`, - }); - - if (firstTime) { - // Use a iam policy to allow the custom resource to list & delete - // images in the repository and the ability to get all repositories to find the arn needed on delete. - provider.addToRolePolicy({ - Effect: 'Allow', - Action: [ - 'ecr:BatchDeleteImage', - 'ecr:DescribeRepositories', - 'ecr:ListImages', - 'ecr:ListTagsForResource', - ], - Resource: [`arn:${Aws.PARTITION}:ecr:${Stack.of(this).region}:${Stack.of(this).account}:repository/*`], - Condition: { - StringEquals: { - ['ecr:ResourceTag/' + AUTO_DELETE_IMAGES_TAG]: 'true', - }, - }, - }); - } - - const customResource = new CustomResource(this, 'AutoDeleteImagesCustomResource', { - resourceType: AUTO_DELETE_IMAGES_RESOURCE_TYPE, - serviceToken: provider.serviceToken, - properties: { - RepositoryName: this.repositoryName, - }, - }); - customResource.node.addDependency(this); - - // We also tag the repository to record the fact that we want it autodeleted. - // The custom resource will check this tag before actually doing the delete. - // Because tagging and untagging will ALWAYS happen before the CR is deleted, - // we can set `autoDeleteImages: false` without the removal of the CR emptying - // the repository as a side effect. - Tags.of(this._resource).add(AUTO_DELETE_IMAGES_TAG, 'true'); - } } function validateAnyRuleLast(rules: LifecycleRule[]) { From d34c8f84e5749b489ea9b0043da4ac44c892f998 Mon Sep 17 00:00:00 2001 From: c19yamahar Date: Wed, 10 Dec 2025 23:26:10 +0900 Subject: [PATCH 2/3] :sparkles:feat: Add README --- packages/aws-cdk-lib/aws-ecr/README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-ecr/README.md b/packages/aws-cdk-lib/aws-ecr/README.md index 352c80bd1a6d7..a6208e1d912c4 100644 --- a/packages/aws-cdk-lib/aws-ecr/README.md +++ b/packages/aws-cdk-lib/aws-ecr/README.md @@ -223,7 +223,7 @@ policy is set to `RemovalPolicy.DESTROY`, the repository will be deleted as long as it does not contain any images. To override this and force all images to get deleted during repository deletion, -enable the `emptyOnDelete` option as well as setting the removal policy to +enable the `emptyOnDelete` option as well as setting the removal policy to `RemovalPolicy.DESTROY`. ```ts @@ -233,6 +233,16 @@ const repository = new ecr.Repository(this, 'MyTempRepo', { }); ``` +Alternatively, you can enable image auto-deletion after the repository has been created, allowing for conditional or dynamic configuration: + +```ts +const repository = new ecr.Repository(this, 'MyTempRepo', { + removalPolicy: cdk.RemovalPolicy.DESTROY, +}); + +repository.enableAutoDeleteImages(); +``` + ## Managing the Resource Policy You can add statements to the resource policy of the repository using the From 89f7037937287f52a6538d3799118b29c2bfabfb Mon Sep 17 00:00:00 2001 From: c19yamahar Date: Wed, 10 Dec 2025 23:27:38 +0900 Subject: [PATCH 3/3] :bug:fix: fix --- packages/aws-cdk-lib/aws-ecr/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-ecr/README.md b/packages/aws-cdk-lib/aws-ecr/README.md index a6208e1d912c4..dacd6b77e100b 100644 --- a/packages/aws-cdk-lib/aws-ecr/README.md +++ b/packages/aws-cdk-lib/aws-ecr/README.md @@ -223,7 +223,7 @@ policy is set to `RemovalPolicy.DESTROY`, the repository will be deleted as long as it does not contain any images. To override this and force all images to get deleted during repository deletion, -enable the `emptyOnDelete` option as well as setting the removal policy to +enable the `emptyOnDelete` option as well as setting the removal policy to `RemovalPolicy.DESTROY`. ```ts