From 9a08f4604dff6f26a928b8a9acb80c12cde95143 Mon Sep 17 00:00:00 2001 From: Randy Ridgley Date: Fri, 12 Nov 2021 12:07:15 -0500 Subject: [PATCH 1/6] fix(aws-lambda-event-sources): clusterArn cannot be used as id of construct --- .../aws-lambda-event-sources/lib/kafka.ts | 4 ++-- .../test/kafka.test.ts | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts index 54e98a47bb55d..2f19dc8b79dee 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts @@ -3,7 +3,7 @@ import { ISecurityGroup, IVpc, SubnetSelection } from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; -import { Stack } from '@aws-cdk/core'; +import { Stack, Names } from '@aws-cdk/core'; import { StreamEventSource, StreamEventSourceProps } from './stream'; // keep this import separate from other imports to reduce chance for merge conflicts with v2-main @@ -109,7 +109,7 @@ export class ManagedKafkaEventSource extends StreamEventSource { public bind(target: lambda.IFunction) { target.addEventSourceMapping( - `KafkaEventSource:${this.innerProps.clusterArn}${this.innerProps.topic}`, + `KafkaEventSource:${Names.nodeUniqueId(target.node)}${this.innerProps.topic}`, this.enrichMappingOptions({ eventSourceArn: this.innerProps.clusterArn, startingPosition: this.innerProps.startingPosition, diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts index 804069373c114..e7747295e34ff 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts @@ -488,5 +488,25 @@ describe('KafkaEventSource', () => { ]), }); }); + + test('ManagedKafkaEventSource name conforms to construct id rules', () => { + // GIVEN + const stack = new cdk.Stack(); + const fn = new TestFunction(stack, 'Fn'); + const clusterArn = 'some-arn'; + const kafkaTopic = 'some-topic'; + + const mskEventMapping = new sources.ManagedKafkaEventSource( + { + clusterArn, + topic: kafkaTopic, + startingPosition: lambda.StartingPosition.TRIM_HORIZON, + }); + + let expectedId = `KafkaEventSource:${cdk.Names.nodeUniqueId(fn.node)}-${kafkaTopic}`; + // WHEN + fn.addEventSource(mskEventMapping); + expect(expectedId).toEqual(mskEventMapping.bind.name); + }); }); }); From fbb90f6bb2e6c626eaa13d5ab2b442ae546624bc Mon Sep 17 00:00:00 2001 From: Randy Ridgley Date: Fri, 12 Nov 2021 14:51:48 -0500 Subject: [PATCH 2/6] fix(aws-lambda-event-sources): introduce eventMappingId similiar to kinesis event source to be able to validate the eventId is created and defined at time of binding --- .../aws-lambda-event-sources/lib/kafka.ts | 15 ++++++++++++++- .../aws-lambda-event-sources/test/kafka.test.ts | 3 +-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts b/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts index 2f19dc8b79dee..e31fac89100cb 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/lib/kafka.ts @@ -101,6 +101,7 @@ export interface SelfManagedKafkaEventSourceProps extends KafkaEventSourceProps export class ManagedKafkaEventSource extends StreamEventSource { // This is to work around JSII inheritance problems private innerProps: ManagedKafkaEventSourceProps; + private _eventSourceMappingId?: string = undefined; constructor(props: ManagedKafkaEventSourceProps) { super(props); @@ -108,7 +109,7 @@ export class ManagedKafkaEventSource extends StreamEventSource { } public bind(target: lambda.IFunction) { - target.addEventSourceMapping( + const eventSourceMapping = target.addEventSourceMapping( `KafkaEventSource:${Names.nodeUniqueId(target.node)}${this.innerProps.topic}`, this.enrichMappingOptions({ eventSourceArn: this.innerProps.clusterArn, @@ -118,6 +119,8 @@ export class ManagedKafkaEventSource extends StreamEventSource { }), ); + this._eventSourceMappingId = eventSourceMapping.eventSourceMappingId; + if (this.innerProps.secret !== undefined) { this.innerProps.secret.grantRead(target); } @@ -146,6 +149,16 @@ export class ManagedKafkaEventSource extends StreamEventSource { ? undefined : sourceAccessConfigurations; } + + /** + * The identifier for this EventSourceMapping + */ + public get eventSourceMappingId(): string { + if (!this._eventSourceMappingId) { + throw new Error('KafkaEventSource is not yet bound to an event source mapping'); + } + return this._eventSourceMappingId; + } } /** diff --git a/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts b/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts index e7747295e34ff..1455931278129 100644 --- a/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts +++ b/packages/@aws-cdk/aws-lambda-event-sources/test/kafka.test.ts @@ -503,10 +503,9 @@ describe('KafkaEventSource', () => { startingPosition: lambda.StartingPosition.TRIM_HORIZON, }); - let expectedId = `KafkaEventSource:${cdk.Names.nodeUniqueId(fn.node)}-${kafkaTopic}`; // WHEN fn.addEventSource(mskEventMapping); - expect(expectedId).toEqual(mskEventMapping.bind.name); + expect(mskEventMapping.eventSourceMappingId).toBeDefined(); }); }); }); From 6de1f9ec950c7c275cf7079178b8d355f8f248f0 Mon Sep 17 00:00:00 2001 From: Randy Ridgley Date: Fri, 10 Mar 2023 16:40:03 +0000 Subject: [PATCH 3/6] feat(ecr): add option to auto delete images upon ECR repository removal --- packages/@aws-cdk/aws-ecr/README.md | 18 + .../lib/auto-delete-images-handler/index.ts | 92 ++++ packages/@aws-cdk/aws-ecr/lib/repository.ts | 78 +++- .../test/auto-delete-images-handler.test.ts | 397 ++++++++++++++++++ .../__entrypoint__.js | 144 +++++++ .../index.js | 83 ++++ .../aws-ecr-integ-stack.assets.json | 32 ++ .../aws-ecr-integ-stack.template.json | 190 +++++++++ .../cdk.out | 1 + ...efaultTestDeployAssert6B08011C.assets.json | 19 + ...aultTestDeployAssert6B08011C.template.json | 36 ++ .../integ.json | 12 + .../manifest.json | 135 ++++++ .../tree.json | 211 ++++++++++ .../integ.repository-auto-delete-images.ts | 20 + 15 files changed, 1467 insertions(+), 1 deletion(-) create mode 100644 packages/@aws-cdk/aws-ecr/lib/auto-delete-images-handler/index.ts create mode 100644 packages/@aws-cdk/aws-ecr/test/auto-delete-images-handler.test.ts create mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c/__entrypoint__.js create mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c/index.js create mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.assets.json create mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.template.json create mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/cdkintegautodeleteimagesDefaultTestDeployAssert6B08011C.assets.json create mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/cdkintegautodeleteimagesDefaultTestDeployAssert6B08011C.template.json create mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/tree.json create mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.ts diff --git a/packages/@aws-cdk/aws-ecr/README.md b/packages/@aws-cdk/aws-ecr/README.md index 7a866e77f7a49..fdfbe2319f9d3 100644 --- a/packages/@aws-cdk/aws-ecr/README.md +++ b/packages/@aws-cdk/aws-ecr/README.md @@ -118,3 +118,21 @@ declare const repository: ecr.Repository; repository.addLifecycleRule({ tagPrefixList: ['prod'], maxImageCount: 9999 }); repository.addLifecycleRule({ maxImageAge: Duration.days(30) }); ``` + +### Repository deletion + +When a repository is removed from a stack (or the stack is deleted), the ECR +repository will be removed according to its removal policy (which by default will +simply orphan the repository and leave it in your AWS account). If the removal +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`autoDeleteImages` option. + +```ts +const repository = new Repository(this, 'MyTempRepo', { + removalPolicy: RemovalPolicy.DESTROY, + autoDeleteImages: true, +}); +``` diff --git a/packages/@aws-cdk/aws-ecr/lib/auto-delete-images-handler/index.ts b/packages/@aws-cdk/aws-ecr/lib/auto-delete-images-handler/index.ts new file mode 100644 index 0000000000000..3596866db0f93 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/lib/auto-delete-images-handler/index.ts @@ -0,0 +1,92 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { ECR } from 'aws-sdk'; + +const AUTO_DELETE_IMAGES_TAG = 'aws-cdk:auto-delete-images'; + +const ecr = new ECR(); + +export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent) { + switch (event.RequestType) { + case 'Create': + case 'Update': + return onUpdate(event); + case 'Delete': + return onDelete(event.ResourceProperties?.RepositoryName); + } +} + +async function onUpdate(event: AWSLambda.CloudFormationCustomResourceEvent) { + const updateEvent = event as AWSLambda.CloudFormationCustomResourceUpdateEvent; + const oldRepositoryName = updateEvent.OldResourceProperties?.RepositoryName; + const newRepositoryName = updateEvent.ResourceProperties?.RepositoryName; + const repositoryNameHasChanged = newRepositoryName != null && oldRepositoryName != null && newRepositoryName !== oldRepositoryName; + + /* If the name of the repository has changed, CloudFormation will try to delete the repository + and create a new one with the new name. So we have to delete the images in the + repository so that this operation does not fail. */ + if (repositoryNameHasChanged) { + return onDelete(oldRepositoryName); + } +} + +/** + * Recursively delete all images in the repository + * + * @param ECR.ListImagesRequest the repositoryName & nextToken if presented + */ +async function emptyRepository(params: ECR.ListImagesRequest) { + const listedImages = await ecr.listImages(params).promise(); + + const imageIds = listedImages?.imageIds ?? []; + const nextToken = listedImages.nextToken ?? null; + if (imageIds.length === 0) { + return; + } + + await ecr.batchDeleteImage({ + repositoryName: params.repositoryName, + imageIds, + }).promise(); + + if (nextToken) { + await emptyRepository({ + ...params, + nextToken, + }); + } +} + +async function onDelete(repositoryName: string) { + if (!repositoryName) { + throw new Error('No RepositoryName was provided.'); + } + + const response = await ecr.describeRepositories({ repositoryNames: [repositoryName] }).promise(); + const repository = response.repositories?.find(repo => repo.repositoryName === repositoryName); + + if (!await isRepositoryTaggedForDeletion(repository?.repositoryArn!)) { + process.stdout.write(`Repository does not have '${AUTO_DELETE_IMAGES_TAG}' tag, skipping cleaning.\n`); + return; + } + try { + await emptyRepository({ repositoryName }); + } catch (e) { + if (e.name !== 'RepositoryNotFoundException') { + throw e; + } + // Repository doesn't exist. Ignoring + } +} + +/** + * The repository will only be tagged for deletion if it's being deleted in the same + * deployment as this Custom Resource. + * + * If the Custom Resource is ever deleted before the repository, it must be because + * `autoDeleteImages` has been switched to false, in which case the tag would have + * been removed before we get to this Delete event. + */ +async function isRepositoryTaggedForDeletion(repositoryArn: string) { + const response = await ecr.listTagsForResource({ resourceArn: repositoryArn }).promise(); + return response.tags?.some(tag => tag.Key === AUTO_DELETE_IMAGES_TAG && tag.Value === 'true'); +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/lib/repository.ts b/packages/@aws-cdk/aws-ecr/lib/repository.ts index d32f3430e3c62..38d51f7bbd7dd 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository.ts @@ -1,12 +1,20 @@ import { EOL } from 'os'; +import * as path from 'path'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; -import { ArnFormat, IResource, Lazy, RemovalPolicy, Resource, Stack, Tags, Token, TokenComparison } from '@aws-cdk/core'; +import { + ArnFormat, IResource, Lazy, RemovalPolicy, Resource, Stack, + Tags, Token, TokenComparison, CustomResource, CustomResourceProvider, + CustomResourceProviderRuntime, +} from '@aws-cdk/core'; import { IConstruct, Construct } from 'constructs'; import { CfnRepository } from './ecr.generated'; import { LifecycleRule, TagStatus } from './lifecycle'; +const AUTO_DELETE_IMAGES_RESOURCE_TYPE = 'Custom::ECRAutoDeleteImages'; +const AUTO_DELETE_IMAGES_TAG = 'aws-cdk:auto-delete-images'; + /** * Represents an ECR repository. */ @@ -479,6 +487,16 @@ export interface RepositoryProps { * @default TagMutability.MUTABLE */ readonly imageTagMutability?: TagMutability; + + /** + * Whether all images should be automatically deleted when the repository is + * removed from the stack or when the stack is deleted. + * + * Requires the `removalPolicy` to be set to `RemovalPolicy.DESTROY`. + * + * @default false + */ + readonly autoDeleteImages?: boolean; } export interface RepositoryAttributes { @@ -589,6 +607,7 @@ export class Repository extends RepositoryBase { private readonly lifecycleRules = new Array(); private readonly registryId?: string; private policyDocument?: iam.PolicyDocument; + private readonly _resource: CfnRepository; constructor(scope: Construct, id: string, props: RepositoryProps = {}) { super(scope, id, { @@ -606,6 +625,14 @@ export class Repository extends RepositoryBase { imageTagMutability: props.imageTagMutability || undefined, encryptionConfiguration: this.parseEncryption(props), }); + this._resource = resource; + + if (props.autoDeleteImages) { + if (props.removalPolicy !== RemovalPolicy.DESTROY) { + throw new Error('Cannot use \'autoDeleteImages\' property on a repository without setting removal policy to \'DESTROY\'.'); + } + this.enableAutoDeleteImages(); + } resource.applyRemovalPolicy(props.removalPolicy); @@ -741,6 +768,55 @@ export class Repository extends RepositoryBase { throw new Error(`Unexpected 'encryptionType': ${encryptionType}`); } + + private enableAutoDeleteImages() { + const provider = CustomResourceProvider.getOrCreateProvider(this, AUTO_DELETE_IMAGES_RESOURCE_TYPE, { + codeDirectory: path.join(__dirname, 'auto-delete-images-handler'), + runtime: CustomResourceProviderRuntime.NODEJS_14_X, + 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 + this.addToResourcePolicy(new iam.PolicyStatement({ + actions: [ + 'ecr:BatchDeleteImage', + 'ecr:ListImages', + ], + resources: [ + this.repositoryArn, + ], + principals: [new iam.ArnPrincipal(provider.roleArn)], + })); + + // This is scoped to * in the case that the repository name is changed during an update + // it can be retrieved from the DescribeRepositories call to ECR + this.addToResourcePolicy(new iam.PolicyStatement({ + actions: [ + 'ecr:DescribeRepositories', + ], + resources: [ + '*', + ], + principals: [new iam.ArnPrincipal(provider.roleArn)], + })); + + const customResource = new CustomResource(this, 'AutoDeleteImagesCustomResource', { + resourceType: AUTO_DELETE_IMAGES_RESOURCE_TYPE, + serviceToken: provider.serviceToken, + properties: { + RepositoryName: Lazy.any({ produce: () => 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[]) { diff --git a/packages/@aws-cdk/aws-ecr/test/auto-delete-images-handler.test.ts b/packages/@aws-cdk/aws-ecr/test/auto-delete-images-handler.test.ts new file mode 100644 index 0000000000000..9bbebdf32c498 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/test/auto-delete-images-handler.test.ts @@ -0,0 +1,397 @@ +const mockECRClient = { + listImages: jest.fn().mockReturnThis(), + batchDeleteImage: jest.fn().mockReturnThis(), + describeRepositories: jest.fn().mockReturnThis(), + listTagsForResource: jest.fn().mockReturnThis(), + promise: jest.fn(), +}; + +import { handler } from '../lib/auto-delete-images-handler'; + +jest.mock('aws-sdk', () => { + return { ECR: jest.fn(() => mockECRClient) }; +}); + +beforeEach(() => { + mockECRClient.listImages.mockReturnThis(); + mockECRClient.batchDeleteImage.mockReturnThis(); + mockECRClient.listTagsForResource.mockReturnThis(); + mockECRClient.describeRepositories.mockReturnThis(); + givenTaggedForDeletion(); +}); + +afterEach(() => { + jest.resetAllMocks(); +}); + +test('does nothing on create event', async () => { + // GIVEN + const event: Partial = { + RequestType: 'Create', + ResourceProperties: { + ServiceToken: 'Foo', + RepositoryName: 'MyRepo', + }, + }; + + // WHEN + await invokeHandler(event); + + // THEN + expect(mockECRClient.listImages).toHaveBeenCalledTimes(0); + expect(mockECRClient.batchDeleteImage).toHaveBeenCalledTimes(0); +}); + +test('does nothing on update event when everything remains the same', async () => { + // GIVEN + const event: Partial = { + RequestType: 'Update', + ResourceProperties: { + ServiceToken: 'Foo', + RepositoryName: 'MyRepo', + }, + OldResourceProperties: { + ServiceToken: 'Foo', + RepositoryName: 'MyRepo', + }, + }; + + // WHEN + await invokeHandler(event); + + // THEN + expect(mockECRClient.describeRepositories).toHaveBeenCalledTimes(0); + expect(mockECRClient.listImages).toHaveBeenCalledTimes(0); + expect(mockECRClient.batchDeleteImage).toHaveBeenCalledTimes(0); +}); + +test('does nothing on update event when the repository name remains the same but the service token changes', async () => { + // GIVEN + const event: Partial = { + RequestType: 'Update', + ResourceProperties: { + ServiceToken: 'Foo', + RepositoryName: 'MyRepo', + }, + OldResourceProperties: { + ServiceToken: 'Bar', + RespositoryName: 'MyRepo', + }, + }; + + // WHEN + await invokeHandler(event); + + // THEN + expect(mockECRClient.describeRepositories).toHaveBeenCalledTimes(0); + expect(mockECRClient.listImages).toHaveBeenCalledTimes(0); + expect(mockECRClient.batchDeleteImage).toHaveBeenCalledTimes(0); +}); + +test('does nothing on update event when the new resource properties are absent', async () => { + // GIVEN + const event: Partial = { + RequestType: 'Update', + OldResourceProperties: { + ServiceToken: 'Foo', + RepositoryName: 'MyRepo', + }, + }; + + // WHEN + await invokeHandler(event); + + // THEN + expect(mockECRClient.listImages).toHaveBeenCalledTimes(0); + expect(mockECRClient.batchDeleteImage).toHaveBeenCalledTimes(0); + expect(mockECRClient.describeRepositories).toHaveBeenCalledTimes(0); +}); + +test('does nothing on update event when the old resource properties are absent', async () => { + // GIVEN + const event: Partial = { + RequestType: 'Update', + ResourceProperties: { + ServiceToken: 'Foo', + RepositoryName: 'MyRepo', + }, + }; + + // WHEN + await invokeHandler(event); + + // THEN + expect(mockECRClient.listImages).toHaveBeenCalledTimes(0); + expect(mockECRClient.batchDeleteImage).toHaveBeenCalledTimes(0); + expect(mockECRClient.describeRepositories).toHaveBeenCalledTimes(0); +}); + +test('deletes all objects when the name changes on update event', async () => { + // GIVEN + mockAwsPromise(mockECRClient.describeRepositories, { + repositories: [ + { repositoryArn: 'RepositoryArn', respositoryName: 'MyRepo' }, + ], + }); + + mockAwsPromise(mockECRClient.listImages, { + imageIds: [ + { imageDigest: 'ImageDigest1', imageTag: 'ImageTag1' }, + { imageDigest: 'ImageDigest2', imageTag: 'ImageTag2' }, + ], + }); + + const event: Partial = { + RequestType: 'Update', + OldResourceProperties: { + ServiceToken: 'Foo', + RepositoryName: 'MyRepo', + }, + ResourceProperties: { + ServiceToken: 'Foo', + RepositoryName: 'MyRepo-renamed', + }, + }; + + // WHEN + await invokeHandler(event); + + // THEN + expect(mockECRClient.listImages).toHaveBeenCalledTimes(1); + expect(mockECRClient.listImages).toHaveBeenCalledWith({ repositoryName: 'MyRepo' }); + expect(mockECRClient.batchDeleteImage).toHaveBeenCalledTimes(1); + expect(mockECRClient.batchDeleteImage).toHaveBeenCalledWith({ + repositoryName: 'MyRepo', + imageIds: [ + { imageDigest: 'ImageDigest1', imageTag: 'ImageTag1' }, + { imageDigest: 'ImageDigest2', imageTag: 'ImageTag2' }, + ], + }); + expect(mockECRClient.describeRepositories).toHaveBeenCalledTimes(1); +}); + +test('deletes no images on delete event when repository has no images', async () => { + // GIVEN + mockECRClient.promise.mockResolvedValue({ imageIds: [] }); // listedImages() call + + // WHEN + const event: Partial = { + RequestType: 'Delete', + ResourceProperties: { + ServiceToken: 'Foo', + RepositoryName: 'MyRepo', + }, + }; + await invokeHandler(event); + + // THEN + expect(mockECRClient.listImages).toHaveBeenCalledTimes(1); + expect(mockECRClient.listImages).toHaveBeenCalledWith({ repositoryName: 'MyRepo' }); + expect(mockECRClient.batchDeleteImage).toHaveBeenCalledTimes(0); + expect(mockECRClient.describeRepositories).toHaveBeenCalledTimes(1); +}); + +test('deletes all images on delete event', async () => { + mockECRClient.promise.mockResolvedValue({ // listedImages() call + imageIds: [ + { + imageTag: 'tag1', + imageDigest: 'sha256-1', + }, + { + imageTag: 'tag2', + imageDigest: 'sha256-2', + }, + ], + }); + + // WHEN + const event: Partial = { + RequestType: 'Delete', + ResourceProperties: { + ServiceToken: 'Foo', + RepositoryName: 'MyRepo', + }, + }; + await invokeHandler(event); + + // THEN + expect(mockECRClient.listImages).toHaveBeenCalledTimes(1); + expect(mockECRClient.listImages).toHaveBeenCalledWith({ repositoryName: 'MyRepo' }); + expect(mockECRClient.batchDeleteImage).toHaveBeenCalledTimes(1); + expect(mockECRClient.batchDeleteImage).toHaveBeenCalledWith({ + repositoryName: 'MyRepo', + imageIds: [ + { + imageTag: 'tag1', + imageDigest: 'sha256-1', + }, + { + imageTag: 'tag2', + imageDigest: 'sha256-2', + }, + ], + }); +}); + +test('does not empty repository if it is not tagged', async () => { + // GIVEN + givenNotTaggedForDeletion(); + mockECRClient.promise.mockResolvedValue({ // listedImages() call + imageIds: [ + { + imageTag: 'tag1', + imageDigest: 'sha256-1', + }, + { + imageTag: 'tag2', + imageDigest: 'sha256-2', + }, + ], + }); + + // WHEN + const event: Partial = { + RequestType: 'Delete', + ResourceProperties: { + ServiceToken: 'Foo', + RepositoryName: 'MyRepo', + }, + }; + await invokeHandler(event); + + // THEN + expect(mockECRClient.batchDeleteImage).not.toHaveBeenCalled(); +}); + +test('delete event where repo has many images does recurse appropriately', async () => { + // GIVEN + mockAwsPromise(mockECRClient.describeRepositories, { + repositories: [ + { repositoryArn: 'RepositoryArn', respositoryName: 'MyRepo' }, + ], + }); + + mockECRClient.promise // listedImages() call + .mockResolvedValueOnce({ + imageIds: [ + { + imageTag: 'tag1', + imageDigest: 'sha256-1', + }, + { + imageTag: 'tag2', + imageDigest: 'sha256-2', + }, + ], + nextToken: 'token1', + }) + .mockResolvedValueOnce(undefined) // batchDeleteImage() call + .mockResolvedValueOnce({ // listedImages() call + imageIds: [ + { + imageTag: 'tag3', + imageDigest: 'sha256-3', + }, + { + imageTag: 'tag4', + imageDigest: 'sha256-4', + }, + ], + }); + + // WHEN + const event: Partial = { + RequestType: 'Delete', + ResourceProperties: { + ServiceToken: 'Foo', + RepositoryName: 'MyRepo', + }, + }; + await invokeHandler(event); + + // THEN + expect(mockECRClient.describeRepositories).toHaveBeenCalledTimes(1); + expect(mockECRClient.listImages).toHaveBeenCalledTimes(2); + expect(mockECRClient.listImages).toHaveBeenCalledWith({ repositoryName: 'MyRepo' }); + expect(mockECRClient.batchDeleteImage).toHaveBeenCalledTimes(2); + expect(mockECRClient.batchDeleteImage).toHaveBeenNthCalledWith(1, { + repositoryName: 'MyRepo', + imageIds: [ + { + imageTag: 'tag1', + imageDigest: 'sha256-1', + }, + { + imageTag: 'tag2', + imageDigest: 'sha256-2', + }, + ], + }); + expect(mockECRClient.batchDeleteImage).toHaveBeenNthCalledWith(2, { + repositoryName: 'MyRepo', + imageIds: [ + { + imageTag: 'tag3', + imageDigest: 'sha256-3', + }, + { + imageTag: 'tag4', + imageDigest: 'sha256-4', + }, + ], + }); +}); + +test('does nothing when the repository does not exist', async () => { + // GIVEN + mockECRClient.promise.mockRejectedValue({ name: 'RepositoryNotFoundException' }); + + mockAwsPromise(mockECRClient.describeRepositories, { + repositories: [ + { repositoryArn: 'RepositoryArn', respositoryName: 'MyRepo' }, + ], + }); + + // WHEN + const event: Partial = { + RequestType: 'Delete', + ResourceProperties: { + ServiceToken: 'Foo', + RepositoryName: 'MyRepo', + }, + }; + await invokeHandler(event); + + expect(mockECRClient.batchDeleteImage).not.toHaveBeenCalled(); +}); + +// helper function to get around TypeScript expecting a complete event object, +// even though our tests only need some of the fields +async function invokeHandler(event: Partial) { + return handler(event as AWSLambda.CloudFormationCustomResourceEvent); +} + +function mockAwsPromise(fn: jest.Mock, value: A, when: 'once' | 'always' = 'always') { + (when === 'always' ? fn.mockReturnValue : fn.mockReturnValueOnce).call(fn, { + promise: () => value, + }); +} + +function givenTaggedForDeletion() { + mockAwsPromise(mockECRClient.listTagsForResource, { + tags: [ + { + Key: 'aws-cdk:auto-delete-images', + Value: 'true', + }, + ], + + }); +} + +function givenNotTaggedForDeletion() { + mockAwsPromise(mockECRClient.listTagsForResource, { + tags: [], + }); +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c/__entrypoint__.js b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c/__entrypoint__.js new file mode 100644 index 0000000000000..1e3a3093c1706 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c/__entrypoint__.js @@ -0,0 +1,144 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.withRetries = exports.handler = exports.external = void 0; +const https = require("https"); +const url = require("url"); +// for unit tests +exports.external = { + sendHttpRequest: defaultSendHttpRequest, + log: defaultLog, + includeStackTraces: true, + userHandlerIndex: './index', +}; +const CREATE_FAILED_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::CREATE_FAILED'; +const MISSING_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID'; +async function handler(event, context) { + const sanitizedEvent = { ...event, ResponseURL: '...' }; + exports.external.log(JSON.stringify(sanitizedEvent, undefined, 2)); + // ignore DELETE event when the physical resource ID is the marker that + // indicates that this DELETE is a subsequent DELETE to a failed CREATE + // operation. + if (event.RequestType === 'Delete' && event.PhysicalResourceId === CREATE_FAILED_PHYSICAL_ID_MARKER) { + exports.external.log('ignoring DELETE event caused by a failed CREATE event'); + await submitResponse('SUCCESS', event); + return; + } + try { + // invoke the user handler. this is intentionally inside the try-catch to + // ensure that if there is an error it's reported as a failure to + // cloudformation (otherwise cfn waits). + // eslint-disable-next-line @typescript-eslint/no-require-imports + const userHandler = require(exports.external.userHandlerIndex).handler; + const result = await userHandler(sanitizedEvent, context); + // validate user response and create the combined event + const responseEvent = renderResponse(event, result); + // submit to cfn as success + await submitResponse('SUCCESS', responseEvent); + } + catch (e) { + const resp = { + ...event, + Reason: exports.external.includeStackTraces ? e.stack : e.message, + }; + if (!resp.PhysicalResourceId) { + // special case: if CREATE fails, which usually implies, we usually don't + // have a physical resource id. in this case, the subsequent DELETE + // operation does not have any meaning, and will likely fail as well. to + // address this, we use a marker so the provider framework can simply + // ignore the subsequent DELETE. + if (event.RequestType === 'Create') { + exports.external.log('CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored'); + resp.PhysicalResourceId = CREATE_FAILED_PHYSICAL_ID_MARKER; + } + else { + // otherwise, if PhysicalResourceId is not specified, something is + // terribly wrong because all other events should have an ID. + exports.external.log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(event)}`); + } + } + // this is an actual error, fail the activity altogether and exist. + await submitResponse('FAILED', resp); + } +} +exports.handler = handler; +function renderResponse(cfnRequest, handlerResponse = {}) { + // if physical ID is not returned, we have some defaults for you based + // on the request type. + const physicalResourceId = handlerResponse.PhysicalResourceId ?? cfnRequest.PhysicalResourceId ?? cfnRequest.RequestId; + // if we are in DELETE and physical ID was changed, it's an error. + if (cfnRequest.RequestType === 'Delete' && physicalResourceId !== cfnRequest.PhysicalResourceId) { + throw new Error(`DELETE: cannot change the physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${handlerResponse.PhysicalResourceId}" during deletion`); + } + // merge request event and result event (result prevails). + return { + ...cfnRequest, + ...handlerResponse, + PhysicalResourceId: physicalResourceId, + }; +} +async function submitResponse(status, event) { + const json = { + Status: status, + Reason: event.Reason ?? status, + StackId: event.StackId, + RequestId: event.RequestId, + PhysicalResourceId: event.PhysicalResourceId || MISSING_PHYSICAL_ID_MARKER, + LogicalResourceId: event.LogicalResourceId, + NoEcho: event.NoEcho, + Data: event.Data, + }; + exports.external.log('submit response to cloudformation', json); + const responseBody = JSON.stringify(json); + const parsedUrl = url.parse(event.ResponseURL); + const req = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: 'PUT', + headers: { 'content-type': '', 'content-length': responseBody.length }, + }; + const retryOptions = { + attempts: 5, + sleep: 1000, + }; + await withRetries(retryOptions, exports.external.sendHttpRequest)(req, responseBody); +} +async function defaultSendHttpRequest(options, responseBody) { + return new Promise((resolve, reject) => { + try { + const request = https.request(options, _ => resolve()); + request.on('error', reject); + request.write(responseBody); + request.end(); + } + catch (e) { + reject(e); + } + }); +} +function defaultLog(fmt, ...params) { + // eslint-disable-next-line no-console + console.log(fmt, ...params); +} +function withRetries(options, fn) { + return async (...xs) => { + let attempts = options.attempts; + let ms = options.sleep; + while (true) { + try { + return await fn(...xs); + } + catch (e) { + if (attempts-- <= 0) { + throw e; + } + await sleep(Math.floor(Math.random() * ms)); + ms *= 2; + } + } + }; +} +exports.withRetries = withRetries; +async function sleep(ms) { + return new Promise((ok) => setTimeout(ok, ms)); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZWpzLWVudHJ5cG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJub2RlanMtZW50cnlwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwrQkFBK0I7QUFDL0IsMkJBQTJCO0FBRTNCLGlCQUFpQjtBQUNKLFFBQUEsUUFBUSxHQUFHO0lBQ3RCLGVBQWUsRUFBRSxzQkFBc0I7SUFDdkMsR0FBRyxFQUFFLFVBQVU7SUFDZixrQkFBa0IsRUFBRSxJQUFJO0lBQ3hCLGdCQUFnQixFQUFFLFNBQVM7Q0FDNUIsQ0FBQztBQUVGLE1BQU0sZ0NBQWdDLEdBQUcsd0RBQXdELENBQUM7QUFDbEcsTUFBTSwwQkFBMEIsR0FBRyw4REFBOEQsQ0FBQztBQVczRixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtELEVBQUUsT0FBMEI7SUFDMUcsTUFBTSxjQUFjLEdBQUcsRUFBRSxHQUFHLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDeEQsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFM0QsdUVBQXVFO0lBQ3ZFLHVFQUF1RTtJQUN2RSxhQUFhO0lBQ2IsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsa0JBQWtCLEtBQUssZ0NBQWdDLEVBQUU7UUFDbkcsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsdURBQXVELENBQUMsQ0FBQztRQUN0RSxNQUFNLGNBQWMsQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDdkMsT0FBTztLQUNSO0lBRUQsSUFBSTtRQUNGLHlFQUF5RTtRQUN6RSxpRUFBaUU7UUFDakUsd0NBQXdDO1FBQ3hDLGlFQUFpRTtRQUNqRSxNQUFNLFdBQVcsR0FBWSxPQUFPLENBQUMsZ0JBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLE9BQU8sQ0FBQztRQUN4RSxNQUFN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RUFBRSxFQUFFO1FBQ3hCLElBQUksUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUM7UUFDaEMsSUFBSSxFQUFFLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQztRQUN2QixPQUFPLElBQUksRUFBRTtZQUNYLElBQUk7Z0JBQ0YsT0FBTyxNQUFNLEVBQUUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDO2FBQ3hCO1lBQUMsT0FBTyxDQUFDLEVBQUU7Z0JBQ1YsSUFBSSxRQUFRLEVBQUUsSUFBSSxDQUFDLEVBQUU7b0JBQ25CLE1BQU0sQ0FBQyxDQUFDO2lCQUNUO2dCQUNELE1BQU0sS0FBSyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7Z0JBQzVDLEVBQUUsSUFBSSxDQUFDLENBQUM7YUFDVDtTQUNGO0lBQ0gsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQWhCRCxrQ0FnQkM7QUFFRCxLQUFLLFVBQVUsS0FBSyxDQUFDLEVBQVU7SUFDN0IsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ2pELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBodHRwcyBmcm9tICdodHRwcyc7XG5pbXBvcnQgKiBhcyB1cmwgZnJvbSAndXJsJztcblxuLy8gZm9yIHVuaXQgdGVzdHNcbmV4cG9ydCBjb25zdCBleHRlcm5hbCA9IHtcbiAgc2VuZEh0dHBSZXF1ZXN0OiBkZWZhdWx0U2VuZEh0dHBSZXF1ZXN0LFxuICBsb2c6IGRlZmF1bHRMb2csXG4gIGluY2x1ZGVTdGFja1RyYWNlczogdHJ1ZSxcbiAgdXNlckhhbmRsZXJJbmRleDogJy4vaW5kZXgnLFxufTtcblxuY29uc3QgQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVIgPSAnQVdTQ0RLOjpDdXN0b21SZXNvdXJjZVByb3ZpZGVyRnJhbWV3b3JrOjpDUkVBVEVfRkFJTEVEJztcbmNvbnN0IE1JU1NJTkdfUEhZU0lDQUxfSURfTUFSS0VSID0gJ0FXU0NESzo6Q3VzdG9tUmVzb3VyY2VQcm92aWRlckZyYW1ld29yazo6TUlTU0lOR19QSFlTSUNBTF9JRCc7XG5cbmV4cG9ydCB0eXBlIFJlc3BvbnNlID0gQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCAmIEhhbmRsZXJSZXNwb25zZTtcbmV4cG9ydCB0eXBlIEhhbmRsZXIgPSAoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQsIGNvbnRleHQ6IEFXU0xhbWJkYS5Db250ZXh0KSA9PiBQcm9taXNlPEhhbmRsZXJSZXNwb25zZSB8IHZvaWQ+O1xuZXhwb3J0IHR5cGUgSGFuZGxlclJlc3BvbnNlID0gdW5kZWZpbmVkIHwge1xuICBEYXRhPzogYW55O1xuICBQaHlzaWNhbFJlc291cmNlSWQ/OiBzdHJpbmc7XG4gIFJlYXNvbj86IHN0cmluZztcbiAgTm9FY2hvPzogYm9vbGVhbjtcbn07XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50LCBjb250ZXh0OiBBV1NMYW1iZGEuQ29udGV4dCkge1xuICBjb25zdCBzYW5pdGl6ZWRFdmVudCA9IHsgLi4uZXZlbnQsIFJlc3BvbnNlVVJMOiAnLi4uJyB9O1xuICBleHRlcm5hbC5sb2coSlNPTi5zdHJpbmdpZnkoc2FuaXRpemVkRXZlbnQsIHVuZGVmaW5lZCwgMikpO1xuXG4gIC8vIGlnbm9yZSBERUxFVEUgZXZlbnQgd2hlbiB0aGUgcGh5c2ljYWwgcmVzb3VyY2UgSUQgaXMgdGhlIG1hcmtlciB0aGF0XG4gIC8vIGluZGljYXRlcyB0aGF0IHRoaXMgREVMRVRFIGlzIGEgc3Vic2VxdWVudCBERUxFVEUgdG8gYSBmYWlsZWQgQ1JFQVRFXG4gIC8vIG9wZXJhdGlvbi5cbiAgaWYgKGV2ZW50LlJlcXVlc3RUeXBlID09PSAnRGVsZXRlJyAmJiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgPT09IENSRUFURV9GQUlMRURfUEhZU0lDQUxfSURfTUFSS0VSKSB7XG4gICAgZXh0ZXJuYWwubG9nKCdpZ25vcmluZyBERUxFVEUgZXZlbnQgY2F1c2VkIGJ5IGEgZmFpbGVkIENSRUFURSBldmVudCcpO1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgZXZlbnQpO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIHRyeSB7XG4gICAgLy8gaW52b2tlIHRoZSB1c2VyIGhhbmRsZXIuIHRoaXMgaXMgaW50ZW50aW9uYWxseSBpbnNpZGUgdGhlIHRyeS1jYXRjaCB0b1xuICAgIC8vIGVuc3VyZSB0aGF0IGlmIHRoZXJlIGlzIGFuIGVycm9yIGl0J3MgcmVwb3J0ZWQgYXMgYSBmYWlsdXJlIHRvXG4gICAgLy8gY2xvdWRmb3JtYXRpb24gKG90aGVyd2lzZSBjZm4gd2FpdHMpLlxuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tcmVxdWlyZS1pbXBvcnRzXG4gICAgY29uc3QgdXNlckhhbmRsZXI6IEhhbmRsZXIgPSByZXF1aXJlKGV4dGVybmFsLnVzZXJIYW5kbGVySW5kZXgpLmhhbmRsZXI7XG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdXNlckhhbmRsZXIoc2FuaXRpemVkRXZlbnQsIGNvbnRleHQpO1xuXG4gICAgLy8gdmFsaWRhdGUgdXNlciByZXNwb25zZSBhbmQgY3JlYXRlIHRoZSBjb21iaW5lZCBldmVudFxuICAgIGNvbnN0IHJlc3BvbnNlRXZlbnQgPSByZW5kZXJSZXNwb25zZShldmVudCwgcmVzdWx0KTtcblxuICAgIC8vIHN1Ym1pdCB0byBjZm4gYXMgc3VjY2Vzc1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgcmVzcG9uc2VFdmVudCk7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBjb25zdCByZXNwOiBSZXNwb25zZSA9IHtcbiAgICAgIC4uLmV2ZW50LFxuICAgICAgUmVhc29uOiBleHRlcm5hbC5pbmNsdWRlU3RhY2tUcmFjZXMgPyBlLnN0YWNrIDogZS5tZXNzYWdlLFxuICAgIH07XG5cbiAgICBpZiAoIXJlc3AuUGh5c2ljYWxSZXNvdXJjZUlkKSB7XG4gICAgICAvLyBzcGVjaWFsIGNhc2U6IGlmIENSRUFURSBmYWlscywgd2hpY2ggdXN1YWxseSBpbXBsaWVzLCB3ZSB1c3VhbGx5IGRvbid0XG4gICAgICAvLyBoYXZlIGEgcGh5c2ljYWwgcmVzb3VyY2UgaWQuIGluIHRoaXMgY2FzZSwgdGhlIHN1YnNlcXVlbnQgREVMRVRFXG4gICAgICAvLyBvcGVyYXRpb24gZG9lcyBub3QgaGF2ZSBhbnkgbWVhbmluZywgYW5kIHdpbGwgbGlrZWx5IGZhaWwgYXMgd2VsbC4gdG9cbiAgICAgIC8vIGFkZHJlc3MgdGhpcywgd2UgdXNlIGEgbWFya2VyIHNvIHRoZSBwcm92aWRlciBmcmFtZXdvcmsgY2FuIHNpbXBseVxuICAgICAgLy8gaWdub3JlIHRoZSBzdWJzZXF1ZW50IERFTEVURS5cbiAgICAgIGlmIChldmVudC5SZXF1ZXN0VHlwZSA9PT0gJ0NyZWF0ZScpIHtcbiAgICAgICAgZXh0ZXJuYWwubG9nKCdDUkVBVEUgZmFpbGVkLCByZXNwb25kaW5nIHdpdGggYSBtYXJrZXIgcGh5c2ljYWwgcmVzb3VyY2UgaWQgc28gdGhhdCB0aGUgc3Vic2VxdWVudCBERUxFVEUgd2lsbCBiZSBpZ25vcmVkJyk7XG4gICAgICAgIHJlc3AuUGh5c2ljYWxSZXNvdXJjZUlkID0gQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVI7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBvdGhlcndpc2UsIGlmIFBoeXNpY2FsUmVzb3VyY2VJZCBpcyBub3Qgc3BlY2lmaWVkLCBzb21ldGhpbmcgaXNcbiAgICAgICAgLy8gdGVycmlibHkgd3JvbmcgYmVjYXVzZSBhbGwgb3RoZXIgZXZlbnRzIHNob3VsZCBoYXZlIGFuIElELlxuICAgICAgICBleHRlcm5hbC5sb2coYEVSUk9SOiBNYWxmb3JtZWQgZXZlbnQuIFwiUGh5c2ljYWxSZXNvdXJjZUlkXCIgaXMgcmVxdWlyZWQ6ICR7SlNPTi5zdHJpbmdpZnkoZXZlbnQpfWApO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIHRoaXMgaXMgYW4gYWN0dWFsIGVycm9yLCBmYWlsIHRoZSBhY3Rpdml0eSBhbHRvZ2V0aGVyIGFuZCBleGlzdC5cbiAgICBhd2FpdCBzdWJtaXRSZXNwb25zZSgnRkFJTEVEJywgcmVzcCk7XG4gIH1cbn1cblxuZnVuY3Rpb24gcmVuZGVyUmVzcG9uc2UoXG4gIGNmblJlcXVlc3Q6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQgJiB7IFBoeXNpY2FsUmVzb3VyY2VJZD86IHN0cmluZyB9LFxuICBoYW5kbGVyUmVzcG9uc2U6IHZvaWQgfCBIYW5kbGVyUmVzcG9uc2UgPSB7IH0pOiBSZXNwb25zZSB7XG5cbiAgLy8gaWYgcGh5c2ljYWwgSUQgaXMgbm90IHJldHVybmVkLCB3ZSBoYXZlIHNvbWUgZGVmYXVsdHMgZm9yIHlvdSBiYXNlZFxuICAvLyBvbiB0aGUgcmVxdWVzdCB0eXBlLlxuICBjb25zdCBwaHlzaWNhbFJlc291cmNlSWQgPSBoYW5kbGVyUmVzcG9uc2UuUGh5c2ljYWxSZXNvdXJjZUlkID8/IGNmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkID8/IGNmblJlcXVlc3QuUmVxdWVzdElkO1xuXG4gIC8vIGlmIHdlIGFyZSBpbiBERUxFVEUgYW5kIHBoeXNpY2FsIElEIHdhcyBjaGFuZ2VkLCBpdCdzIGFuIGVycm9yLlxuICBpZiAoY2ZuUmVxdWVzdC5SZXF1ZXN0VHlwZSA9PT0gJ0RlbGV0ZScgJiYgcGh5c2ljYWxSZXNvdXJjZUlkICE9PSBjZm5SZXF1ZXN0LlBoeXNpY2FsUmVzb3VyY2VJZCkge1xuICAgIHRocm93IG5ldyBFcnJvcihgREVMRVRFOiBjYW5ub3QgY2hhbmdlIHRoZSBwaHlzaWNhbCByZXNvdXJjZSBJRCBmcm9tIFwiJHtjZm5SZXF1ZXN0LlBoeXNpY2FsUmVzb3VyY2VJZH1cIiB0byBcIiR7aGFuZGxlclJlc3BvbnNlLlBoeXNpY2FsUmVzb3VyY2VJZH1cIiBkdXJpbmcgZGVsZXRpb25gKTtcbiAgfVxuXG4gIC8vIG1lcmdlIHJlcXVlc3QgZXZlbnQgYW5kIHJlc3VsdCBldmVudCAocmVzdWx0IHByZXZhaWxzKS5cbiAgcmV0dXJuIHtcbiAgICAuLi5jZm5SZXF1ZXN0LFxuICAgIC4uLmhhbmRsZXJSZXNwb25zZSxcbiAgICBQaHlzaWNhbFJlc291cmNlSWQ6IHBoeXNpY2FsUmVzb3VyY2VJZCxcbiAgfTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gc3VibWl0UmVzcG9uc2Uoc3RhdHVzOiAnU1VDQ0VTUycgfCAnRkFJTEVEJywgZXZlbnQ6IFJlc3BvbnNlKSB7XG4gIGNvbnN0IGpzb246IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlUmVzcG9uc2UgPSB7XG4gICAgU3RhdHVzOiBzdGF0dXMsXG4gICAgUmVhc29uOiBldmVudC5SZWFzb24gPz8gc3RhdHVzLFxuICAgIFN0YWNrSWQ6IGV2ZW50LlN0YWNrSWQsXG4gICAgUmVxdWVzdElkOiBldmVudC5SZXF1ZXN0SWQsXG4gICAgUGh5c2ljYWxSZXNvdXJjZUlkOiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgfHwgTUlTU0lOR19QSFlTSUNBTF9JRF9NQVJLRVIsXG4gICAgTG9naWNhbFJlc291cmNlSWQ6IGV2ZW50LkxvZ2ljYWxSZXNvdXJjZUlkLFxuICAgIE5vRWNobzogZXZlbnQuTm9FY2hvLFxuICAgIERhdGE6IGV2ZW50LkRhdGEsXG4gIH07XG5cbiAgZXh0ZXJuYWwubG9nKCdzdWJtaXQgcmVzcG9uc2UgdG8gY2xvdWRmb3JtYXRpb24nLCBqc29uKTtcblxuICBjb25zdCByZXNwb25zZUJvZHkgPSBKU09OLnN0cmluZ2lmeShqc29uKTtcbiAgY29uc3QgcGFyc2VkVXJsID0gdXJsLnBhcnNlKGV2ZW50LlJlc3BvbnNlVVJMKTtcbiAgY29uc3QgcmVxID0ge1xuICAgIGhvc3RuYW1lOiBwYXJzZWRVcmwuaG9zdG5hbWUsXG4gICAgcGF0aDogcGFyc2VkVXJsLnBhdGgsXG4gICAgbWV0aG9kOiAnUFVUJyxcbiAgICBoZWFkZXJzOiB7ICdjb250ZW50LXR5cGUnOiAnJywgJ2NvbnRlbnQtbGVuZ3RoJzogcmVzcG9uc2VCb2R5Lmxlbmd0aCB9LFxuICB9O1xuXG4gIGNvbnN0IHJldHJ5T3B0aW9ucyA9IHtcbiAgICBhdHRlbXB0czogNSxcbiAgICBzbGVlcDogMTAwMCxcbiAgfTtcbiAgYXdhaXQgd2l0aFJldHJpZXMocmV0cnlPcHRpb25zLCBleHRlcm5hbC5zZW5kSHR0cFJlcXVlc3QpKHJlcSwgcmVzcG9uc2VCb2R5KTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZGVmYXVsdFNlbmRIdHRwUmVxdWVzdChvcHRpb25zOiBodHRwcy5SZXF1ZXN0T3B0aW9ucywgcmVzcG9uc2VCb2R5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgcmVxdWVzdCA9IGh0dHBzLnJlcXVlc3Qob3B0aW9ucywgXyA9PiByZXNvbHZlKCkpO1xuICAgICAgcmVxdWVzdC5vbignZXJyb3InLCByZWplY3QpO1xuICAgICAgcmVxdWVzdC53cml0ZShyZXNwb25zZUJvZHkpO1xuICAgICAgcmVxdWVzdC5lbmQoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICByZWplY3QoZSk7XG4gICAgfVxuICB9KTtcbn1cblxuZnVuY3Rpb24gZGVmYXVsdExvZyhmbXQ6IHN0cmluZywgLi4ucGFyYW1zOiBhbnlbXSkge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuICBjb25zb2xlLmxvZyhmbXQsIC4uLnBhcmFtcyk7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmV0cnlPcHRpb25zIHtcbiAgLyoqIEhvdyBtYW55IHJldHJpZXMgKHdpbGwgYXQgbGVhc3QgdHJ5IG9uY2UpICovXG4gIHJlYWRvbmx5IGF0dGVtcHRzOiBudW1iZXI7XG4gIC8qKiBTbGVlcCBiYXNlLCBpbiBtcyAqL1xuICByZWFkb25seSBzbGVlcDogbnVtYmVyO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gd2l0aFJldHJpZXM8QSBleHRlbmRzIEFycmF5PGFueT4sIEI+KG9wdGlvbnM6IFJldHJ5T3B0aW9ucywgZm46ICguLi54czogQSkgPT4gUHJvbWlzZTxCPik6ICguLi54czogQSkgPT4gUHJvbWlzZTxCPiB7XG4gIHJldHVybiBhc3luYyAoLi4ueHM6IEEpID0+IHtcbiAgICBsZXQgYXR0ZW1wdHMgPSBvcHRpb25zLmF0dGVtcHRzO1xuICAgIGxldCBtcyA9IG9wdGlvbnMuc2xlZXA7XG4gICAgd2hpbGUgKHRydWUpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBhd2FpdCBmbiguLi54cyk7XG4gICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGlmIChhdHRlbXB0cy0tIDw9IDApIHtcbiAgICAgICAgICB0aHJvdyBlO1xuICAgICAgICB9XG4gICAgICAgIGF3YWl0IHNsZWVwKE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIG1zKSk7XG4gICAgICAgIG1zICo9IDI7XG4gICAgICB9XG4gICAgfVxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBzbGVlcChtczogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgob2spID0+IHNldFRpbWVvdXQob2ssIG1zKSk7XG59Il19 \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c/index.js b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c/index.js new file mode 100644 index 0000000000000..c7fa666848ee8 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c/index.js @@ -0,0 +1,83 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handler = void 0; +// eslint-disable-next-line import/no-extraneous-dependencies +const aws_sdk_1 = require("aws-sdk"); +const AUTO_DELETE_IMAGES_TAG = 'aws-cdk:auto-delete-images'; +const ecr = new aws_sdk_1.ECR(); +async function handler(event) { + switch (event.RequestType) { + case 'Create': + case 'Update': + return onUpdate(event); + case 'Delete': + return onDelete(event.ResourceProperties?.RepositoryName, event.ResourceProperties?.RepositoryArn); + } +} +exports.handler = handler; +async function onUpdate(event) { + const updateEvent = event; + const oldRepositoryName = updateEvent.OldResourceProperties?.RepositoryName; + const newRepositoryName = updateEvent.ResourceProperties?.RepositoryName; + const repositoryNameHasChanged = newRepositoryName != null && oldRepositoryName != null && newRepositoryName !== oldRepositoryName; + /* If the name of the repository has changed, CloudFormation will try to delete the repository + and create a new one with the new name. So we have to delete the images in the + repository so that this operation does not fail. */ + if (repositoryNameHasChanged) { + return onDelete(oldRepositoryName, updateEvent.OldResourceProperties?.RepositoryArn); + } +} +/** + * Recursively delete all images in the repository + * + * @param ECR.ListImagesRequest the repositoryName & nextToken if presented + */ +async function emptyRepository(params) { + const listedImages = await ecr.listImages(params).promise(); + const imageIds = listedImages?.imageIds ?? []; + const nextToken = listedImages.nextToken ?? null; + if (imageIds.length === 0) { + return; + } + await ecr.batchDeleteImage({ + repositoryName: params.repositoryName, + imageIds, + }).promise(); + if (nextToken) { + await emptyRepository({ + ...params, + nextToken, + }); + } +} +async function onDelete(repositoryName, repositoryArn) { + if (!repositoryName) { + throw new Error('No RepositoryName was provided.'); + } + if (!await isRepositoryTaggedForDeletion(repositoryArn)) { + process.stdout.write(`Repository does not have '${AUTO_DELETE_IMAGES_TAG}' tag, skipping cleaning.\n`); + return; + } + try { + await emptyRepository({ repositoryName }); + } + catch (e) { + if (e.name !== 'RepositoryNotFoundException') { + throw e; + } + // Repository doesn't exist. Ignoring + } +} +/** + * The repository will only be tagged for deletion if it's being deleted in the same + * deployment as this Custom Resource. + * + * If the Custom Resource is ever deleted before the repository, it must be because + * `autoDeleteImages` has been switched to false, in which case the tag would have + * been removed before we get to this Delete event. + */ +async function isRepositoryTaggedForDeletion(repositoryArn) { + const response = await ecr.listTagsForResource({ resourceArn: repositoryArn }).promise(); + return response.tags?.some(tag => tag.Key === AUTO_DELETE_IMAGES_TAG && tag.Value === 'true'); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2REFBNkQ7QUFDN0QscUNBQThCO0FBRTlCLE1BQU0sc0JBQXNCLEdBQUcsNEJBQTRCLENBQUM7QUFFNUQsTUFBTSxHQUFHLEdBQUcsSUFBSSxhQUFHLEVBQUUsQ0FBQztBQUVmLEtBQUssVUFBVSxPQUFPLENBQUMsS0FBa0Q7SUFDOUUsUUFBUSxLQUFLLENBQUMsV0FBVyxFQUFFO1FBQ3pCLEtBQUssUUFBUSxDQUFDO1FBQ2QsS0FBSyxRQUFRO1lBQ1gsT0FBTyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDekIsS0FBSyxRQUFRO1lBQ1gsT0FBTyxRQUFRLENBQUMsS0FBSyxDQUFDLGtCQUFrQixFQUFFLGNBQWMsRUFBRSxLQUFLLENBQUMsa0JBQWtCLEVBQUUsYUFBYSxDQUFDLENBQUM7S0FDdEc7QUFDSCxDQUFDO0FBUkQsMEJBUUM7QUFFRCxLQUFLLFVBQVUsUUFBUSxDQUFDLEtBQWtEO0lBQ3hFLE1BQU0sV0FBVyxHQUFHLEtBQTBELENBQUM7SUFDL0UsTUFBTSxpQkFBaUIsR0FBRyxXQUFXLENBQUMscUJBQXFCLEVBQUUsY0FBYyxDQUFDO0lBQzVFLE1BQU0saUJBQWlCLEdBQUcsV0FBVyxDQUFDLGtCQUFrQixFQUFFLGNBQWMsQ0FBQztJQUN6RSxNQUFNLHdCQUF3QixHQUFHLGlCQUFpQixJQUFJLElBQUksSUFBSSxpQkFBaUIsSUFBSSxJQUFJLElBQUksaUJBQWlCLEtBQUssaUJBQWlCLENBQUM7SUFFbkk7OzBEQUVzRDtJQUN0RCxJQUFJLHdCQUF3QixFQUFFO1FBQzVCLE9BQU8sUUFBUSxDQUFDLGlCQUFpQixFQUFFLFdBQVcsQ0FBQyxxQkFBcUIsRUFBRSxhQUFhLENBQUMsQ0FBQztLQUN0RjtBQUNILENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsS0FBSyxVQUFVLGVBQWUsQ0FBQyxNQUE2QjtJQUMxRCxNQUFNLFlBQVksR0FBRyxNQUFNLEdBQUcsQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7SUFFNUQsTUFBTSxRQUFRLEdBQUcsWUFBWSxFQUFFLFFBQVEsSUFBSSxFQUFFLENBQUM7SUFDOUMsTUFBTSxTQUFTLEdBQUcsWUFBWSxDQUFDLFNBQVMsSUFBSSxJQUFJLENBQUM7SUFDakQsSUFBSSxRQUFRLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRTtRQUN6QixPQUFPO0tBQ1I7SUFFRCxNQUFNLEdBQUcsQ0FBQyxnQkFBZ0IsQ0FBQztRQUN6QixjQUFjLEVBQUUsTUFBTSxDQUFDLGNBQWM7UUFDckMsUUFBUTtLQUNULENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUViLElBQUksU0FBUyxFQUFFO1FBQ2IsTUFBTSxlQUFlLENBQUM7WUFDcEIsR0FBRyxNQUFNO1lBQ1QsU0FBUztTQUNWLENBQUMsQ0FBQztLQUNKO0FBQ0gsQ0FBQztBQUVELEtBQUssVUFBVSxRQUFRLENBQUMsY0FBc0IsRUFBRSxhQUFxQjtJQUNuRSxJQUFJLENBQUMsY0FBYyxFQUFFO1FBQ25CLE1BQU0sSUFBSSxLQUFLLENBQUMsaUNBQWlDLENBQUMsQ0FBQztLQUNwRDtJQUVELElBQUksQ0FBQyxNQUFNLDZCQUE2QixDQUFDLGFBQWEsQ0FBQyxFQUFFO1FBQ3ZELE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLDZCQUE2QixzQkFBc0IsNkJBQTZCLENBQUMsQ0FBQztRQUN2RyxPQUFPO0tBQ1I7SUFDRCxJQUFJO1FBQ0YsTUFBTSxlQUFlLENBQUMsRUFBRSxjQUFjLEVBQUUsQ0FBQyxDQUFDO0tBQzNDO0lBQUMsT0FBTyxDQUFDLEVBQUU7UUFDVixJQUFJLENBQUMsQ0FBQyxJQUFJLEtBQUssNkJBQTZCLEVBQUU7WUFDNUMsTUFBTSxDQUFDLENBQUM7U0FDVDtRQUNELHFDQUFxQztLQUN0QztBQUNILENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsS0FBSyxVQUFVLDZCQUE2QixDQUFDLGFBQXFCO0lBQ2hFLE1BQU0sUUFBUSxHQUFHLE1BQU0sR0FBRyxDQUFDLG1CQUFtQixDQUFDLEVBQUUsV0FBVyxFQUFFLGFBQWEsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDekYsT0FBTyxRQUFRLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEtBQUssc0JBQXNCLElBQUksR0FBRyxDQUFDLEtBQUssS0FBSyxNQUFNLENBQUMsQ0FBQztBQUNoRyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGltcG9ydC9uby1leHRyYW5lb3VzLWRlcGVuZGVuY2llc1xuaW1wb3J0IHsgRUNSIH0gZnJvbSAnYXdzLXNkayc7XG5cbmNvbnN0IEFVVE9fREVMRVRFX0lNQUdFU19UQUcgPSAnYXdzLWNkazphdXRvLWRlbGV0ZS1pbWFnZXMnO1xuXG5jb25zdCBlY3IgPSBuZXcgRUNSKCk7XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50KSB7XG4gIHN3aXRjaCAoZXZlbnQuUmVxdWVzdFR5cGUpIHtcbiAgICBjYXNlICdDcmVhdGUnOlxuICAgIGNhc2UgJ1VwZGF0ZSc6XG4gICAgICByZXR1cm4gb25VcGRhdGUoZXZlbnQpO1xuICAgIGNhc2UgJ0RlbGV0ZSc6XG4gICAgICByZXR1cm4gb25EZWxldGUoZXZlbnQuUmVzb3VyY2VQcm9wZXJ0aWVzPy5SZXBvc2l0b3J5TmFtZSwgZXZlbnQuUmVzb3VyY2VQcm9wZXJ0aWVzPy5SZXBvc2l0b3J5QXJuKTtcbiAgfVxufVxuXG5hc3luYyBmdW5jdGlvbiBvblVwZGF0ZShldmVudDogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCkge1xuICBjb25zdCB1cGRhdGVFdmVudCA9IGV2ZW50IGFzIEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlVXBkYXRlRXZlbnQ7XG4gIGNvbnN0IG9sZFJlcG9zaXRvcnlOYW1lID0gdXBkYXRlRXZlbnQuT2xkUmVzb3VyY2VQcm9wZXJ0aWVzPy5SZXBvc2l0b3J5TmFtZTtcbiAgY29uc3QgbmV3UmVwb3NpdG9yeU5hbWUgPSB1cGRhdGVFdmVudC5SZXNvdXJjZVByb3BlcnRpZXM/LlJlcG9zaXRvcnlOYW1lO1xuICBjb25zdCByZXBvc2l0b3J5TmFtZUhhc0NoYW5nZWQgPSBuZXdSZXBvc2l0b3J5TmFtZSAhPSBudWxsICYmIG9sZFJlcG9zaXRvcnlOYW1lICE9IG51bGwgJiYgbmV3UmVwb3NpdG9yeU5hbWUgIT09IG9sZFJlcG9zaXRvcnlOYW1lO1xuXG4gIC8qIElmIHRoZSBuYW1lIG9mIHRoZSByZXBvc2l0b3J5IGhhcyBjaGFuZ2VkLCBDbG91ZEZvcm1hdGlvbiB3aWxsIHRyeSB0byBkZWxldGUgdGhlIHJlcG9zaXRvcnlcbiAgICAgYW5kIGNyZWF0ZSBhIG5ldyBvbmUgd2l0aCB0aGUgbmV3IG5hbWUuIFNvIHdlIGhhdmUgdG8gZGVsZXRlIHRoZSBpbWFnZXMgaW4gdGhlXG4gICAgIHJlcG9zaXRvcnkgc28gdGhhdCB0aGlzIG9wZXJhdGlvbiBkb2VzIG5vdCBmYWlsLiAqL1xuICBpZiAocmVwb3NpdG9yeU5hbWVIYXNDaGFuZ2VkKSB7XG4gICAgcmV0dXJuIG9uRGVsZXRlKG9sZFJlcG9zaXRvcnlOYW1lLCB1cGRhdGVFdmVudC5PbGRSZXNvdXJjZVByb3BlcnRpZXM/LlJlcG9zaXRvcnlBcm4pO1xuICB9XG59XG5cbi8qKlxuICogUmVjdXJzaXZlbHkgZGVsZXRlIGFsbCBpbWFnZXMgaW4gdGhlIHJlcG9zaXRvcnlcbiAqXG4gKiBAcGFyYW0gRUNSLkxpc3RJbWFnZXNSZXF1ZXN0IHRoZSByZXBvc2l0b3J5TmFtZSAmIG5leHRUb2tlbiBpZiBwcmVzZW50ZWRcbiAqL1xuYXN5bmMgZnVuY3Rpb24gZW1wdHlSZXBvc2l0b3J5KHBhcmFtczogRUNSLkxpc3RJbWFnZXNSZXF1ZXN0KSB7XG4gIGNvbnN0IGxpc3RlZEltYWdlcyA9IGF3YWl0IGVjci5saXN0SW1hZ2VzKHBhcmFtcykucHJvbWlzZSgpO1xuXG4gIGNvbnN0IGltYWdlSWRzID0gbGlzdGVkSW1hZ2VzPy5pbWFnZUlkcyA/PyBbXTtcbiAgY29uc3QgbmV4dFRva2VuID0gbGlzdGVkSW1hZ2VzLm5leHRUb2tlbiA/PyBudWxsO1xuICBpZiAoaW1hZ2VJZHMubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgYXdhaXQgZWNyLmJhdGNoRGVsZXRlSW1hZ2Uoe1xuICAgIHJlcG9zaXRvcnlOYW1lOiBwYXJhbXMucmVwb3NpdG9yeU5hbWUsXG4gICAgaW1hZ2VJZHMsXG4gIH0pLnByb21pc2UoKTtcblxuICBpZiAobmV4dFRva2VuKSB7XG4gICAgYXdhaXQgZW1wdHlSZXBvc2l0b3J5KHtcbiAgICAgIC4uLnBhcmFtcyxcbiAgICAgIG5leHRUb2tlbixcbiAgICB9KTtcbiAgfVxufVxuXG5hc3luYyBmdW5jdGlvbiBvbkRlbGV0ZShyZXBvc2l0b3J5TmFtZTogc3RyaW5nLCByZXBvc2l0b3J5QXJuOiBzdHJpbmcpIHtcbiAgaWYgKCFyZXBvc2l0b3J5TmFtZSkge1xuICAgIHRocm93IG5ldyBFcnJvcignTm8gUmVwb3NpdG9yeU5hbWUgd2FzIHByb3ZpZGVkLicpO1xuICB9XG5cbiAgaWYgKCFhd2FpdCBpc1JlcG9zaXRvcnlUYWdnZWRGb3JEZWxldGlvbihyZXBvc2l0b3J5QXJuKSkge1xuICAgIHByb2Nlc3Muc3Rkb3V0LndyaXRlKGBSZXBvc2l0b3J5IGRvZXMgbm90IGhhdmUgJyR7QVVUT19ERUxFVEVfSU1BR0VTX1RBR30nIHRhZywgc2tpcHBpbmcgY2xlYW5pbmcuXFxuYCk7XG4gICAgcmV0dXJuO1xuICB9XG4gIHRyeSB7XG4gICAgYXdhaXQgZW1wdHlSZXBvc2l0b3J5KHsgcmVwb3NpdG9yeU5hbWUgfSk7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBpZiAoZS5uYW1lICE9PSAnUmVwb3NpdG9yeU5vdEZvdW5kRXhjZXB0aW9uJykge1xuICAgICAgdGhyb3cgZTtcbiAgICB9XG4gICAgLy8gUmVwb3NpdG9yeSBkb2Vzbid0IGV4aXN0LiBJZ25vcmluZ1xuICB9XG59XG5cbi8qKlxuICogVGhlIHJlcG9zaXRvcnkgd2lsbCBvbmx5IGJlIHRhZ2dlZCBmb3IgZGVsZXRpb24gaWYgaXQncyBiZWluZyBkZWxldGVkIGluIHRoZSBzYW1lXG4gKiBkZXBsb3ltZW50IGFzIHRoaXMgQ3VzdG9tIFJlc291cmNlLlxuICpcbiAqIElmIHRoZSBDdXN0b20gUmVzb3VyY2UgaXMgZXZlciBkZWxldGVkIGJlZm9yZSB0aGUgcmVwb3NpdG9yeSwgaXQgbXVzdCBiZSBiZWNhdXNlXG4gKiBgYXV0b0RlbGV0ZUltYWdlc2AgaGFzIGJlZW4gc3dpdGNoZWQgdG8gZmFsc2UsIGluIHdoaWNoIGNhc2UgdGhlIHRhZyB3b3VsZCBoYXZlXG4gKiBiZWVuIHJlbW92ZWQgYmVmb3JlIHdlIGdldCB0byB0aGlzIERlbGV0ZSBldmVudC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gaXNSZXBvc2l0b3J5VGFnZ2VkRm9yRGVsZXRpb24ocmVwb3NpdG9yeUFybjogc3RyaW5nKSB7XG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZWNyLmxpc3RUYWdzRm9yUmVzb3VyY2UoeyByZXNvdXJjZUFybjogcmVwb3NpdG9yeUFybiB9KS5wcm9taXNlKCk7XG4gIHJldHVybiByZXNwb25zZS50YWdzPy5zb21lKHRhZyA9PiB0YWcuS2V5ID09PSBBVVRPX0RFTEVURV9JTUFHRVNfVEFHICYmIHRhZy5WYWx1ZSA9PT0gJ3RydWUnKTtcbn0iXX0= \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.assets.json b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.assets.json new file mode 100644 index 0000000000000..f31c7867c7d28 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.assets.json @@ -0,0 +1,32 @@ +{ + "version": "31.0.0", + "files": { + "0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c": { + "source": { + "path": "asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c", + "packaging": "zip" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c.zip", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "740719c7baa1a196b04e874646ded5b2a2e4d68894eddc1b18791366c3785922": { + "source": { + "path": "aws-ecr-integ-stack.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "740719c7baa1a196b04e874646ded5b2a2e4d68894eddc1b18791366c3785922.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/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.template.json b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.template.json new file mode 100644 index 0000000000000..243ba48b02b69 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.template.json @@ -0,0 +1,190 @@ +{ + "Resources": { + "Repo02AC86CF": { + "Type": "AWS::ECR::Repository", + "Properties": { + "RepositoryName": "delete-even-if-containing-images", + "RepositoryPolicyText": { + "Statement": [ + { + "Action": [ + "ecr:BatchDeleteImage", + "ecr:ListImages" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomECRAutoDeleteImagesCustomResourceProviderRole665F2773", + "Arn" + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "aws-cdk:auto-delete-images", + "Value": "true" + } + ] + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "RepoAutoDeleteImagesCustomResource65201E29": { + "Type": "Custom::ECRAutoDeleteImages", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "CustomECRAutoDeleteImagesCustomResourceProviderHandler8D89C030", + "Arn" + ] + }, + "RepositoryName": { + "Ref": "Repo02AC86CF" + } + }, + "DependsOn": [ + "Repo02AC86CF" + ], + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "CustomECRAutoDeleteImagesCustomResourceProviderRole665F2773": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ] + }, + "ManagedPolicyArns": [ + { + "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + } + ] + } + }, + "CustomECRAutoDeleteImagesCustomResourceProviderHandler8D89C030": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" + }, + "S3Key": "0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c.zip" + }, + "Timeout": 900, + "MemorySize": 128, + "Handler": "__entrypoint__.handler", + "Role": { + "Fn::GetAtt": [ + "CustomECRAutoDeleteImagesCustomResourceProviderRole665F2773", + "Arn" + ] + }, + "Runtime": "nodejs14.x", + "Description": "Lambda function for auto-deleting images in undefined repository." + }, + "DependsOn": [ + "CustomECRAutoDeleteImagesCustomResourceProviderRole665F2773" + ] + } + }, + "Outputs": { + "RepositoryURI": { + "Value": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 4, + { + "Fn::Split": [ + ":", + { + "Fn::GetAtt": [ + "Repo02AC86CF", + "Arn" + ] + } + ] + } + ] + }, + ".dkr.ecr.", + { + "Fn::Select": [ + 3, + { + "Fn::Split": [ + ":", + { + "Fn::GetAtt": [ + "Repo02AC86CF", + "Arn" + ] + } + ] + } + ] + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "Repo02AC86CF" + } + ] + ] + } + } + }, + "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/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/cdk.out b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/cdk.out new file mode 100644 index 0000000000000..7925065efbcc4 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"31.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/cdkintegautodeleteimagesDefaultTestDeployAssert6B08011C.assets.json b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/cdkintegautodeleteimagesDefaultTestDeployAssert6B08011C.assets.json new file mode 100644 index 0000000000000..9ff70a8cba8bc --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/cdkintegautodeleteimagesDefaultTestDeployAssert6B08011C.assets.json @@ -0,0 +1,19 @@ +{ + "version": "31.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "cdkintegautodeleteimagesDefaultTestDeployAssert6B08011C.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/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/cdkintegautodeleteimagesDefaultTestDeployAssert6B08011C.template.json b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/cdkintegautodeleteimagesDefaultTestDeployAssert6B08011C.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/cdkintegautodeleteimagesDefaultTestDeployAssert6B08011C.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/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/integ.json b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/integ.json new file mode 100644 index 0000000000000..3b51355c51c47 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "31.0.0", + "testCases": { + "cdk-integ-auto-delete-images/DefaultTest": { + "stacks": [ + "aws-ecr-integ-stack" + ], + "assertionStack": "cdk-integ-auto-delete-images/DefaultTest/DeployAssert", + "assertionStackName": "cdkintegautodeleteimagesDefaultTestDeployAssert6B08011C" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/manifest.json b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/manifest.json new file mode 100644 index 0000000000000..ce711210493c6 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/manifest.json @@ -0,0 +1,135 @@ +{ + "version": "31.0.0", + "artifacts": { + "aws-ecr-integ-stack.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "aws-ecr-integ-stack.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "aws-ecr-integ-stack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "aws-ecr-integ-stack.template.json", + "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}/740719c7baa1a196b04e874646ded5b2a2e4d68894eddc1b18791366c3785922.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "aws-ecr-integ-stack.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": [ + "aws-ecr-integ-stack.assets" + ], + "metadata": { + "/aws-ecr-integ-stack/Repo/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Repo02AC86CF" + } + ], + "/aws-ecr-integ-stack/Repo/AutoDeleteImagesCustomResource/Default": [ + { + "type": "aws:cdk:logicalId", + "data": "RepoAutoDeleteImagesCustomResource65201E29" + } + ], + "/aws-ecr-integ-stack/Custom::ECRAutoDeleteImagesCustomResourceProvider/Role": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomECRAutoDeleteImagesCustomResourceProviderRole665F2773" + } + ], + "/aws-ecr-integ-stack/Custom::ECRAutoDeleteImagesCustomResourceProvider/Handler": [ + { + "type": "aws:cdk:logicalId", + "data": "CustomECRAutoDeleteImagesCustomResourceProviderHandler8D89C030" + } + ], + "/aws-ecr-integ-stack/RepositoryURI": [ + { + "type": "aws:cdk:logicalId", + "data": "RepositoryURI" + } + ], + "/aws-ecr-integ-stack/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/aws-ecr-integ-stack/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "aws-ecr-integ-stack" + }, + "cdkintegautodeleteimagesDefaultTestDeployAssert6B08011C.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "cdkintegautodeleteimagesDefaultTestDeployAssert6B08011C.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "cdkintegautodeleteimagesDefaultTestDeployAssert6B08011C": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "cdkintegautodeleteimagesDefaultTestDeployAssert6B08011C.template.json", + "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": [ + "cdkintegautodeleteimagesDefaultTestDeployAssert6B08011C.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": [ + "cdkintegautodeleteimagesDefaultTestDeployAssert6B08011C.assets" + ], + "metadata": { + "/cdk-integ-auto-delete-images/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/cdk-integ-auto-delete-images/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "cdk-integ-auto-delete-images/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/tree.json b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/tree.json new file mode 100644 index 0000000000000..e9886219fe29a --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/tree.json @@ -0,0 +1,211 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "aws-ecr-integ-stack": { + "id": "aws-ecr-integ-stack", + "path": "aws-ecr-integ-stack", + "children": { + "Repo": { + "id": "Repo", + "path": "aws-ecr-integ-stack/Repo", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-ecr-integ-stack/Repo/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ECR::Repository", + "aws:cdk:cloudformation:props": { + "repositoryName": "delete-even-if-containing-images", + "repositoryPolicyText": { + "Statement": [ + { + "Action": [ + "ecr:BatchDeleteImage", + "ecr:ListImages" + ], + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::GetAtt": [ + "CustomECRAutoDeleteImagesCustomResourceProviderRole665F2773", + "Arn" + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "tags": [ + { + "key": "aws-cdk:auto-delete-images", + "value": "true" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecr.CfnRepository", + "version": "0.0.0" + } + }, + "AutoDeleteImagesCustomResource": { + "id": "AutoDeleteImagesCustomResource", + "path": "aws-ecr-integ-stack/Repo/AutoDeleteImagesCustomResource", + "children": { + "Default": { + "id": "Default", + "path": "aws-ecr-integ-stack/Repo/AutoDeleteImagesCustomResource/Default", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-ecr.Repository", + "version": "0.0.0" + } + }, + "Custom::ECRAutoDeleteImagesCustomResourceProvider": { + "id": "Custom::ECRAutoDeleteImagesCustomResourceProvider", + "path": "aws-ecr-integ-stack/Custom::ECRAutoDeleteImagesCustomResourceProvider", + "children": { + "Staging": { + "id": "Staging", + "path": "aws-ecr-integ-stack/Custom::ECRAutoDeleteImagesCustomResourceProvider/Staging", + "constructInfo": { + "fqn": "@aws-cdk/core.AssetStaging", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "aws-ecr-integ-stack/Custom::ECRAutoDeleteImagesCustomResourceProvider/Role", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + }, + "Handler": { + "id": "Handler", + "path": "aws-ecr-integ-stack/Custom::ECRAutoDeleteImagesCustomResourceProvider/Handler", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnResource", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.CustomResourceProvider", + "version": "0.0.0" + } + }, + "RepositoryURI": { + "id": "RepositoryURI", + "path": "aws-ecr-integ-stack/RepositoryURI", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "aws-ecr-integ-stack/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "aws-ecr-integ-stack/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "cdk-integ-auto-delete-images": { + "id": "cdk-integ-auto-delete-images", + "path": "cdk-integ-auto-delete-images", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "cdk-integ-auto-delete-images/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "cdk-integ-auto-delete-images/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.270" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "cdk-integ-auto-delete-images/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "cdk-integ-auto-delete-images/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "cdk-integ-auto-delete-images/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.270" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.ts b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.ts new file mode 100644 index 0000000000000..1d57788631713 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.ts @@ -0,0 +1,20 @@ +import * as cdk from '@aws-cdk/core'; +import { IntegTest } from '@aws-cdk/integ-tests'; +import * as ecr from '../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecr-integ-stack'); + +const repo = new ecr.Repository(stack, 'Repo', { + repositoryName: 'delete-even-if-containing-images', + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteImages: true, +}); + +new cdk.CfnOutput(stack, 'RepositoryURI', { + value: repo.repositoryUri, +}); + +new IntegTest(app, 'cdk-integ-auto-delete-images', { + testCases: [stack], +}); From 575ddc559286adb91100996942b8763031efbc2d Mon Sep 17 00:00:00 2001 From: Randy Ridgley Date: Fri, 10 Mar 2023 17:38:28 +0000 Subject: [PATCH 4/6] feat(ecr): add option to auto delete images upon ECR repository removal --- packages/@aws-cdk/aws-ecr/lib/repository.ts | 45 +++++----- .../index.js | 83 ------------------ .../__entrypoint__.js | 0 .../index.js | 85 +++++++++++++++++++ .../aws-ecr-integ-stack.assets.json | 10 +-- .../aws-ecr-integ-stack.template.json | 52 +++++++----- .../manifest.json | 2 +- .../tree.json | 20 ----- 8 files changed, 142 insertions(+), 155 deletions(-) delete mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c/index.js rename packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/{asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c => asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e}/__entrypoint__.js (100%) create mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e/index.js diff --git a/packages/@aws-cdk/aws-ecr/lib/repository.ts b/packages/@aws-cdk/aws-ecr/lib/repository.ts index 38d51f7bbd7dd..08dc89e296d29 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository.ts @@ -770,36 +770,31 @@ export class Repository extends RepositoryBase { } private enableAutoDeleteImages() { + // 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. const provider = CustomResourceProvider.getOrCreateProvider(this, AUTO_DELETE_IMAGES_RESOURCE_TYPE, { codeDirectory: path.join(__dirname, 'auto-delete-images-handler'), runtime: CustomResourceProviderRuntime.NODEJS_14_X, 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 - this.addToResourcePolicy(new iam.PolicyStatement({ - actions: [ - 'ecr:BatchDeleteImage', - 'ecr:ListImages', - ], - resources: [ - this.repositoryArn, - ], - principals: [new iam.ArnPrincipal(provider.roleArn)], - })); - - // This is scoped to * in the case that the repository name is changed during an update - // it can be retrieved from the DescribeRepositories call to ECR - this.addToResourcePolicy(new iam.PolicyStatement({ - actions: [ - 'ecr:DescribeRepositories', - ], - resources: [ - '*', + policyStatements: [ + { + Effect: 'Allow', + Action: [ + 'ecr:BatchDeleteImage', + 'ecr:ListImages', + ], + Resource: ['*'], // TODO? + }, + { + Effect: 'Allow', + Action: [ + 'ecr:DescribeRepositories', + 'ecr:ListTagsForResource', + ], + Resource: ['*'], + }, ], - principals: [new iam.ArnPrincipal(provider.roleArn)], - })); + }); const customResource = new CustomResource(this, 'AutoDeleteImagesCustomResource', { resourceType: AUTO_DELETE_IMAGES_RESOURCE_TYPE, diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c/index.js b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c/index.js deleted file mode 100644 index c7fa666848ee8..0000000000000 --- a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c/index.js +++ /dev/null @@ -1,83 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.handler = void 0; -// eslint-disable-next-line import/no-extraneous-dependencies -const aws_sdk_1 = require("aws-sdk"); -const AUTO_DELETE_IMAGES_TAG = 'aws-cdk:auto-delete-images'; -const ecr = new aws_sdk_1.ECR(); -async function handler(event) { - switch (event.RequestType) { - case 'Create': - case 'Update': - return onUpdate(event); - case 'Delete': - return onDelete(event.ResourceProperties?.RepositoryName, event.ResourceProperties?.RepositoryArn); - } -} -exports.handler = handler; -async function onUpdate(event) { - const updateEvent = event; - const oldRepositoryName = updateEvent.OldResourceProperties?.RepositoryName; - const newRepositoryName = updateEvent.ResourceProperties?.RepositoryName; - const repositoryNameHasChanged = newRepositoryName != null && oldRepositoryName != null && newRepositoryName !== oldRepositoryName; - /* If the name of the repository has changed, CloudFormation will try to delete the repository - and create a new one with the new name. So we have to delete the images in the - repository so that this operation does not fail. */ - if (repositoryNameHasChanged) { - return onDelete(oldRepositoryName, updateEvent.OldResourceProperties?.RepositoryArn); - } -} -/** - * Recursively delete all images in the repository - * - * @param ECR.ListImagesRequest the repositoryName & nextToken if presented - */ -async function emptyRepository(params) { - const listedImages = await ecr.listImages(params).promise(); - const imageIds = listedImages?.imageIds ?? []; - const nextToken = listedImages.nextToken ?? null; - if (imageIds.length === 0) { - return; - } - await ecr.batchDeleteImage({ - repositoryName: params.repositoryName, - imageIds, - }).promise(); - if (nextToken) { - await emptyRepository({ - ...params, - nextToken, - }); - } -} -async function onDelete(repositoryName, repositoryArn) { - if (!repositoryName) { - throw new Error('No RepositoryName was provided.'); - } - if (!await isRepositoryTaggedForDeletion(repositoryArn)) { - process.stdout.write(`Repository does not have '${AUTO_DELETE_IMAGES_TAG}' tag, skipping cleaning.\n`); - return; - } - try { - await emptyRepository({ repositoryName }); - } - catch (e) { - if (e.name !== 'RepositoryNotFoundException') { - throw e; - } - // Repository doesn't exist. Ignoring - } -} -/** - * The repository will only be tagged for deletion if it's being deleted in the same - * deployment as this Custom Resource. - * - * If the Custom Resource is ever deleted before the repository, it must be because - * `autoDeleteImages` has been switched to false, in which case the tag would have - * been removed before we get to this Delete event. - */ -async function isRepositoryTaggedForDeletion(repositoryArn) { - const response = await ecr.listTagsForResource({ resourceArn: repositoryArn }).promise(); - return response.tags?.some(tag => tag.Key === AUTO_DELETE_IMAGES_TAG && tag.Value === 'true'); -} -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2REFBNkQ7QUFDN0QscUNBQThCO0FBRTlCLE1BQU0sc0JBQXNCLEdBQUcsNEJBQTRCLENBQUM7QUFFNUQsTUFBTSxHQUFHLEdBQUcsSUFBSSxhQUFHLEVBQUUsQ0FBQztBQUVmLEtBQUssVUFBVSxPQUFPLENBQUMsS0FBa0Q7SUFDOUUsUUFBUSxLQUFLLENBQUMsV0FBVyxFQUFFO1FBQ3pCLEtBQUssUUFBUSxDQUFDO1FBQ2QsS0FBSyxRQUFRO1lBQ1gsT0FBTyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDekIsS0FBSyxRQUFRO1lBQ1gsT0FBTyxRQUFRLENBQUMsS0FBSyxDQUFDLGtCQUFrQixFQUFFLGNBQWMsRUFBRSxLQUFLLENBQUMsa0JBQWtCLEVBQUUsYUFBYSxDQUFDLENBQUM7S0FDdEc7QUFDSCxDQUFDO0FBUkQsMEJBUUM7QUFFRCxLQUFLLFVBQVUsUUFBUSxDQUFDLEtBQWtEO0lBQ3hFLE1BQU0sV0FBVyxHQUFHLEtBQTBELENBQUM7SUFDL0UsTUFBTSxpQkFBaUIsR0FBRyxXQUFXLENBQUMscUJBQXFCLEVBQUUsY0FBYyxDQUFDO0lBQzVFLE1BQU0saUJBQWlCLEdBQUcsV0FBVyxDQUFDLGtCQUFrQixFQUFFLGNBQWMsQ0FBQztJQUN6RSxNQUFNLHdCQUF3QixHQUFHLGlCQUFpQixJQUFJLElBQUksSUFBSSxpQkFBaUIsSUFBSSxJQUFJLElBQUksaUJBQWlCLEtBQUssaUJBQWlCLENBQUM7SUFFbkk7OzBEQUVzRDtJQUN0RCxJQUFJLHdCQUF3QixFQUFFO1FBQzVCLE9BQU8sUUFBUSxDQUFDLGlCQUFpQixFQUFFLFdBQVcsQ0FBQyxxQkFBcUIsRUFBRSxhQUFhLENBQUMsQ0FBQztLQUN0RjtBQUNILENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsS0FBSyxVQUFVLGVBQWUsQ0FBQyxNQUE2QjtJQUMxRCxNQUFNLFlBQVksR0FBRyxNQUFNLEdBQUcsQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7SUFFNUQsTUFBTSxRQUFRLEdBQUcsWUFBWSxFQUFFLFFBQVEsSUFBSSxFQUFFLENBQUM7SUFDOUMsTUFBTSxTQUFTLEdBQUcsWUFBWSxDQUFDLFNBQVMsSUFBSSxJQUFJLENBQUM7SUFDakQsSUFBSSxRQUFRLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRTtRQUN6QixPQUFPO0tBQ1I7SUFFRCxNQUFNLEdBQUcsQ0FBQyxnQkFBZ0IsQ0FBQztRQUN6QixjQUFjLEVBQUUsTUFBTSxDQUFDLGNBQWM7UUFDckMsUUFBUTtLQUNULENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUViLElBQUksU0FBUyxFQUFFO1FBQ2IsTUFBTSxlQUFlLENBQUM7WUFDcEIsR0FBRyxNQUFNO1lBQ1QsU0FBUztTQUNWLENBQUMsQ0FBQztLQUNKO0FBQ0gsQ0FBQztBQUVELEtBQUssVUFBVSxRQUFRLENBQUMsY0FBc0IsRUFBRSxhQUFxQjtJQUNuRSxJQUFJLENBQUMsY0FBYyxFQUFFO1FBQ25CLE1BQU0sSUFBSSxLQUFLLENBQUMsaUNBQWlDLENBQUMsQ0FBQztLQUNwRDtJQUVELElBQUksQ0FBQyxNQUFNLDZCQUE2QixDQUFDLGFBQWEsQ0FBQyxFQUFFO1FBQ3ZELE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLDZCQUE2QixzQkFBc0IsNkJBQTZCLENBQUMsQ0FBQztRQUN2RyxPQUFPO0tBQ1I7SUFDRCxJQUFJO1FBQ0YsTUFBTSxlQUFlLENBQUMsRUFBRSxjQUFjLEVBQUUsQ0FBQyxDQUFDO0tBQzNDO0lBQUMsT0FBTyxDQUFDLEVBQUU7UUFDVixJQUFJLENBQUMsQ0FBQyxJQUFJLEtBQUssNkJBQTZCLEVBQUU7WUFDNUMsTUFBTSxDQUFDLENBQUM7U0FDVDtRQUNELHFDQUFxQztLQUN0QztBQUNILENBQUM7QUFFRDs7Ozs7OztHQU9HO0FBQ0gsS0FBSyxVQUFVLDZCQUE2QixDQUFDLGFBQXFCO0lBQ2hFLE1BQU0sUUFBUSxHQUFHLE1BQU0sR0FBRyxDQUFDLG1CQUFtQixDQUFDLEVBQUUsV0FBVyxFQUFFLGFBQWEsRUFBRSxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDekYsT0FBTyxRQUFRLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEtBQUssc0JBQXNCLElBQUksR0FBRyxDQUFDLEtBQUssS0FBSyxNQUFNLENBQUMsQ0FBQztBQUNoRyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGltcG9ydC9uby1leHRyYW5lb3VzLWRlcGVuZGVuY2llc1xuaW1wb3J0IHsgRUNSIH0gZnJvbSAnYXdzLXNkayc7XG5cbmNvbnN0IEFVVE9fREVMRVRFX0lNQUdFU19UQUcgPSAnYXdzLWNkazphdXRvLWRlbGV0ZS1pbWFnZXMnO1xuXG5jb25zdCBlY3IgPSBuZXcgRUNSKCk7XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50KSB7XG4gIHN3aXRjaCAoZXZlbnQuUmVxdWVzdFR5cGUpIHtcbiAgICBjYXNlICdDcmVhdGUnOlxuICAgIGNhc2UgJ1VwZGF0ZSc6XG4gICAgICByZXR1cm4gb25VcGRhdGUoZXZlbnQpO1xuICAgIGNhc2UgJ0RlbGV0ZSc6XG4gICAgICByZXR1cm4gb25EZWxldGUoZXZlbnQuUmVzb3VyY2VQcm9wZXJ0aWVzPy5SZXBvc2l0b3J5TmFtZSwgZXZlbnQuUmVzb3VyY2VQcm9wZXJ0aWVzPy5SZXBvc2l0b3J5QXJuKTtcbiAgfVxufVxuXG5hc3luYyBmdW5jdGlvbiBvblVwZGF0ZShldmVudDogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCkge1xuICBjb25zdCB1cGRhdGVFdmVudCA9IGV2ZW50IGFzIEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlVXBkYXRlRXZlbnQ7XG4gIGNvbnN0IG9sZFJlcG9zaXRvcnlOYW1lID0gdXBkYXRlRXZlbnQuT2xkUmVzb3VyY2VQcm9wZXJ0aWVzPy5SZXBvc2l0b3J5TmFtZTtcbiAgY29uc3QgbmV3UmVwb3NpdG9yeU5hbWUgPSB1cGRhdGVFdmVudC5SZXNvdXJjZVByb3BlcnRpZXM/LlJlcG9zaXRvcnlOYW1lO1xuICBjb25zdCByZXBvc2l0b3J5TmFtZUhhc0NoYW5nZWQgPSBuZXdSZXBvc2l0b3J5TmFtZSAhPSBudWxsICYmIG9sZFJlcG9zaXRvcnlOYW1lICE9IG51bGwgJiYgbmV3UmVwb3NpdG9yeU5hbWUgIT09IG9sZFJlcG9zaXRvcnlOYW1lO1xuXG4gIC8qIElmIHRoZSBuYW1lIG9mIHRoZSByZXBvc2l0b3J5IGhhcyBjaGFuZ2VkLCBDbG91ZEZvcm1hdGlvbiB3aWxsIHRyeSB0byBkZWxldGUgdGhlIHJlcG9zaXRvcnlcbiAgICAgYW5kIGNyZWF0ZSBhIG5ldyBvbmUgd2l0aCB0aGUgbmV3IG5hbWUuIFNvIHdlIGhhdmUgdG8gZGVsZXRlIHRoZSBpbWFnZXMgaW4gdGhlXG4gICAgIHJlcG9zaXRvcnkgc28gdGhhdCB0aGlzIG9wZXJhdGlvbiBkb2VzIG5vdCBmYWlsLiAqL1xuICBpZiAocmVwb3NpdG9yeU5hbWVIYXNDaGFuZ2VkKSB7XG4gICAgcmV0dXJuIG9uRGVsZXRlKG9sZFJlcG9zaXRvcnlOYW1lLCB1cGRhdGVFdmVudC5PbGRSZXNvdXJjZVByb3BlcnRpZXM/LlJlcG9zaXRvcnlBcm4pO1xuICB9XG59XG5cbi8qKlxuICogUmVjdXJzaXZlbHkgZGVsZXRlIGFsbCBpbWFnZXMgaW4gdGhlIHJlcG9zaXRvcnlcbiAqXG4gKiBAcGFyYW0gRUNSLkxpc3RJbWFnZXNSZXF1ZXN0IHRoZSByZXBvc2l0b3J5TmFtZSAmIG5leHRUb2tlbiBpZiBwcmVzZW50ZWRcbiAqL1xuYXN5bmMgZnVuY3Rpb24gZW1wdHlSZXBvc2l0b3J5KHBhcmFtczogRUNSLkxpc3RJbWFnZXNSZXF1ZXN0KSB7XG4gIGNvbnN0IGxpc3RlZEltYWdlcyA9IGF3YWl0IGVjci5saXN0SW1hZ2VzKHBhcmFtcykucHJvbWlzZSgpO1xuXG4gIGNvbnN0IGltYWdlSWRzID0gbGlzdGVkSW1hZ2VzPy5pbWFnZUlkcyA/PyBbXTtcbiAgY29uc3QgbmV4dFRva2VuID0gbGlzdGVkSW1hZ2VzLm5leHRUb2tlbiA/PyBudWxsO1xuICBpZiAoaW1hZ2VJZHMubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgYXdhaXQgZWNyLmJhdGNoRGVsZXRlSW1hZ2Uoe1xuICAgIHJlcG9zaXRvcnlOYW1lOiBwYXJhbXMucmVwb3NpdG9yeU5hbWUsXG4gICAgaW1hZ2VJZHMsXG4gIH0pLnByb21pc2UoKTtcblxuICBpZiAobmV4dFRva2VuKSB7XG4gICAgYXdhaXQgZW1wdHlSZXBvc2l0b3J5KHtcbiAgICAgIC4uLnBhcmFtcyxcbiAgICAgIG5leHRUb2tlbixcbiAgICB9KTtcbiAgfVxufVxuXG5hc3luYyBmdW5jdGlvbiBvbkRlbGV0ZShyZXBvc2l0b3J5TmFtZTogc3RyaW5nLCByZXBvc2l0b3J5QXJuOiBzdHJpbmcpIHtcbiAgaWYgKCFyZXBvc2l0b3J5TmFtZSkge1xuICAgIHRocm93IG5ldyBFcnJvcignTm8gUmVwb3NpdG9yeU5hbWUgd2FzIHByb3ZpZGVkLicpO1xuICB9XG5cbiAgaWYgKCFhd2FpdCBpc1JlcG9zaXRvcnlUYWdnZWRGb3JEZWxldGlvbihyZXBvc2l0b3J5QXJuKSkge1xuICAgIHByb2Nlc3Muc3Rkb3V0LndyaXRlKGBSZXBvc2l0b3J5IGRvZXMgbm90IGhhdmUgJyR7QVVUT19ERUxFVEVfSU1BR0VTX1RBR30nIHRhZywgc2tpcHBpbmcgY2xlYW5pbmcuXFxuYCk7XG4gICAgcmV0dXJuO1xuICB9XG4gIHRyeSB7XG4gICAgYXdhaXQgZW1wdHlSZXBvc2l0b3J5KHsgcmVwb3NpdG9yeU5hbWUgfSk7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBpZiAoZS5uYW1lICE9PSAnUmVwb3NpdG9yeU5vdEZvdW5kRXhjZXB0aW9uJykge1xuICAgICAgdGhyb3cgZTtcbiAgICB9XG4gICAgLy8gUmVwb3NpdG9yeSBkb2Vzbid0IGV4aXN0LiBJZ25vcmluZ1xuICB9XG59XG5cbi8qKlxuICogVGhlIHJlcG9zaXRvcnkgd2lsbCBvbmx5IGJlIHRhZ2dlZCBmb3IgZGVsZXRpb24gaWYgaXQncyBiZWluZyBkZWxldGVkIGluIHRoZSBzYW1lXG4gKiBkZXBsb3ltZW50IGFzIHRoaXMgQ3VzdG9tIFJlc291cmNlLlxuICpcbiAqIElmIHRoZSBDdXN0b20gUmVzb3VyY2UgaXMgZXZlciBkZWxldGVkIGJlZm9yZSB0aGUgcmVwb3NpdG9yeSwgaXQgbXVzdCBiZSBiZWNhdXNlXG4gKiBgYXV0b0RlbGV0ZUltYWdlc2AgaGFzIGJlZW4gc3dpdGNoZWQgdG8gZmFsc2UsIGluIHdoaWNoIGNhc2UgdGhlIHRhZyB3b3VsZCBoYXZlXG4gKiBiZWVuIHJlbW92ZWQgYmVmb3JlIHdlIGdldCB0byB0aGlzIERlbGV0ZSBldmVudC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gaXNSZXBvc2l0b3J5VGFnZ2VkRm9yRGVsZXRpb24ocmVwb3NpdG9yeUFybjogc3RyaW5nKSB7XG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZWNyLmxpc3RUYWdzRm9yUmVzb3VyY2UoeyByZXNvdXJjZUFybjogcmVwb3NpdG9yeUFybiB9KS5wcm9taXNlKCk7XG4gIHJldHVybiByZXNwb25zZS50YWdzPy5zb21lKHRhZyA9PiB0YWcuS2V5ID09PSBBVVRPX0RFTEVURV9JTUFHRVNfVEFHICYmIHRhZy5WYWx1ZSA9PT0gJ3RydWUnKTtcbn0iXX0= \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c/__entrypoint__.js b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e/__entrypoint__.js similarity index 100% rename from packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c/__entrypoint__.js rename to packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e/__entrypoint__.js diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e/index.js b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e/index.js new file mode 100644 index 0000000000000..4d66880dac823 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e/index.js @@ -0,0 +1,85 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handler = void 0; +// eslint-disable-next-line import/no-extraneous-dependencies +const aws_sdk_1 = require("aws-sdk"); +const AUTO_DELETE_IMAGES_TAG = 'aws-cdk:auto-delete-images'; +const ecr = new aws_sdk_1.ECR(); +async function handler(event) { + switch (event.RequestType) { + case 'Create': + case 'Update': + return onUpdate(event); + case 'Delete': + return onDelete(event.ResourceProperties?.RepositoryName); + } +} +exports.handler = handler; +async function onUpdate(event) { + const updateEvent = event; + const oldRepositoryName = updateEvent.OldResourceProperties?.RepositoryName; + const newRepositoryName = updateEvent.ResourceProperties?.RepositoryName; + const repositoryNameHasChanged = newRepositoryName != null && oldRepositoryName != null && newRepositoryName !== oldRepositoryName; + /* If the name of the repository has changed, CloudFormation will try to delete the repository + and create a new one with the new name. So we have to delete the images in the + repository so that this operation does not fail. */ + if (repositoryNameHasChanged) { + return onDelete(oldRepositoryName); + } +} +/** + * Recursively delete all images in the repository + * + * @param ECR.ListImagesRequest the repositoryName & nextToken if presented + */ +async function emptyRepository(params) { + const listedImages = await ecr.listImages(params).promise(); + const imageIds = listedImages?.imageIds ?? []; + const nextToken = listedImages.nextToken ?? null; + if (imageIds.length === 0) { + return; + } + await ecr.batchDeleteImage({ + repositoryName: params.repositoryName, + imageIds, + }).promise(); + if (nextToken) { + await emptyRepository({ + ...params, + nextToken, + }); + } +} +async function onDelete(repositoryName) { + if (!repositoryName) { + throw new Error('No RepositoryName was provided.'); + } + const response = await ecr.describeRepositories({ repositoryNames: [repositoryName] }).promise(); + const repository = response.repositories?.find(repo => repo.repositoryName === repositoryName); + if (!await isRepositoryTaggedForDeletion(repository?.repositoryArn)) { + process.stdout.write(`Repository does not have '${AUTO_DELETE_IMAGES_TAG}' tag, skipping cleaning.\n`); + return; + } + try { + await emptyRepository({ repositoryName }); + } + catch (e) { + if (e.name !== 'RepositoryNotFoundException') { + throw e; + } + // Repository doesn't exist. Ignoring + } +} +/** + * The repository will only be tagged for deletion if it's being deleted in the same + * deployment as this Custom Resource. + * + * If the Custom Resource is ever deleted before the repository, it must be because + * `autoDeleteImages` has been switched to false, in which case the tag would have + * been removed before we get to this Delete event. + */ +async function isRepositoryTaggedForDeletion(repositoryArn) { + const response = await ecr.listTagsForResource({ resourceArn: repositoryArn }).promise(); + return response.tags?.some(tag => tag.Key === AUTO_DELETE_IMAGES_TAG && tag.Value === 'true'); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2REFBNkQ7QUFDN0QscUNBQThCO0FBRTlCLE1BQU0sc0JBQXNCLEdBQUcsNEJBQTRCLENBQUM7QUFFNUQsTUFBTSxHQUFHLEdBQUcsSUFBSSxhQUFHLEVBQUUsQ0FBQztBQUVmLEtBQUssVUFBVSxPQUFPLENBQUMsS0FBa0Q7SUFDOUUsUUFBUSxLQUFLLENBQUMsV0FBVyxFQUFFO1FBQ3pCLEtBQUssUUFBUSxDQUFDO1FBQ2QsS0FBSyxRQUFRO1lBQ1gsT0FBTyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDekIsS0FBSyxRQUFRO1lBQ1gsT0FBTyxRQUFRLENBQUMsS0FBSyxDQUFDLGtCQUFrQixFQUFFLGNBQWMsQ0FBQyxDQUFDO0tBQzdEO0FBQ0gsQ0FBQztBQVJELDBCQVFDO0FBRUQsS0FBSyxVQUFVLFFBQVEsQ0FBQyxLQUFrRDtJQUN4RSxNQUFNLFdBQVcsR0FBRyxLQUEwRCxDQUFDO0lBQy9FLE1BQU0saUJBQWlCLEdBQUcsV0FBVyxDQUFDLHFCQUFxQixFQUFFLGNBQWMsQ0FBQztJQUM1RSxNQUFNLGlCQUFpQixHQUFHLFdBQVcsQ0FBQyxrQkFBa0IsRUFBRSxjQUFjLENBQUM7SUFDekUsTUFBTSx3QkFBd0IsR0FBRyxpQkFBaUIsSUFBSSxJQUFJLElBQUksaUJBQWlCLElBQUksSUFBSSxJQUFJLGlCQUFpQixLQUFLLGlCQUFpQixDQUFDO0lBRW5JOzswREFFc0Q7SUFDdEQsSUFBSSx3QkFBd0IsRUFBRTtRQUM1QixPQUFPLFFBQVEsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO0tBQ3BDO0FBQ0gsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxLQUFLLFVBQVUsZUFBZSxDQUFDLE1BQTZCO0lBQzFELE1BQU0sWUFBWSxHQUFHLE1BQU0sR0FBRyxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUU1RCxNQUFNLFFBQVEsR0FBRyxZQUFZLEVBQUUsUUFBUSxJQUFJLEVBQUUsQ0FBQztJQUM5QyxNQUFNLFNBQVMsR0FBRyxZQUFZLENBQUMsU0FBUyxJQUFJLElBQUksQ0FBQztJQUNqRCxJQUFJLFFBQVEsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFO1FBQ3pCLE9BQU87S0FDUjtJQUVELE1BQU0sR0FBRyxDQUFDLGdCQUFnQixDQUFDO1FBQ3pCLGNBQWMsRUFBRSxNQUFNLENBQUMsY0FBYztRQUNyQyxRQUFRO0tBQ1QsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBRWIsSUFBSSxTQUFTLEVBQUU7UUFDYixNQUFNLGVBQWUsQ0FBQztZQUNwQixHQUFHLE1BQU07WUFDVCxTQUFTO1NBQ1YsQ0FBQyxDQUFDO0tBQ0o7QUFDSCxDQUFDO0FBRUQsS0FBSyxVQUFVLFFBQVEsQ0FBQyxjQUFzQjtJQUM1QyxJQUFJLENBQUMsY0FBYyxFQUFFO1FBQ25CLE1BQU0sSUFBSSxLQUFLLENBQUMsaUNBQWlDLENBQUMsQ0FBQztLQUNwRDtJQUVELE1BQU0sUUFBUSxHQUFHLE1BQU0sR0FBRyxDQUFDLG9CQUFvQixDQUFDLEVBQUUsZUFBZSxFQUFFLENBQUMsY0FBYyxDQUFDLEVBQUUsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2pHLE1BQU0sVUFBVSxHQUFHLFFBQVEsQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLGNBQWMsS0FBSyxjQUFjLENBQUMsQ0FBQztJQUUvRixJQUFJLENBQUMsTUFBTSw2QkFBNkIsQ0FBQyxVQUFVLEVBQUUsYUFBYyxDQUFDLEVBQUU7UUFDcEUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsNkJBQTZCLHNCQUFzQiw2QkFBNkIsQ0FBQyxDQUFDO1FBQ3ZHLE9BQU87S0FDUjtJQUNELElBQUk7UUFDRixNQUFNLGVBQWUsQ0FBQyxFQUFFLGNBQWMsRUFBRSxDQUFDLENBQUM7S0FDM0M7SUFBQyxPQUFPLENBQUMsRUFBRTtRQUNWLElBQUksQ0FBQyxDQUFDLElBQUksS0FBSyw2QkFBNkIsRUFBRTtZQUM1QyxNQUFNLENBQUMsQ0FBQztTQUNUO1FBQ0QscUNBQXFDO0tBQ3RDO0FBQ0gsQ0FBQztBQUVEOzs7Ozs7O0dBT0c7QUFDSCxLQUFLLFVBQVUsNkJBQTZCLENBQUMsYUFBcUI7SUFDaEUsTUFBTSxRQUFRLEdBQUcsTUFBTSxHQUFHLENBQUMsbUJBQW1CLENBQUMsRUFBRSxXQUFXLEVBQUUsYUFBYSxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUN6RixPQUFPLFFBQVEsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLEdBQUcsS0FBSyxzQkFBc0IsSUFBSSxHQUFHLENBQUMsS0FBSyxLQUFLLE1BQU0sQ0FBQyxDQUFDO0FBQ2hHLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgaW1wb3J0L25vLWV4dHJhbmVvdXMtZGVwZW5kZW5jaWVzXG5pbXBvcnQgeyBFQ1IgfSBmcm9tICdhd3Mtc2RrJztcblxuY29uc3QgQVVUT19ERUxFVEVfSU1BR0VTX1RBRyA9ICdhd3MtY2RrOmF1dG8tZGVsZXRlLWltYWdlcyc7XG5cbmNvbnN0IGVjciA9IG5ldyBFQ1IoKTtcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGhhbmRsZXIoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQpIHtcbiAgc3dpdGNoIChldmVudC5SZXF1ZXN0VHlwZSkge1xuICAgIGNhc2UgJ0NyZWF0ZSc6XG4gICAgY2FzZSAnVXBkYXRlJzpcbiAgICAgIHJldHVybiBvblVwZGF0ZShldmVudCk7XG4gICAgY2FzZSAnRGVsZXRlJzpcbiAgICAgIHJldHVybiBvbkRlbGV0ZShldmVudC5SZXNvdXJjZVByb3BlcnRpZXM/LlJlcG9zaXRvcnlOYW1lKTtcbiAgfVxufVxuXG5hc3luYyBmdW5jdGlvbiBvblVwZGF0ZShldmVudDogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCkge1xuICBjb25zdCB1cGRhdGVFdmVudCA9IGV2ZW50IGFzIEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlVXBkYXRlRXZlbnQ7XG4gIGNvbnN0IG9sZFJlcG9zaXRvcnlOYW1lID0gdXBkYXRlRXZlbnQuT2xkUmVzb3VyY2VQcm9wZXJ0aWVzPy5SZXBvc2l0b3J5TmFtZTtcbiAgY29uc3QgbmV3UmVwb3NpdG9yeU5hbWUgPSB1cGRhdGVFdmVudC5SZXNvdXJjZVByb3BlcnRpZXM/LlJlcG9zaXRvcnlOYW1lO1xuICBjb25zdCByZXBvc2l0b3J5TmFtZUhhc0NoYW5nZWQgPSBuZXdSZXBvc2l0b3J5TmFtZSAhPSBudWxsICYmIG9sZFJlcG9zaXRvcnlOYW1lICE9IG51bGwgJiYgbmV3UmVwb3NpdG9yeU5hbWUgIT09IG9sZFJlcG9zaXRvcnlOYW1lO1xuXG4gIC8qIElmIHRoZSBuYW1lIG9mIHRoZSByZXBvc2l0b3J5IGhhcyBjaGFuZ2VkLCBDbG91ZEZvcm1hdGlvbiB3aWxsIHRyeSB0byBkZWxldGUgdGhlIHJlcG9zaXRvcnlcbiAgICAgYW5kIGNyZWF0ZSBhIG5ldyBvbmUgd2l0aCB0aGUgbmV3IG5hbWUuIFNvIHdlIGhhdmUgdG8gZGVsZXRlIHRoZSBpbWFnZXMgaW4gdGhlXG4gICAgIHJlcG9zaXRvcnkgc28gdGhhdCB0aGlzIG9wZXJhdGlvbiBkb2VzIG5vdCBmYWlsLiAqL1xuICBpZiAocmVwb3NpdG9yeU5hbWVIYXNDaGFuZ2VkKSB7XG4gICAgcmV0dXJuIG9uRGVsZXRlKG9sZFJlcG9zaXRvcnlOYW1lKTtcbiAgfVxufVxuXG4vKipcbiAqIFJlY3Vyc2l2ZWx5IGRlbGV0ZSBhbGwgaW1hZ2VzIGluIHRoZSByZXBvc2l0b3J5XG4gKlxuICogQHBhcmFtIEVDUi5MaXN0SW1hZ2VzUmVxdWVzdCB0aGUgcmVwb3NpdG9yeU5hbWUgJiBuZXh0VG9rZW4gaWYgcHJlc2VudGVkXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGVtcHR5UmVwb3NpdG9yeShwYXJhbXM6IEVDUi5MaXN0SW1hZ2VzUmVxdWVzdCkge1xuICBjb25zdCBsaXN0ZWRJbWFnZXMgPSBhd2FpdCBlY3IubGlzdEltYWdlcyhwYXJhbXMpLnByb21pc2UoKTtcblxuICBjb25zdCBpbWFnZUlkcyA9IGxpc3RlZEltYWdlcz8uaW1hZ2VJZHMgPz8gW107XG4gIGNvbnN0IG5leHRUb2tlbiA9IGxpc3RlZEltYWdlcy5uZXh0VG9rZW4gPz8gbnVsbDtcbiAgaWYgKGltYWdlSWRzLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybjtcbiAgfVxuXG4gIGF3YWl0IGVjci5iYXRjaERlbGV0ZUltYWdlKHtcbiAgICByZXBvc2l0b3J5TmFtZTogcGFyYW1zLnJlcG9zaXRvcnlOYW1lLFxuICAgIGltYWdlSWRzLFxuICB9KS5wcm9taXNlKCk7XG5cbiAgaWYgKG5leHRUb2tlbikge1xuICAgIGF3YWl0IGVtcHR5UmVwb3NpdG9yeSh7XG4gICAgICAuLi5wYXJhbXMsXG4gICAgICBuZXh0VG9rZW4sXG4gICAgfSk7XG4gIH1cbn1cblxuYXN5bmMgZnVuY3Rpb24gb25EZWxldGUocmVwb3NpdG9yeU5hbWU6IHN0cmluZykge1xuICBpZiAoIXJlcG9zaXRvcnlOYW1lKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdObyBSZXBvc2l0b3J5TmFtZSB3YXMgcHJvdmlkZWQuJyk7XG4gIH1cblxuICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGVjci5kZXNjcmliZVJlcG9zaXRvcmllcyh7IHJlcG9zaXRvcnlOYW1lczogW3JlcG9zaXRvcnlOYW1lXSB9KS5wcm9taXNlKCk7XG4gIGNvbnN0IHJlcG9zaXRvcnkgPSByZXNwb25zZS5yZXBvc2l0b3JpZXM/LmZpbmQocmVwbyA9PiByZXBvLnJlcG9zaXRvcnlOYW1lID09PSByZXBvc2l0b3J5TmFtZSk7XG5cbiAgaWYgKCFhd2FpdCBpc1JlcG9zaXRvcnlUYWdnZWRGb3JEZWxldGlvbihyZXBvc2l0b3J5Py5yZXBvc2l0b3J5QXJuISkpIHtcbiAgICBwcm9jZXNzLnN0ZG91dC53cml0ZShgUmVwb3NpdG9yeSBkb2VzIG5vdCBoYXZlICcke0FVVE9fREVMRVRFX0lNQUdFU19UQUd9JyB0YWcsIHNraXBwaW5nIGNsZWFuaW5nLlxcbmApO1xuICAgIHJldHVybjtcbiAgfVxuICB0cnkge1xuICAgIGF3YWl0IGVtcHR5UmVwb3NpdG9yeSh7IHJlcG9zaXRvcnlOYW1lIH0pO1xuICB9IGNhdGNoIChlKSB7XG4gICAgaWYgKGUubmFtZSAhPT0gJ1JlcG9zaXRvcnlOb3RGb3VuZEV4Y2VwdGlvbicpIHtcbiAgICAgIHRocm93IGU7XG4gICAgfVxuICAgIC8vIFJlcG9zaXRvcnkgZG9lc24ndCBleGlzdC4gSWdub3JpbmdcbiAgfVxufVxuXG4vKipcbiAqIFRoZSByZXBvc2l0b3J5IHdpbGwgb25seSBiZSB0YWdnZWQgZm9yIGRlbGV0aW9uIGlmIGl0J3MgYmVpbmcgZGVsZXRlZCBpbiB0aGUgc2FtZVxuICogZGVwbG95bWVudCBhcyB0aGlzIEN1c3RvbSBSZXNvdXJjZS5cbiAqXG4gKiBJZiB0aGUgQ3VzdG9tIFJlc291cmNlIGlzIGV2ZXIgZGVsZXRlZCBiZWZvcmUgdGhlIHJlcG9zaXRvcnksIGl0IG11c3QgYmUgYmVjYXVzZVxuICogYGF1dG9EZWxldGVJbWFnZXNgIGhhcyBiZWVuIHN3aXRjaGVkIHRvIGZhbHNlLCBpbiB3aGljaCBjYXNlIHRoZSB0YWcgd291bGQgaGF2ZVxuICogYmVlbiByZW1vdmVkIGJlZm9yZSB3ZSBnZXQgdG8gdGhpcyBEZWxldGUgZXZlbnQuXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGlzUmVwb3NpdG9yeVRhZ2dlZEZvckRlbGV0aW9uKHJlcG9zaXRvcnlBcm46IHN0cmluZykge1xuICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGVjci5saXN0VGFnc0ZvclJlc291cmNlKHsgcmVzb3VyY2VBcm46IHJlcG9zaXRvcnlBcm4gfSkucHJvbWlzZSgpO1xuICByZXR1cm4gcmVzcG9uc2UudGFncz8uc29tZSh0YWcgPT4gdGFnLktleSA9PT0gQVVUT19ERUxFVEVfSU1BR0VTX1RBRyAmJiB0YWcuVmFsdWUgPT09ICd0cnVlJyk7XG59Il19 \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.assets.json b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.assets.json index f31c7867c7d28..b3044ee6c440e 100644 --- a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.assets.json +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.assets.json @@ -1,20 +1,20 @@ { "version": "31.0.0", "files": { - "0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c": { + "9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e": { "source": { - "path": "asset.0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c", + "path": "asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e", "packaging": "zip" }, "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c.zip", + "objectKey": "9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e.zip", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } }, - "740719c7baa1a196b04e874646ded5b2a2e4d68894eddc1b18791366c3785922": { + "4b76dc2c8dce081997458543a2578e2be9ad8a97158b98f186b0359daf217360": { "source": { "path": "aws-ecr-integ-stack.template.json", "packaging": "file" @@ -22,7 +22,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "740719c7baa1a196b04e874646ded5b2a2e4d68894eddc1b18791366c3785922.json", + "objectKey": "4b76dc2c8dce081997458543a2578e2be9ad8a97158b98f186b0359daf217360.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.template.json b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.template.json index 243ba48b02b69..3337ffe427e5c 100644 --- a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.template.json +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.template.json @@ -4,26 +4,6 @@ "Type": "AWS::ECR::Repository", "Properties": { "RepositoryName": "delete-even-if-containing-images", - "RepositoryPolicyText": { - "Statement": [ - { - "Action": [ - "ecr:BatchDeleteImage", - "ecr:ListImages" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::GetAtt": [ - "CustomECRAutoDeleteImagesCustomResourceProviderRole665F2773", - "Arn" - ] - } - } - } - ], - "Version": "2012-10-17" - }, "Tags": [ { "Key": "aws-cdk:auto-delete-images", @@ -72,6 +52,36 @@ { "Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" } + ], + "Policies": [ + { + "PolicyName": "Inline", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ecr:BatchDeleteImage", + "ecr:ListImages" + ], + "Resource": [ + "*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "ecr:DescribeRepositories", + "ecr:ListTagsForResource" + ], + "Resource": [ + "*" + ] + } + ] + } + } ] } }, @@ -82,7 +92,7 @@ "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "S3Key": "0229a1c26dd814c519801273acd6652a34bb1e80a37af75e678f0c1d4e50457c.zip" + "S3Key": "9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e.zip" }, "Timeout": 900, "MemorySize": 128, diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/manifest.json b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/manifest.json index ce711210493c6..c75b473e1126c 100644 --- a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/manifest.json @@ -17,7 +17,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}/740719c7baa1a196b04e874646ded5b2a2e4d68894eddc1b18791366c3785922.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/4b76dc2c8dce081997458543a2578e2be9ad8a97158b98f186b0359daf217360.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/tree.json b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/tree.json index e9886219fe29a..3d686786ff658 100644 --- a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/tree.json @@ -19,26 +19,6 @@ "aws:cdk:cloudformation:type": "AWS::ECR::Repository", "aws:cdk:cloudformation:props": { "repositoryName": "delete-even-if-containing-images", - "repositoryPolicyText": { - "Statement": [ - { - "Action": [ - "ecr:BatchDeleteImage", - "ecr:ListImages" - ], - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::GetAtt": [ - "CustomECRAutoDeleteImagesCustomResourceProviderRole665F2773", - "Arn" - ] - } - } - } - ], - "Version": "2012-10-17" - }, "tags": [ { "key": "aws-cdk:auto-delete-images", From 16e53a1648dc45429de0cc9eefd98615f6fca313 Mon Sep 17 00:00:00 2001 From: Randy Ridgley Date: Tue, 21 Mar 2023 19:11:57 +0000 Subject: [PATCH 5/6] feat(ecr): add option to auto delete images upon ECR repository removal. Updates based on PR review --- .../lib/auto-delete-images-handler/index.ts | 4 +- packages/@aws-cdk/aws-ecr/lib/repository.ts | 10 +- .../__entrypoint__.js | 147 ++++++++++++++++++ .../index.js | 87 +++++++++++ .../__entrypoint__.js | 144 ----------------- .../index.js | 85 ---------- .../aws-ecr-integ-stack.assets.json | 10 +- .../aws-ecr-integ-stack.template.json | 19 +-- .../manifest.json | 2 +- 9 files changed, 253 insertions(+), 255 deletions(-) create mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.6150227d515909a73f0bcde4b9e19b4b206cc65634027053380d700f6e53f08e/__entrypoint__.js create mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.6150227d515909a73f0bcde4b9e19b4b206cc65634027053380d700f6e53f08e/index.js delete mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e/__entrypoint__.js delete mode 100644 packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e/index.js diff --git a/packages/@aws-cdk/aws-ecr/lib/auto-delete-images-handler/index.ts b/packages/@aws-cdk/aws-ecr/lib/auto-delete-images-handler/index.ts index 3596866db0f93..fbc6ca60add7d 100644 --- a/packages/@aws-cdk/aws-ecr/lib/auto-delete-images-handler/index.ts +++ b/packages/@aws-cdk/aws-ecr/lib/auto-delete-images-handler/index.ts @@ -8,6 +8,7 @@ const ecr = new ECR(); export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent) { switch (event.RequestType) { case 'Create': + break; case 'Update': return onUpdate(event); case 'Delete': @@ -19,7 +20,8 @@ async function onUpdate(event: AWSLambda.CloudFormationCustomResourceEvent) { const updateEvent = event as AWSLambda.CloudFormationCustomResourceUpdateEvent; const oldRepositoryName = updateEvent.OldResourceProperties?.RepositoryName; const newRepositoryName = updateEvent.ResourceProperties?.RepositoryName; - const repositoryNameHasChanged = newRepositoryName != null && oldRepositoryName != null && newRepositoryName !== oldRepositoryName; + const repositoryNameHasChanged = (newRepositoryName && oldRepositoryName) + && (newRepositoryName !== oldRepositoryName); /* If the name of the repository has changed, CloudFormation will try to delete the repository and create a new one with the new name. So we have to delete the images in the diff --git a/packages/@aws-cdk/aws-ecr/lib/repository.ts b/packages/@aws-cdk/aws-ecr/lib/repository.ts index 08dc89e296d29..4ead5901c69b2 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository.ts @@ -781,17 +781,11 @@ export class Repository extends RepositoryBase { Effect: 'Allow', Action: [ 'ecr:BatchDeleteImage', - 'ecr:ListImages', - ], - Resource: ['*'], // TODO? - }, - { - Effect: 'Allow', - Action: [ 'ecr:DescribeRepositories', + 'ecr:ListImages', 'ecr:ListTagsForResource', ], - Resource: ['*'], + Resource: [this._resource.attrArn], }, ], }); diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.6150227d515909a73f0bcde4b9e19b4b206cc65634027053380d700f6e53f08e/__entrypoint__.js b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.6150227d515909a73f0bcde4b9e19b4b206cc65634027053380d700f6e53f08e/__entrypoint__.js new file mode 100644 index 0000000000000..c366685b1451b --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.6150227d515909a73f0bcde4b9e19b4b206cc65634027053380d700f6e53f08e/__entrypoint__.js @@ -0,0 +1,147 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.withRetries = exports.handler = exports.external = void 0; +const https = require("https"); +const url = require("url"); +// for unit tests +exports.external = { + sendHttpRequest: defaultSendHttpRequest, + log: defaultLog, + includeStackTraces: true, + userHandlerIndex: './index', +}; +const CREATE_FAILED_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::CREATE_FAILED'; +const MISSING_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID'; +async function handler(event, context) { + const sanitizedEvent = { ...event, ResponseURL: '...' }; + exports.external.log(JSON.stringify(sanitizedEvent, undefined, 2)); + // ignore DELETE event when the physical resource ID is the marker that + // indicates that this DELETE is a subsequent DELETE to a failed CREATE + // operation. + if (event.RequestType === 'Delete' && event.PhysicalResourceId === CREATE_FAILED_PHYSICAL_ID_MARKER) { + exports.external.log('ignoring DELETE event caused by a failed CREATE event'); + await submitResponse('SUCCESS', event); + return; + } + try { + // invoke the user handler. this is intentionally inside the try-catch to + // ensure that if there is an error it's reported as a failure to + // cloudformation (otherwise cfn waits). + // eslint-disable-next-line @typescript-eslint/no-require-imports + const userHandler = require(exports.external.userHandlerIndex).handler; + const result = await userHandler(sanitizedEvent, context); + // validate user response and create the combined event + const responseEvent = renderResponse(event, result); + // submit to cfn as success + await submitResponse('SUCCESS', responseEvent); + } + catch (e) { + const resp = { + ...event, + Reason: exports.external.includeStackTraces ? e.stack : e.message, + }; + if (!resp.PhysicalResourceId) { + // special case: if CREATE fails, which usually implies, we usually don't + // have a physical resource id. in this case, the subsequent DELETE + // operation does not have any meaning, and will likely fail as well. to + // address this, we use a marker so the provider framework can simply + // ignore the subsequent DELETE. + if (event.RequestType === 'Create') { + exports.external.log('CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored'); + resp.PhysicalResourceId = CREATE_FAILED_PHYSICAL_ID_MARKER; + } + else { + // otherwise, if PhysicalResourceId is not specified, something is + // terribly wrong because all other events should have an ID. + exports.external.log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(event)}`); + } + } + // this is an actual error, fail the activity altogether and exist. + await submitResponse('FAILED', resp); + } +} +exports.handler = handler; +function renderResponse(cfnRequest, handlerResponse = {}) { + // if physical ID is not returned, we have some defaults for you based + // on the request type. + const physicalResourceId = handlerResponse.PhysicalResourceId ?? cfnRequest.PhysicalResourceId ?? cfnRequest.RequestId; + // if we are in DELETE and physical ID was changed, it's an error. + if (cfnRequest.RequestType === 'Delete' && physicalResourceId !== cfnRequest.PhysicalResourceId) { + throw new Error(`DELETE: cannot change the physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${handlerResponse.PhysicalResourceId}" during deletion`); + } + // merge request event and result event (result prevails). + return { + ...cfnRequest, + ...handlerResponse, + PhysicalResourceId: physicalResourceId, + }; +} +async function submitResponse(status, event) { + const json = { + Status: status, + Reason: event.Reason ?? status, + StackId: event.StackId, + RequestId: event.RequestId, + PhysicalResourceId: event.PhysicalResourceId || MISSING_PHYSICAL_ID_MARKER, + LogicalResourceId: event.LogicalResourceId, + NoEcho: event.NoEcho, + Data: event.Data, + }; + exports.external.log('submit response to cloudformation', json); + const responseBody = JSON.stringify(json); + const parsedUrl = url.parse(event.ResponseURL); + const req = { + hostname: parsedUrl.hostname, + path: parsedUrl.path, + method: 'PUT', + headers: { + 'content-type': '', + 'content-length': Buffer.byteLength(responseBody, 'utf8'), + }, + }; + const retryOptions = { + attempts: 5, + sleep: 1000, + }; + await withRetries(retryOptions, exports.external.sendHttpRequest)(req, responseBody); +} +async function defaultSendHttpRequest(options, responseBody) { + return new Promise((resolve, reject) => { + try { + const request = https.request(options, _ => resolve()); + request.on('error', reject); + request.write(responseBody); + request.end(); + } + catch (e) { + reject(e); + } + }); +} +function defaultLog(fmt, ...params) { + // eslint-disable-next-line no-console + console.log(fmt, ...params); +} +function withRetries(options, fn) { + return async (...xs) => { + let attempts = options.attempts; + let ms = options.sleep; + while (true) { + try { + return await fn(...xs); + } + catch (e) { + if (attempts-- <= 0) { + throw e; + } + await sleep(Math.floor(Math.random() * ms)); + ms *= 2; + } + } + }; +} +exports.withRetries = withRetries; +async function sleep(ms) { + return new Promise((ok) => setTimeout(ok, ms)); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZWpzLWVudHJ5cG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJub2RlanMtZW50cnlwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwrQkFBK0I7QUFDL0IsMkJBQTJCO0FBRTNCLGlCQUFpQjtBQUNKLFFBQUEsUUFBUSxHQUFHO0lBQ3RCLGVBQWUsRUFBRSxzQkFBc0I7SUFDdkMsR0FBRyxFQUFFLFVBQVU7SUFDZixrQkFBa0IsRUFBRSxJQUFJO0lBQ3hCLGdCQUFnQixFQUFFLFNBQVM7Q0FDNUIsQ0FBQztBQUVGLE1BQU0sZ0NBQWdDLEdBQUcsd0RBQXdELENBQUM7QUFDbEcsTUFBTSwwQkFBMEIsR0FBRyw4REFBOEQsQ0FBQztBQVczRixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtELEVBQUUsT0FBMEI7SUFDMUcsTUFBTSxjQUFjLEdBQUcsRUFBRSxHQUFHLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDeEQsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFM0QsdUVBQXVFO0lBQ3ZFLHVFQUF1RTtJQUN2RSxhQUFhO0lBQ2IsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsa0JBQWtCLEtBQUssZ0NBQWdDLEVBQUU7UUFDbkcsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsdURBQXVELENBQUMsQ0FBQztRQUN0RSxNQUFNLGNBQWMsQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDdkMsT0FBTztLQUNSO0lBRUQsSUFBSTtRQUNGLHlFQUF5RTtRQUN6RSxpRUFBaUU7UUFDakUsd0NBQXdDO1FBQ3hDLGlFQUFpRTtRQUNqRSxNQUFNLFdBQVcsR0FBWSxPQUFPLENBQUMsZ0JBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLE9BQU8sQ0FBQztRQUN4RSxNQUFN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LE9BQU8sS0FBSyxFQUFFLEdBQUcsRUFBSyxFQUFFLEVBQUU7UUFDeEIsSUFBSSxRQUFRLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQztRQUNoQyxJQUFJLEVBQUUsR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDO1FBQ3ZCLE9BQU8sSUFBSSxFQUFFO1lBQ1gsSUFBSTtnQkFDRixPQUFPLE1BQU0sRUFBRSxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUM7YUFDeEI7WUFBQyxPQUFPLENBQUMsRUFBRTtnQkFDVixJQUFJLFFBQVEsRUFBRSxJQUFJLENBQUMsRUFBRTtvQkFDbkIsTUFBTSxDQUFDLENBQUM7aUJBQ1Q7Z0JBQ0QsTUFBTSxLQUFLLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQztnQkFDNUMsRUFBRSxJQUFJLENBQUMsQ0FBQzthQUNUO1NBQ0Y7SUFDSCxDQUFDLENBQUM7QUFDSixDQUFDO0FBaEJELGtDQWdCQztBQUVELEtBQUssVUFBVSxLQUFLLENBQUMsRUFBVTtJQUM3QixPQUFPLElBQUksT0FBTyxDQUFDLENBQUMsRUFBRSxFQUFFLEVBQUUsQ0FBQyxVQUFVLENBQUMsRUFBRSxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUM7QUFDakQsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIGh0dHBzIGZyb20gJ2h0dHBzJztcbmltcG9ydCAqIGFzIHVybCBmcm9tICd1cmwnO1xuXG4vLyBmb3IgdW5pdCB0ZXN0c1xuZXhwb3J0IGNvbnN0IGV4dGVybmFsID0ge1xuICBzZW5kSHR0cFJlcXVlc3Q6IGRlZmF1bHRTZW5kSHR0cFJlcXVlc3QsXG4gIGxvZzogZGVmYXVsdExvZyxcbiAgaW5jbHVkZVN0YWNrVHJhY2VzOiB0cnVlLFxuICB1c2VySGFuZGxlckluZGV4OiAnLi9pbmRleCcsXG59O1xuXG5jb25zdCBDUkVBVEVfRkFJTEVEX1BIWVNJQ0FMX0lEX01BUktFUiA9ICdBV1NDREs6OkN1c3RvbVJlc291cmNlUHJvdmlkZXJGcmFtZXdvcms6OkNSRUFURV9GQUlMRUQnO1xuY29uc3QgTUlTU0lOR19QSFlTSUNBTF9JRF9NQVJLRVIgPSAnQVdTQ0RLOjpDdXN0b21SZXNvdXJjZVByb3ZpZGVyRnJhbWV3b3JrOjpNSVNTSU5HX1BIWVNJQ0FMX0lEJztcblxuZXhwb3J0IHR5cGUgUmVzcG9uc2UgPSBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50ICYgSGFuZGxlclJlc3BvbnNlO1xuZXhwb3J0IHR5cGUgSGFuZGxlciA9IChldmVudDogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCwgY29udGV4dDogQVdTTGFtYmRhLkNvbnRleHQpID0+IFByb21pc2U8SGFuZGxlclJlc3BvbnNlIHwgdm9pZD47XG5leHBvcnQgdHlwZSBIYW5kbGVyUmVzcG9uc2UgPSB1bmRlZmluZWQgfCB7XG4gIERhdGE/OiBhbnk7XG4gIFBoeXNpY2FsUmVzb3VyY2VJZD86IHN0cmluZztcbiAgUmVhc29uPzogc3RyaW5nO1xuICBOb0VjaG8/OiBib29sZWFuO1xufTtcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGhhbmRsZXIoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQsIGNvbnRleHQ6IEFXU0xhbWJkYS5Db250ZXh0KSB7XG4gIGNvbnN0IHNhbml0aXplZEV2ZW50ID0geyAuLi5ldmVudCwgUmVzcG9uc2VVUkw6ICcuLi4nIH07XG4gIGV4dGVybmFsLmxvZyhKU09OLnN0cmluZ2lmeShzYW5pdGl6ZWRFdmVudCwgdW5kZWZpbmVkLCAyKSk7XG5cbiAgLy8gaWdub3JlIERFTEVURSBldmVudCB3aGVuIHRoZSBwaHlzaWNhbCByZXNvdXJjZSBJRCBpcyB0aGUgbWFya2VyIHRoYXRcbiAgLy8gaW5kaWNhdGVzIHRoYXQgdGhpcyBERUxFVEUgaXMgYSBzdWJzZXF1ZW50IERFTEVURSB0byBhIGZhaWxlZCBDUkVBVEVcbiAgLy8gb3BlcmF0aW9uLlxuICBpZiAoZXZlbnQuUmVxdWVzdFR5cGUgPT09ICdEZWxldGUnICYmIGV2ZW50LlBoeXNpY2FsUmVzb3VyY2VJZCA9PT0gQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVIpIHtcbiAgICBleHRlcm5hbC5sb2coJ2lnbm9yaW5nIERFTEVURSBldmVudCBjYXVzZWQgYnkgYSBmYWlsZWQgQ1JFQVRFIGV2ZW50Jyk7XG4gICAgYXdhaXQgc3VibWl0UmVzcG9uc2UoJ1NVQ0NFU1MnLCBldmVudCk7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgdHJ5IHtcbiAgICAvLyBpbnZva2UgdGhlIHVzZXIgaGFuZGxlci4gdGhpcyBpcyBpbnRlbnRpb25hbGx5IGluc2lkZSB0aGUgdHJ5LWNhdGNoIHRvXG4gICAgLy8gZW5zdXJlIHRoYXQgaWYgdGhlcmUgaXMgYW4gZXJyb3IgaXQncyByZXBvcnRlZCBhcyBhIGZhaWx1cmUgdG9cbiAgICAvLyBjbG91ZGZvcm1hdGlvbiAob3RoZXJ3aXNlIGNmbiB3YWl0cykuXG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1yZXF1aXJlLWltcG9ydHNcbiAgICBjb25zdCB1c2VySGFuZGxlcjogSGFuZGxlciA9IHJlcXVpcmUoZXh0ZXJuYWwudXNlckhhbmRsZXJJbmRleCkuaGFuZGxlcjtcbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB1c2VySGFuZGxlcihzYW5pdGl6ZWRFdmVudCwgY29udGV4dCk7XG5cbiAgICAvLyB2YWxpZGF0ZSB1c2VyIHJlc3BvbnNlIGFuZCBjcmVhdGUgdGhlIGNvbWJpbmVkIGV2ZW50XG4gICAgY29uc3QgcmVzcG9uc2VFdmVudCA9IHJlbmRlclJlc3BvbnNlKGV2ZW50LCByZXN1bHQpO1xuXG4gICAgLy8gc3VibWl0IHRvIGNmbiBhcyBzdWNjZXNzXG4gICAgYXdhaXQgc3VibWl0UmVzcG9uc2UoJ1NVQ0NFU1MnLCByZXNwb25zZUV2ZW50KTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGNvbnN0IHJlc3A6IFJlc3BvbnNlID0ge1xuICAgICAgLi4uZXZlbnQsXG4gICAgICBSZWFzb246IGV4dGVybmFsLmluY2x1ZGVTdGFja1RyYWNlcyA/IGUuc3RhY2sgOiBlLm1lc3NhZ2UsXG4gICAgfTtcblxuICAgIGlmICghcmVzcC5QaHlzaWNhbFJlc291cmNlSWQpIHtcbiAgICAgIC8vIHNwZWNpYWwgY2FzZTogaWYgQ1JFQVRFIGZhaWxzLCB3aGljaCB1c3VhbGx5IGltcGxpZXMsIHdlIHVzdWFsbHkgZG9uJ3RcbiAgICAgIC8vIGhhdmUgYSBwaHlzaWNhbCByZXNvdXJjZSBpZC4gaW4gdGhpcyBjYXNlLCB0aGUgc3Vic2VxdWVudCBERUxFVEVcbiAgICAgIC8vIG9wZXJhdGlvbiBkb2VzIG5vdCBoYXZlIGFueSBtZWFuaW5nLCBhbmQgd2lsbCBsaWtlbHkgZmFpbCBhcyB3ZWxsLiB0b1xuICAgICAgLy8gYWRkcmVzcyB0aGlzLCB3ZSB1c2UgYSBtYXJrZXIgc28gdGhlIHByb3ZpZGVyIGZyYW1ld29yayBjYW4gc2ltcGx5XG4gICAgICAvLyBpZ25vcmUgdGhlIHN1YnNlcXVlbnQgREVMRVRFLlxuICAgICAgaWYgKGV2ZW50LlJlcXVlc3RUeXBlID09PSAnQ3JlYXRlJykge1xuICAgICAgICBleHRlcm5hbC5sb2coJ0NSRUFURSBmYWlsZWQsIHJlc3BvbmRpbmcgd2l0aCBhIG1hcmtlciBwaHlzaWNhbCByZXNvdXJjZSBpZCBzbyB0aGF0IHRoZSBzdWJzZXF1ZW50IERFTEVURSB3aWxsIGJlIGlnbm9yZWQnKTtcbiAgICAgICAgcmVzcC5QaHlzaWNhbFJlc291cmNlSWQgPSBDUkVBVEVfRkFJTEVEX1BIWVNJQ0FMX0lEX01BUktFUjtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIG90aGVyd2lzZSwgaWYgUGh5c2ljYWxSZXNvdXJjZUlkIGlzIG5vdCBzcGVjaWZpZWQsIHNvbWV0aGluZyBpc1xuICAgICAgICAvLyB0ZXJyaWJseSB3cm9uZyBiZWNhdXNlIGFsbCBvdGhlciBldmVudHMgc2hvdWxkIGhhdmUgYW4gSUQuXG4gICAgICAgIGV4dGVybmFsLmxvZyhgRVJST1I6IE1hbGZvcm1lZCBldmVudC4gXCJQaHlzaWNhbFJlc291cmNlSWRcIiBpcyByZXF1aXJlZDogJHtKU09OLnN0cmluZ2lmeShldmVudCl9YCk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gdGhpcyBpcyBhbiBhY3R1YWwgZXJyb3IsIGZhaWwgdGhlIGFjdGl2aXR5IGFsdG9nZXRoZXIgYW5kIGV4aXN0LlxuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdGQUlMRUQnLCByZXNwKTtcbiAgfVxufVxuXG5mdW5jdGlvbiByZW5kZXJSZXNwb25zZShcbiAgY2ZuUmVxdWVzdDogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCAmIHsgUGh5c2ljYWxSZXNvdXJjZUlkPzogc3RyaW5nIH0sXG4gIGhhbmRsZXJSZXNwb25zZTogdm9pZCB8IEhhbmRsZXJSZXNwb25zZSA9IHsgfSk6IFJlc3BvbnNlIHtcblxuICAvLyBpZiBwaHlzaWNhbCBJRCBpcyBub3QgcmV0dXJuZWQsIHdlIGhhdmUgc29tZSBkZWZhdWx0cyBmb3IgeW91IGJhc2VkXG4gIC8vIG9uIHRoZSByZXF1ZXN0IHR5cGUuXG4gIGNvbnN0IHBoeXNpY2FsUmVzb3VyY2VJZCA9IGhhbmRsZXJSZXNwb25zZS5QaHlzaWNhbFJlc291cmNlSWQgPz8gY2ZuUmVxdWVzdC5QaHlzaWNhbFJlc291cmNlSWQgPz8gY2ZuUmVxdWVzdC5SZXF1ZXN0SWQ7XG5cbiAgLy8gaWYgd2UgYXJlIGluIERFTEVURSBhbmQgcGh5c2ljYWwgSUQgd2FzIGNoYW5nZWQsIGl0J3MgYW4gZXJyb3IuXG4gIGlmIChjZm5SZXF1ZXN0LlJlcXVlc3RUeXBlID09PSAnRGVsZXRlJyAmJiBwaHlzaWNhbFJlc291cmNlSWQgIT09IGNmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBERUxFVEU6IGNhbm5vdCBjaGFuZ2UgdGhlIHBoeXNpY2FsIHJlc291cmNlIElEIGZyb20gXCIke2NmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkfVwiIHRvIFwiJHtoYW5kbGVyUmVzcG9uc2UuUGh5c2ljYWxSZXNvdXJjZUlkfVwiIGR1cmluZyBkZWxldGlvbmApO1xuICB9XG5cbiAgLy8gbWVyZ2UgcmVxdWVzdCBldmVudCBhbmQgcmVzdWx0IGV2ZW50IChyZXN1bHQgcHJldmFpbHMpLlxuICByZXR1cm4ge1xuICAgIC4uLmNmblJlcXVlc3QsXG4gICAgLi4uaGFuZGxlclJlc3BvbnNlLFxuICAgIFBoeXNpY2FsUmVzb3VyY2VJZDogcGh5c2ljYWxSZXNvdXJjZUlkLFxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBzdWJtaXRSZXNwb25zZShzdGF0dXM6ICdTVUNDRVNTJyB8ICdGQUlMRUQnLCBldmVudDogUmVzcG9uc2UpIHtcbiAgY29uc3QganNvbjogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VSZXNwb25zZSA9IHtcbiAgICBTdGF0dXM6IHN0YXR1cyxcbiAgICBSZWFzb246IGV2ZW50LlJlYXNvbiA/PyBzdGF0dXMsXG4gICAgU3RhY2tJZDogZXZlbnQuU3RhY2tJZCxcbiAgICBSZXF1ZXN0SWQ6IGV2ZW50LlJlcXVlc3RJZCxcbiAgICBQaHlzaWNhbFJlc291cmNlSWQ6IGV2ZW50LlBoeXNpY2FsUmVzb3VyY2VJZCB8fCBNSVNTSU5HX1BIWVNJQ0FMX0lEX01BUktFUixcbiAgICBMb2dpY2FsUmVzb3VyY2VJZDogZXZlbnQuTG9naWNhbFJlc291cmNlSWQsXG4gICAgTm9FY2hvOiBldmVudC5Ob0VjaG8sXG4gICAgRGF0YTogZXZlbnQuRGF0YSxcbiAgfTtcblxuICBleHRlcm5hbC5sb2coJ3N1Ym1pdCByZXNwb25zZSB0byBjbG91ZGZvcm1hdGlvbicsIGpzb24pO1xuXG4gIGNvbnN0IHJlc3BvbnNlQm9keSA9IEpTT04uc3RyaW5naWZ5KGpzb24pO1xuICBjb25zdCBwYXJzZWRVcmwgPSB1cmwucGFyc2UoZXZlbnQuUmVzcG9uc2VVUkwpO1xuICBjb25zdCByZXEgPSB7XG4gICAgaG9zdG5hbWU6IHBhcnNlZFVybC5ob3N0bmFtZSxcbiAgICBwYXRoOiBwYXJzZWRVcmwucGF0aCxcbiAgICBtZXRob2Q6ICdQVVQnLFxuICAgIGhlYWRlcnM6IHtcbiAgICAgICdjb250ZW50LXR5cGUnOiAnJyxcbiAgICAgICdjb250ZW50LWxlbmd0aCc6IEJ1ZmZlci5ieXRlTGVuZ3RoKHJlc3BvbnNlQm9keSwgJ3V0ZjgnKSxcbiAgICB9LFxuICB9O1xuXG4gIGNvbnN0IHJldHJ5T3B0aW9ucyA9IHtcbiAgICBhdHRlbXB0czogNSxcbiAgICBzbGVlcDogMTAwMCxcbiAgfTtcbiAgYXdhaXQgd2l0aFJldHJpZXMocmV0cnlPcHRpb25zLCBleHRlcm5hbC5zZW5kSHR0cFJlcXVlc3QpKHJlcSwgcmVzcG9uc2VCb2R5KTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZGVmYXVsdFNlbmRIdHRwUmVxdWVzdChvcHRpb25zOiBodHRwcy5SZXF1ZXN0T3B0aW9ucywgcmVzcG9uc2VCb2R5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgcmVxdWVzdCA9IGh0dHBzLnJlcXVlc3Qob3B0aW9ucywgXyA9PiByZXNvbHZlKCkpO1xuICAgICAgcmVxdWVzdC5vbignZXJyb3InLCByZWplY3QpO1xuICAgICAgcmVxdWVzdC53cml0ZShyZXNwb25zZUJvZHkpO1xuICAgICAgcmVxdWVzdC5lbmQoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICByZWplY3QoZSk7XG4gICAgfVxuICB9KTtcbn1cblxuZnVuY3Rpb24gZGVmYXVsdExvZyhmbXQ6IHN0cmluZywgLi4ucGFyYW1zOiBhbnlbXSkge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuICBjb25zb2xlLmxvZyhmbXQsIC4uLnBhcmFtcyk7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmV0cnlPcHRpb25zIHtcbiAgLyoqIEhvdyBtYW55IHJldHJpZXMgKHdpbGwgYXQgbGVhc3QgdHJ5IG9uY2UpICovXG4gIHJlYWRvbmx5IGF0dGVtcHRzOiBudW1iZXI7XG4gIC8qKiBTbGVlcCBiYXNlLCBpbiBtcyAqL1xuICByZWFkb25seSBzbGVlcDogbnVtYmVyO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gd2l0aFJldHJpZXM8QSBleHRlbmRzIEFycmF5PGFueT4sIEI+KG9wdGlvbnM6IFJldHJ5T3B0aW9ucywgZm46ICguLi54czogQSkgPT4gUHJvbWlzZTxCPik6ICguLi54czogQSkgPT4gUHJvbWlzZTxCPiB7XG4gIHJldHVybiBhc3luYyAoLi4ueHM6IEEpID0+IHtcbiAgICBsZXQgYXR0ZW1wdHMgPSBvcHRpb25zLmF0dGVtcHRzO1xuICAgIGxldCBtcyA9IG9wdGlvbnMuc2xlZXA7XG4gICAgd2hpbGUgKHRydWUpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBhd2FpdCBmbiguLi54cyk7XG4gICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGlmIChhdHRlbXB0cy0tIDw9IDApIHtcbiAgICAgICAgICB0aHJvdyBlO1xuICAgICAgICB9XG4gICAgICAgIGF3YWl0IHNsZWVwKE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIG1zKSk7XG4gICAgICAgIG1zICo9IDI7XG4gICAgICB9XG4gICAgfVxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBzbGVlcChtczogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgob2spID0+IHNldFRpbWVvdXQob2ssIG1zKSk7XG59Il19 \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.6150227d515909a73f0bcde4b9e19b4b206cc65634027053380d700f6e53f08e/index.js b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.6150227d515909a73f0bcde4b9e19b4b206cc65634027053380d700f6e53f08e/index.js new file mode 100644 index 0000000000000..442bb4f1c65b8 --- /dev/null +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.6150227d515909a73f0bcde4b9e19b4b206cc65634027053380d700f6e53f08e/index.js @@ -0,0 +1,87 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.handler = void 0; +// eslint-disable-next-line import/no-extraneous-dependencies +const aws_sdk_1 = require("aws-sdk"); +const AUTO_DELETE_IMAGES_TAG = 'aws-cdk:auto-delete-images'; +const ecr = new aws_sdk_1.ECR(); +async function handler(event) { + switch (event.RequestType) { + case 'Create': + break; + case 'Update': + return onUpdate(event); + case 'Delete': + return onDelete(event.ResourceProperties?.RepositoryName); + } +} +exports.handler = handler; +async function onUpdate(event) { + const updateEvent = event; + const oldRepositoryName = updateEvent.OldResourceProperties?.RepositoryName; + const newRepositoryName = updateEvent.ResourceProperties?.RepositoryName; + const repositoryNameHasChanged = (newRepositoryName && oldRepositoryName) + && (newRepositoryName !== oldRepositoryName); + /* If the name of the repository has changed, CloudFormation will try to delete the repository + and create a new one with the new name. So we have to delete the images in the + repository so that this operation does not fail. */ + if (repositoryNameHasChanged) { + return onDelete(oldRepositoryName); + } +} +/** + * Recursively delete all images in the repository + * + * @param ECR.ListImagesRequest the repositoryName & nextToken if presented + */ +async function emptyRepository(params) { + const listedImages = await ecr.listImages(params).promise(); + const imageIds = listedImages?.imageIds ?? []; + const nextToken = listedImages.nextToken ?? null; + if (imageIds.length === 0) { + return; + } + await ecr.batchDeleteImage({ + repositoryName: params.repositoryName, + imageIds, + }).promise(); + if (nextToken) { + await emptyRepository({ + ...params, + nextToken, + }); + } +} +async function onDelete(repositoryName) { + if (!repositoryName) { + throw new Error('No RepositoryName was provided.'); + } + const response = await ecr.describeRepositories({ repositoryNames: [repositoryName] }).promise(); + const repository = response.repositories?.find(repo => repo.repositoryName === repositoryName); + if (!await isRepositoryTaggedForDeletion(repository?.repositoryArn)) { + process.stdout.write(`Repository does not have '${AUTO_DELETE_IMAGES_TAG}' tag, skipping cleaning.\n`); + return; + } + try { + await emptyRepository({ repositoryName }); + } + catch (e) { + if (e.name !== 'RepositoryNotFoundException') { + throw e; + } + // Repository doesn't exist. Ignoring + } +} +/** + * The repository will only be tagged for deletion if it's being deleted in the same + * deployment as this Custom Resource. + * + * If the Custom Resource is ever deleted before the repository, it must be because + * `autoDeleteImages` has been switched to false, in which case the tag would have + * been removed before we get to this Delete event. + */ +async function isRepositoryTaggedForDeletion(repositoryArn) { + const response = await ecr.listTagsForResource({ resourceArn: repositoryArn }).promise(); + return response.tags?.some(tag => tag.Key === AUTO_DELETE_IMAGES_TAG && tag.Value === 'true'); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2REFBNkQ7QUFDN0QscUNBQThCO0FBRTlCLE1BQU0sc0JBQXNCLEdBQUcsNEJBQTRCLENBQUM7QUFFNUQsTUFBTSxHQUFHLEdBQUcsSUFBSSxhQUFHLEVBQUUsQ0FBQztBQUVmLEtBQUssVUFBVSxPQUFPLENBQUMsS0FBa0Q7SUFDOUUsUUFBUSxLQUFLLENBQUMsV0FBVyxFQUFFO1FBQ3pCLEtBQUssUUFBUTtZQUNYLE1BQU07UUFDUixLQUFLLFFBQVE7WUFDWCxPQUFPLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN6QixLQUFLLFFBQVE7WUFDWCxPQUFPLFFBQVEsQ0FBQyxLQUFLLENBQUMsa0JBQWtCLEVBQUUsY0FBYyxDQUFDLENBQUM7S0FDN0Q7QUFDSCxDQUFDO0FBVEQsMEJBU0M7QUFFRCxLQUFLLFVBQVUsUUFBUSxDQUFDLEtBQWtEO0lBQ3hFLE1BQU0sV0FBVyxHQUFHLEtBQTBELENBQUM7SUFDL0UsTUFBTSxpQkFBaUIsR0FBRyxXQUFXLENBQUMscUJBQXFCLEVBQUUsY0FBYyxDQUFDO0lBQzVFLE1BQU0saUJBQWlCLEdBQUcsV0FBVyxDQUFDLGtCQUFrQixFQUFFLGNBQWMsQ0FBQztJQUN6RSxNQUFNLHdCQUF3QixHQUFHLENBQUMsaUJBQWlCLElBQUksaUJBQWlCLENBQUM7V0FDcEUsQ0FBQyxpQkFBaUIsS0FBSyxpQkFBaUIsQ0FBQyxDQUFDO0lBRS9DOzswREFFc0Q7SUFDdEQsSUFBSSx3QkFBd0IsRUFBRTtRQUM1QixPQUFPLFFBQVEsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO0tBQ3BDO0FBQ0gsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxLQUFLLFVBQVUsZUFBZSxDQUFDLE1BQTZCO0lBQzFELE1BQU0sWUFBWSxHQUFHLE1BQU0sR0FBRyxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUU1RCxNQUFNLFFBQVEsR0FBRyxZQUFZLEVBQUUsUUFBUSxJQUFJLEVBQUUsQ0FBQztJQUM5QyxNQUFNLFNBQVMsR0FBRyxZQUFZLENBQUMsU0FBUyxJQUFJLElBQUksQ0FBQztJQUNqRCxJQUFJLFFBQVEsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFO1FBQ3pCLE9BQU87S0FDUjtJQUVELE1BQU0sR0FBRyxDQUFDLGdCQUFnQixDQUFDO1FBQ3pCLGNBQWMsRUFBRSxNQUFNLENBQUMsY0FBYztRQUNyQyxRQUFRO0tBQ1QsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBRWIsSUFBSSxTQUFTLEVBQUU7UUFDYixNQUFNLGVBQWUsQ0FBQztZQUNwQixHQUFHLE1BQU07WUFDVCxTQUFTO1NBQ1YsQ0FBQyxDQUFDO0tBQ0o7QUFDSCxDQUFDO0FBRUQsS0FBSyxVQUFVLFFBQVEsQ0FBQyxjQUFzQjtJQUM1QyxJQUFJLENBQUMsY0FBYyxFQUFFO1FBQ25CLE1BQU0sSUFBSSxLQUFLLENBQUMsaUNBQWlDLENBQUMsQ0FBQztLQUNwRDtJQUVELE1BQU0sUUFBUSxHQUFHLE1BQU0sR0FBRyxDQUFDLG9CQUFvQixDQUFDLEVBQUUsZUFBZSxFQUFFLENBQUMsY0FBYyxDQUFDLEVBQUUsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2pHLE1BQU0sVUFBVSxHQUFHLFFBQVEsQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLGNBQWMsS0FBSyxjQUFjLENBQUMsQ0FBQztJQUUvRixJQUFJLENBQUMsTUFBTSw2QkFBNkIsQ0FBQyxVQUFVLEVBQUUsYUFBYyxDQUFDLEVBQUU7UUFDcEUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsNkJBQTZCLHNCQUFzQiw2QkFBNkIsQ0FBQyxDQUFDO1FBQ3ZHLE9BQU87S0FDUjtJQUNELElBQUk7UUFDRixNQUFNLGVBQWUsQ0FBQyxFQUFFLGNBQWMsRUFBRSxDQUFDLENBQUM7S0FDM0M7SUFBQyxPQUFPLENBQUMsRUFBRTtRQUNWLElBQUksQ0FBQyxDQUFDLElBQUksS0FBSyw2QkFBNkIsRUFBRTtZQUM1QyxNQUFNLENBQUMsQ0FBQztTQUNUO1FBQ0QscUNBQXFDO0tBQ3RDO0FBQ0gsQ0FBQztBQUVEOzs7Ozs7O0dBT0c7QUFDSCxLQUFLLFVBQVUsNkJBQTZCLENBQUMsYUFBcUI7SUFDaEUsTUFBTSxRQUFRLEdBQUcsTUFBTSxHQUFHLENBQUMsbUJBQW1CLENBQUMsRUFBRSxXQUFXLEVBQUUsYUFBYSxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUN6RixPQUFPLFFBQVEsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLEdBQUcsS0FBSyxzQkFBc0IsSUFBSSxHQUFHLENBQUMsS0FBSyxLQUFLLE1BQU0sQ0FBQyxDQUFDO0FBQ2hHLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgaW1wb3J0L25vLWV4dHJhbmVvdXMtZGVwZW5kZW5jaWVzXG5pbXBvcnQgeyBFQ1IgfSBmcm9tICdhd3Mtc2RrJztcblxuY29uc3QgQVVUT19ERUxFVEVfSU1BR0VTX1RBRyA9ICdhd3MtY2RrOmF1dG8tZGVsZXRlLWltYWdlcyc7XG5cbmNvbnN0IGVjciA9IG5ldyBFQ1IoKTtcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGhhbmRsZXIoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQpIHtcbiAgc3dpdGNoIChldmVudC5SZXF1ZXN0VHlwZSkge1xuICAgIGNhc2UgJ0NyZWF0ZSc6XG4gICAgICBicmVhaztcbiAgICBjYXNlICdVcGRhdGUnOlxuICAgICAgcmV0dXJuIG9uVXBkYXRlKGV2ZW50KTtcbiAgICBjYXNlICdEZWxldGUnOlxuICAgICAgcmV0dXJuIG9uRGVsZXRlKGV2ZW50LlJlc291cmNlUHJvcGVydGllcz8uUmVwb3NpdG9yeU5hbWUpO1xuICB9XG59XG5cbmFzeW5jIGZ1bmN0aW9uIG9uVXBkYXRlKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50KSB7XG4gIGNvbnN0IHVwZGF0ZUV2ZW50ID0gZXZlbnQgYXMgQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VVcGRhdGVFdmVudDtcbiAgY29uc3Qgb2xkUmVwb3NpdG9yeU5hbWUgPSB1cGRhdGVFdmVudC5PbGRSZXNvdXJjZVByb3BlcnRpZXM/LlJlcG9zaXRvcnlOYW1lO1xuICBjb25zdCBuZXdSZXBvc2l0b3J5TmFtZSA9IHVwZGF0ZUV2ZW50LlJlc291cmNlUHJvcGVydGllcz8uUmVwb3NpdG9yeU5hbWU7XG4gIGNvbnN0IHJlcG9zaXRvcnlOYW1lSGFzQ2hhbmdlZCA9IChuZXdSZXBvc2l0b3J5TmFtZSAmJiBvbGRSZXBvc2l0b3J5TmFtZSlcbiAgICAmJiAobmV3UmVwb3NpdG9yeU5hbWUgIT09IG9sZFJlcG9zaXRvcnlOYW1lKTtcblxuICAvKiBJZiB0aGUgbmFtZSBvZiB0aGUgcmVwb3NpdG9yeSBoYXMgY2hhbmdlZCwgQ2xvdWRGb3JtYXRpb24gd2lsbCB0cnkgdG8gZGVsZXRlIHRoZSByZXBvc2l0b3J5XG4gICAgIGFuZCBjcmVhdGUgYSBuZXcgb25lIHdpdGggdGhlIG5ldyBuYW1lLiBTbyB3ZSBoYXZlIHRvIGRlbGV0ZSB0aGUgaW1hZ2VzIGluIHRoZVxuICAgICByZXBvc2l0b3J5IHNvIHRoYXQgdGhpcyBvcGVyYXRpb24gZG9lcyBub3QgZmFpbC4gKi9cbiAgaWYgKHJlcG9zaXRvcnlOYW1lSGFzQ2hhbmdlZCkge1xuICAgIHJldHVybiBvbkRlbGV0ZShvbGRSZXBvc2l0b3J5TmFtZSk7XG4gIH1cbn1cblxuLyoqXG4gKiBSZWN1cnNpdmVseSBkZWxldGUgYWxsIGltYWdlcyBpbiB0aGUgcmVwb3NpdG9yeVxuICpcbiAqIEBwYXJhbSBFQ1IuTGlzdEltYWdlc1JlcXVlc3QgdGhlIHJlcG9zaXRvcnlOYW1lICYgbmV4dFRva2VuIGlmIHByZXNlbnRlZFxuICovXG5hc3luYyBmdW5jdGlvbiBlbXB0eVJlcG9zaXRvcnkocGFyYW1zOiBFQ1IuTGlzdEltYWdlc1JlcXVlc3QpIHtcbiAgY29uc3QgbGlzdGVkSW1hZ2VzID0gYXdhaXQgZWNyLmxpc3RJbWFnZXMocGFyYW1zKS5wcm9taXNlKCk7XG5cbiAgY29uc3QgaW1hZ2VJZHMgPSBsaXN0ZWRJbWFnZXM/LmltYWdlSWRzID8/IFtdO1xuICBjb25zdCBuZXh0VG9rZW4gPSBsaXN0ZWRJbWFnZXMubmV4dFRva2VuID8/IG51bGw7XG4gIGlmIChpbWFnZUlkcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm47XG4gIH1cblxuICBhd2FpdCBlY3IuYmF0Y2hEZWxldGVJbWFnZSh7XG4gICAgcmVwb3NpdG9yeU5hbWU6IHBhcmFtcy5yZXBvc2l0b3J5TmFtZSxcbiAgICBpbWFnZUlkcyxcbiAgfSkucHJvbWlzZSgpO1xuXG4gIGlmIChuZXh0VG9rZW4pIHtcbiAgICBhd2FpdCBlbXB0eVJlcG9zaXRvcnkoe1xuICAgICAgLi4ucGFyYW1zLFxuICAgICAgbmV4dFRva2VuLFxuICAgIH0pO1xuICB9XG59XG5cbmFzeW5jIGZ1bmN0aW9uIG9uRGVsZXRlKHJlcG9zaXRvcnlOYW1lOiBzdHJpbmcpIHtcbiAgaWYgKCFyZXBvc2l0b3J5TmFtZSkge1xuICAgIHRocm93IG5ldyBFcnJvcignTm8gUmVwb3NpdG9yeU5hbWUgd2FzIHByb3ZpZGVkLicpO1xuICB9XG5cbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBlY3IuZGVzY3JpYmVSZXBvc2l0b3JpZXMoeyByZXBvc2l0b3J5TmFtZXM6IFtyZXBvc2l0b3J5TmFtZV0gfSkucHJvbWlzZSgpO1xuICBjb25zdCByZXBvc2l0b3J5ID0gcmVzcG9uc2UucmVwb3NpdG9yaWVzPy5maW5kKHJlcG8gPT4gcmVwby5yZXBvc2l0b3J5TmFtZSA9PT0gcmVwb3NpdG9yeU5hbWUpO1xuXG4gIGlmICghYXdhaXQgaXNSZXBvc2l0b3J5VGFnZ2VkRm9yRGVsZXRpb24ocmVwb3NpdG9yeT8ucmVwb3NpdG9yeUFybiEpKSB7XG4gICAgcHJvY2Vzcy5zdGRvdXQud3JpdGUoYFJlcG9zaXRvcnkgZG9lcyBub3QgaGF2ZSAnJHtBVVRPX0RFTEVURV9JTUFHRVNfVEFHfScgdGFnLCBza2lwcGluZyBjbGVhbmluZy5cXG5gKTtcbiAgICByZXR1cm47XG4gIH1cbiAgdHJ5IHtcbiAgICBhd2FpdCBlbXB0eVJlcG9zaXRvcnkoeyByZXBvc2l0b3J5TmFtZSB9KTtcbiAgfSBjYXRjaCAoZSkge1xuICAgIGlmIChlLm5hbWUgIT09ICdSZXBvc2l0b3J5Tm90Rm91bmRFeGNlcHRpb24nKSB7XG4gICAgICB0aHJvdyBlO1xuICAgIH1cbiAgICAvLyBSZXBvc2l0b3J5IGRvZXNuJ3QgZXhpc3QuIElnbm9yaW5nXG4gIH1cbn1cblxuLyoqXG4gKiBUaGUgcmVwb3NpdG9yeSB3aWxsIG9ubHkgYmUgdGFnZ2VkIGZvciBkZWxldGlvbiBpZiBpdCdzIGJlaW5nIGRlbGV0ZWQgaW4gdGhlIHNhbWVcbiAqIGRlcGxveW1lbnQgYXMgdGhpcyBDdXN0b20gUmVzb3VyY2UuXG4gKlxuICogSWYgdGhlIEN1c3RvbSBSZXNvdXJjZSBpcyBldmVyIGRlbGV0ZWQgYmVmb3JlIHRoZSByZXBvc2l0b3J5LCBpdCBtdXN0IGJlIGJlY2F1c2VcbiAqIGBhdXRvRGVsZXRlSW1hZ2VzYCBoYXMgYmVlbiBzd2l0Y2hlZCB0byBmYWxzZSwgaW4gd2hpY2ggY2FzZSB0aGUgdGFnIHdvdWxkIGhhdmVcbiAqIGJlZW4gcmVtb3ZlZCBiZWZvcmUgd2UgZ2V0IHRvIHRoaXMgRGVsZXRlIGV2ZW50LlxuICovXG5hc3luYyBmdW5jdGlvbiBpc1JlcG9zaXRvcnlUYWdnZWRGb3JEZWxldGlvbihyZXBvc2l0b3J5QXJuOiBzdHJpbmcpIHtcbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBlY3IubGlzdFRhZ3NGb3JSZXNvdXJjZSh7IHJlc291cmNlQXJuOiByZXBvc2l0b3J5QXJuIH0pLnByb21pc2UoKTtcbiAgcmV0dXJuIHJlc3BvbnNlLnRhZ3M/LnNvbWUodGFnID0+IHRhZy5LZXkgPT09IEFVVE9fREVMRVRFX0lNQUdFU19UQUcgJiYgdGFnLlZhbHVlID09PSAndHJ1ZScpO1xufSJdfQ== \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e/__entrypoint__.js b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e/__entrypoint__.js deleted file mode 100644 index 1e3a3093c1706..0000000000000 --- a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e/__entrypoint__.js +++ /dev/null @@ -1,144 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.withRetries = exports.handler = exports.external = void 0; -const https = require("https"); -const url = require("url"); -// for unit tests -exports.external = { - sendHttpRequest: defaultSendHttpRequest, - log: defaultLog, - includeStackTraces: true, - userHandlerIndex: './index', -}; -const CREATE_FAILED_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::CREATE_FAILED'; -const MISSING_PHYSICAL_ID_MARKER = 'AWSCDK::CustomResourceProviderFramework::MISSING_PHYSICAL_ID'; -async function handler(event, context) { - const sanitizedEvent = { ...event, ResponseURL: '...' }; - exports.external.log(JSON.stringify(sanitizedEvent, undefined, 2)); - // ignore DELETE event when the physical resource ID is the marker that - // indicates that this DELETE is a subsequent DELETE to a failed CREATE - // operation. - if (event.RequestType === 'Delete' && event.PhysicalResourceId === CREATE_FAILED_PHYSICAL_ID_MARKER) { - exports.external.log('ignoring DELETE event caused by a failed CREATE event'); - await submitResponse('SUCCESS', event); - return; - } - try { - // invoke the user handler. this is intentionally inside the try-catch to - // ensure that if there is an error it's reported as a failure to - // cloudformation (otherwise cfn waits). - // eslint-disable-next-line @typescript-eslint/no-require-imports - const userHandler = require(exports.external.userHandlerIndex).handler; - const result = await userHandler(sanitizedEvent, context); - // validate user response and create the combined event - const responseEvent = renderResponse(event, result); - // submit to cfn as success - await submitResponse('SUCCESS', responseEvent); - } - catch (e) { - const resp = { - ...event, - Reason: exports.external.includeStackTraces ? e.stack : e.message, - }; - if (!resp.PhysicalResourceId) { - // special case: if CREATE fails, which usually implies, we usually don't - // have a physical resource id. in this case, the subsequent DELETE - // operation does not have any meaning, and will likely fail as well. to - // address this, we use a marker so the provider framework can simply - // ignore the subsequent DELETE. - if (event.RequestType === 'Create') { - exports.external.log('CREATE failed, responding with a marker physical resource id so that the subsequent DELETE will be ignored'); - resp.PhysicalResourceId = CREATE_FAILED_PHYSICAL_ID_MARKER; - } - else { - // otherwise, if PhysicalResourceId is not specified, something is - // terribly wrong because all other events should have an ID. - exports.external.log(`ERROR: Malformed event. "PhysicalResourceId" is required: ${JSON.stringify(event)}`); - } - } - // this is an actual error, fail the activity altogether and exist. - await submitResponse('FAILED', resp); - } -} -exports.handler = handler; -function renderResponse(cfnRequest, handlerResponse = {}) { - // if physical ID is not returned, we have some defaults for you based - // on the request type. - const physicalResourceId = handlerResponse.PhysicalResourceId ?? cfnRequest.PhysicalResourceId ?? cfnRequest.RequestId; - // if we are in DELETE and physical ID was changed, it's an error. - if (cfnRequest.RequestType === 'Delete' && physicalResourceId !== cfnRequest.PhysicalResourceId) { - throw new Error(`DELETE: cannot change the physical resource ID from "${cfnRequest.PhysicalResourceId}" to "${handlerResponse.PhysicalResourceId}" during deletion`); - } - // merge request event and result event (result prevails). - return { - ...cfnRequest, - ...handlerResponse, - PhysicalResourceId: physicalResourceId, - }; -} -async function submitResponse(status, event) { - const json = { - Status: status, - Reason: event.Reason ?? status, - StackId: event.StackId, - RequestId: event.RequestId, - PhysicalResourceId: event.PhysicalResourceId || MISSING_PHYSICAL_ID_MARKER, - LogicalResourceId: event.LogicalResourceId, - NoEcho: event.NoEcho, - Data: event.Data, - }; - exports.external.log('submit response to cloudformation', json); - const responseBody = JSON.stringify(json); - const parsedUrl = url.parse(event.ResponseURL); - const req = { - hostname: parsedUrl.hostname, - path: parsedUrl.path, - method: 'PUT', - headers: { 'content-type': '', 'content-length': responseBody.length }, - }; - const retryOptions = { - attempts: 5, - sleep: 1000, - }; - await withRetries(retryOptions, exports.external.sendHttpRequest)(req, responseBody); -} -async function defaultSendHttpRequest(options, responseBody) { - return new Promise((resolve, reject) => { - try { - const request = https.request(options, _ => resolve()); - request.on('error', reject); - request.write(responseBody); - request.end(); - } - catch (e) { - reject(e); - } - }); -} -function defaultLog(fmt, ...params) { - // eslint-disable-next-line no-console - console.log(fmt, ...params); -} -function withRetries(options, fn) { - return async (...xs) => { - let attempts = options.attempts; - let ms = options.sleep; - while (true) { - try { - return await fn(...xs); - } - catch (e) { - if (attempts-- <= 0) { - throw e; - } - await sleep(Math.floor(Math.random() * ms)); - ms *= 2; - } - } - }; -} -exports.withRetries = withRetries; -async function sleep(ms) { - return new Promise((ok) => setTimeout(ok, ms)); -} -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZWpzLWVudHJ5cG9pbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJub2RlanMtZW50cnlwb2ludC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwrQkFBK0I7QUFDL0IsMkJBQTJCO0FBRTNCLGlCQUFpQjtBQUNKLFFBQUEsUUFBUSxHQUFHO0lBQ3RCLGVBQWUsRUFBRSxzQkFBc0I7SUFDdkMsR0FBRyxFQUFFLFVBQVU7SUFDZixrQkFBa0IsRUFBRSxJQUFJO0lBQ3hCLGdCQUFnQixFQUFFLFNBQVM7Q0FDNUIsQ0FBQztBQUVGLE1BQU0sZ0NBQWdDLEdBQUcsd0RBQXdELENBQUM7QUFDbEcsTUFBTSwwQkFBMEIsR0FBRyw4REFBOEQsQ0FBQztBQVczRixLQUFLLFVBQVUsT0FBTyxDQUFDLEtBQWtELEVBQUUsT0FBMEI7SUFDMUcsTUFBTSxjQUFjLEdBQUcsRUFBRSxHQUFHLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDeEQsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFM0QsdUVBQXVFO0lBQ3ZFLHVFQUF1RTtJQUN2RSxhQUFhO0lBQ2IsSUFBSSxLQUFLLENBQUMsV0FBVyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsa0JBQWtCLEtBQUssZ0NBQWdDLEVBQUU7UUFDbkcsZ0JBQVEsQ0FBQyxHQUFHLENBQUMsdURBQXVELENBQUMsQ0FBQztRQUN0RSxNQUFNLGNBQWMsQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDdkMsT0FBTztLQUNSO0lBRUQsSUFBSTtRQUNGLHlFQUF5RTtRQUN6RSxpRUFBaUU7UUFDakUsd0NBQXdDO1FBQ3hDLGlFQUFpRTtRQUNqRSxNQUFNLFdBQVcsR0FBWSxPQUFPLENBQUMsZ0JBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLE9BQU8sQ0FBQztRQUN4RSxNQUFN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RUFBRSxFQUFFO1FBQ3hCLElBQUksUUFBUSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUM7UUFDaEMsSUFBSSxFQUFFLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQztRQUN2QixPQUFPLElBQUksRUFBRTtZQUNYLElBQUk7Z0JBQ0YsT0FBTyxNQUFNLEVBQUUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDO2FBQ3hCO1lBQUMsT0FBTyxDQUFDLEVBQUU7Z0JBQ1YsSUFBSSxRQUFRLEVBQUUsSUFBSSxDQUFDLEVBQUU7b0JBQ25CLE1BQU0sQ0FBQyxDQUFDO2lCQUNUO2dCQUNELE1BQU0sS0FBSyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7Z0JBQzVDLEVBQUUsSUFBSSxDQUFDLENBQUM7YUFDVDtTQUNGO0lBQ0gsQ0FBQyxDQUFDO0FBQ0osQ0FBQztBQWhCRCxrQ0FnQkM7QUFFRCxLQUFLLFVBQVUsS0FBSyxDQUFDLEVBQVU7SUFDN0IsT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLEVBQUUsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ2pELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBodHRwcyBmcm9tICdodHRwcyc7XG5pbXBvcnQgKiBhcyB1cmwgZnJvbSAndXJsJztcblxuLy8gZm9yIHVuaXQgdGVzdHNcbmV4cG9ydCBjb25zdCBleHRlcm5hbCA9IHtcbiAgc2VuZEh0dHBSZXF1ZXN0OiBkZWZhdWx0U2VuZEh0dHBSZXF1ZXN0LFxuICBsb2c6IGRlZmF1bHRMb2csXG4gIGluY2x1ZGVTdGFja1RyYWNlczogdHJ1ZSxcbiAgdXNlckhhbmRsZXJJbmRleDogJy4vaW5kZXgnLFxufTtcblxuY29uc3QgQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVIgPSAnQVdTQ0RLOjpDdXN0b21SZXNvdXJjZVByb3ZpZGVyRnJhbWV3b3JrOjpDUkVBVEVfRkFJTEVEJztcbmNvbnN0IE1JU1NJTkdfUEhZU0lDQUxfSURfTUFSS0VSID0gJ0FXU0NESzo6Q3VzdG9tUmVzb3VyY2VQcm92aWRlckZyYW1ld29yazo6TUlTU0lOR19QSFlTSUNBTF9JRCc7XG5cbmV4cG9ydCB0eXBlIFJlc3BvbnNlID0gQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCAmIEhhbmRsZXJSZXNwb25zZTtcbmV4cG9ydCB0eXBlIEhhbmRsZXIgPSAoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQsIGNvbnRleHQ6IEFXU0xhbWJkYS5Db250ZXh0KSA9PiBQcm9taXNlPEhhbmRsZXJSZXNwb25zZSB8IHZvaWQ+O1xuZXhwb3J0IHR5cGUgSGFuZGxlclJlc3BvbnNlID0gdW5kZWZpbmVkIHwge1xuICBEYXRhPzogYW55O1xuICBQaHlzaWNhbFJlc291cmNlSWQ/OiBzdHJpbmc7XG4gIFJlYXNvbj86IHN0cmluZztcbiAgTm9FY2hvPzogYm9vbGVhbjtcbn07XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50OiBBV1NMYW1iZGEuQ2xvdWRGb3JtYXRpb25DdXN0b21SZXNvdXJjZUV2ZW50LCBjb250ZXh0OiBBV1NMYW1iZGEuQ29udGV4dCkge1xuICBjb25zdCBzYW5pdGl6ZWRFdmVudCA9IHsgLi4uZXZlbnQsIFJlc3BvbnNlVVJMOiAnLi4uJyB9O1xuICBleHRlcm5hbC5sb2coSlNPTi5zdHJpbmdpZnkoc2FuaXRpemVkRXZlbnQsIHVuZGVmaW5lZCwgMikpO1xuXG4gIC8vIGlnbm9yZSBERUxFVEUgZXZlbnQgd2hlbiB0aGUgcGh5c2ljYWwgcmVzb3VyY2UgSUQgaXMgdGhlIG1hcmtlciB0aGF0XG4gIC8vIGluZGljYXRlcyB0aGF0IHRoaXMgREVMRVRFIGlzIGEgc3Vic2VxdWVudCBERUxFVEUgdG8gYSBmYWlsZWQgQ1JFQVRFXG4gIC8vIG9wZXJhdGlvbi5cbiAgaWYgKGV2ZW50LlJlcXVlc3RUeXBlID09PSAnRGVsZXRlJyAmJiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgPT09IENSRUFURV9GQUlMRURfUEhZU0lDQUxfSURfTUFSS0VSKSB7XG4gICAgZXh0ZXJuYWwubG9nKCdpZ25vcmluZyBERUxFVEUgZXZlbnQgY2F1c2VkIGJ5IGEgZmFpbGVkIENSRUFURSBldmVudCcpO1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgZXZlbnQpO1xuICAgIHJldHVybjtcbiAgfVxuXG4gIHRyeSB7XG4gICAgLy8gaW52b2tlIHRoZSB1c2VyIGhhbmRsZXIuIHRoaXMgaXMgaW50ZW50aW9uYWxseSBpbnNpZGUgdGhlIHRyeS1jYXRjaCB0b1xuICAgIC8vIGVuc3VyZSB0aGF0IGlmIHRoZXJlIGlzIGFuIGVycm9yIGl0J3MgcmVwb3J0ZWQgYXMgYSBmYWlsdXJlIHRvXG4gICAgLy8gY2xvdWRmb3JtYXRpb24gKG90aGVyd2lzZSBjZm4gd2FpdHMpLlxuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tcmVxdWlyZS1pbXBvcnRzXG4gICAgY29uc3QgdXNlckhhbmRsZXI6IEhhbmRsZXIgPSByZXF1aXJlKGV4dGVybmFsLnVzZXJIYW5kbGVySW5kZXgpLmhhbmRsZXI7XG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdXNlckhhbmRsZXIoc2FuaXRpemVkRXZlbnQsIGNvbnRleHQpO1xuXG4gICAgLy8gdmFsaWRhdGUgdXNlciByZXNwb25zZSBhbmQgY3JlYXRlIHRoZSBjb21iaW5lZCBldmVudFxuICAgIGNvbnN0IHJlc3BvbnNlRXZlbnQgPSByZW5kZXJSZXNwb25zZShldmVudCwgcmVzdWx0KTtcblxuICAgIC8vIHN1Ym1pdCB0byBjZm4gYXMgc3VjY2Vzc1xuICAgIGF3YWl0IHN1Ym1pdFJlc3BvbnNlKCdTVUNDRVNTJywgcmVzcG9uc2VFdmVudCk7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBjb25zdCByZXNwOiBSZXNwb25zZSA9IHtcbiAgICAgIC4uLmV2ZW50LFxuICAgICAgUmVhc29uOiBleHRlcm5hbC5pbmNsdWRlU3RhY2tUcmFjZXMgPyBlLnN0YWNrIDogZS5tZXNzYWdlLFxuICAgIH07XG5cbiAgICBpZiAoIXJlc3AuUGh5c2ljYWxSZXNvdXJjZUlkKSB7XG4gICAgICAvLyBzcGVjaWFsIGNhc2U6IGlmIENSRUFURSBmYWlscywgd2hpY2ggdXN1YWxseSBpbXBsaWVzLCB3ZSB1c3VhbGx5IGRvbid0XG4gICAgICAvLyBoYXZlIGEgcGh5c2ljYWwgcmVzb3VyY2UgaWQuIGluIHRoaXMgY2FzZSwgdGhlIHN1YnNlcXVlbnQgREVMRVRFXG4gICAgICAvLyBvcGVyYXRpb24gZG9lcyBub3QgaGF2ZSBhbnkgbWVhbmluZywgYW5kIHdpbGwgbGlrZWx5IGZhaWwgYXMgd2VsbC4gdG9cbiAgICAgIC8vIGFkZHJlc3MgdGhpcywgd2UgdXNlIGEgbWFya2VyIHNvIHRoZSBwcm92aWRlciBmcmFtZXdvcmsgY2FuIHNpbXBseVxuICAgICAgLy8gaWdub3JlIHRoZSBzdWJzZXF1ZW50IERFTEVURS5cbiAgICAgIGlmIChldmVudC5SZXF1ZXN0VHlwZSA9PT0gJ0NyZWF0ZScpIHtcbiAgICAgICAgZXh0ZXJuYWwubG9nKCdDUkVBVEUgZmFpbGVkLCByZXNwb25kaW5nIHdpdGggYSBtYXJrZXIgcGh5c2ljYWwgcmVzb3VyY2UgaWQgc28gdGhhdCB0aGUgc3Vic2VxdWVudCBERUxFVEUgd2lsbCBiZSBpZ25vcmVkJyk7XG4gICAgICAgIHJlc3AuUGh5c2ljYWxSZXNvdXJjZUlkID0gQ1JFQVRFX0ZBSUxFRF9QSFlTSUNBTF9JRF9NQVJLRVI7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBvdGhlcndpc2UsIGlmIFBoeXNpY2FsUmVzb3VyY2VJZCBpcyBub3Qgc3BlY2lmaWVkLCBzb21ldGhpbmcgaXNcbiAgICAgICAgLy8gdGVycmlibHkgd3JvbmcgYmVjYXVzZSBhbGwgb3RoZXIgZXZlbnRzIHNob3VsZCBoYXZlIGFuIElELlxuICAgICAgICBleHRlcm5hbC5sb2coYEVSUk9SOiBNYWxmb3JtZWQgZXZlbnQuIFwiUGh5c2ljYWxSZXNvdXJjZUlkXCIgaXMgcmVxdWlyZWQ6ICR7SlNPTi5zdHJpbmdpZnkoZXZlbnQpfWApO1xuICAgICAgfVxuICAgIH1cblxuICAgIC8vIHRoaXMgaXMgYW4gYWN0dWFsIGVycm9yLCBmYWlsIHRoZSBhY3Rpdml0eSBhbHRvZ2V0aGVyIGFuZCBleGlzdC5cbiAgICBhd2FpdCBzdWJtaXRSZXNwb25zZSgnRkFJTEVEJywgcmVzcCk7XG4gIH1cbn1cblxuZnVuY3Rpb24gcmVuZGVyUmVzcG9uc2UoXG4gIGNmblJlcXVlc3Q6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQgJiB7IFBoeXNpY2FsUmVzb3VyY2VJZD86IHN0cmluZyB9LFxuICBoYW5kbGVyUmVzcG9uc2U6IHZvaWQgfCBIYW5kbGVyUmVzcG9uc2UgPSB7IH0pOiBSZXNwb25zZSB7XG5cbiAgLy8gaWYgcGh5c2ljYWwgSUQgaXMgbm90IHJldHVybmVkLCB3ZSBoYXZlIHNvbWUgZGVmYXVsdHMgZm9yIHlvdSBiYXNlZFxuICAvLyBvbiB0aGUgcmVxdWVzdCB0eXBlLlxuICBjb25zdCBwaHlzaWNhbFJlc291cmNlSWQgPSBoYW5kbGVyUmVzcG9uc2UuUGh5c2ljYWxSZXNvdXJjZUlkID8/IGNmblJlcXVlc3QuUGh5c2ljYWxSZXNvdXJjZUlkID8/IGNmblJlcXVlc3QuUmVxdWVzdElkO1xuXG4gIC8vIGlmIHdlIGFyZSBpbiBERUxFVEUgYW5kIHBoeXNpY2FsIElEIHdhcyBjaGFuZ2VkLCBpdCdzIGFuIGVycm9yLlxuICBpZiAoY2ZuUmVxdWVzdC5SZXF1ZXN0VHlwZSA9PT0gJ0RlbGV0ZScgJiYgcGh5c2ljYWxSZXNvdXJjZUlkICE9PSBjZm5SZXF1ZXN0LlBoeXNpY2FsUmVzb3VyY2VJZCkge1xuICAgIHRocm93IG5ldyBFcnJvcihgREVMRVRFOiBjYW5ub3QgY2hhbmdlIHRoZSBwaHlzaWNhbCByZXNvdXJjZSBJRCBmcm9tIFwiJHtjZm5SZXF1ZXN0LlBoeXNpY2FsUmVzb3VyY2VJZH1cIiB0byBcIiR7aGFuZGxlclJlc3BvbnNlLlBoeXNpY2FsUmVzb3VyY2VJZH1cIiBkdXJpbmcgZGVsZXRpb25gKTtcbiAgfVxuXG4gIC8vIG1lcmdlIHJlcXVlc3QgZXZlbnQgYW5kIHJlc3VsdCBldmVudCAocmVzdWx0IHByZXZhaWxzKS5cbiAgcmV0dXJuIHtcbiAgICAuLi5jZm5SZXF1ZXN0LFxuICAgIC4uLmhhbmRsZXJSZXNwb25zZSxcbiAgICBQaHlzaWNhbFJlc291cmNlSWQ6IHBoeXNpY2FsUmVzb3VyY2VJZCxcbiAgfTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gc3VibWl0UmVzcG9uc2Uoc3RhdHVzOiAnU1VDQ0VTUycgfCAnRkFJTEVEJywgZXZlbnQ6IFJlc3BvbnNlKSB7XG4gIGNvbnN0IGpzb246IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlUmVzcG9uc2UgPSB7XG4gICAgU3RhdHVzOiBzdGF0dXMsXG4gICAgUmVhc29uOiBldmVudC5SZWFzb24gPz8gc3RhdHVzLFxuICAgIFN0YWNrSWQ6IGV2ZW50LlN0YWNrSWQsXG4gICAgUmVxdWVzdElkOiBldmVudC5SZXF1ZXN0SWQsXG4gICAgUGh5c2ljYWxSZXNvdXJjZUlkOiBldmVudC5QaHlzaWNhbFJlc291cmNlSWQgfHwgTUlTU0lOR19QSFlTSUNBTF9JRF9NQVJLRVIsXG4gICAgTG9naWNhbFJlc291cmNlSWQ6IGV2ZW50LkxvZ2ljYWxSZXNvdXJjZUlkLFxuICAgIE5vRWNobzogZXZlbnQuTm9FY2hvLFxuICAgIERhdGE6IGV2ZW50LkRhdGEsXG4gIH07XG5cbiAgZXh0ZXJuYWwubG9nKCdzdWJtaXQgcmVzcG9uc2UgdG8gY2xvdWRmb3JtYXRpb24nLCBqc29uKTtcblxuICBjb25zdCByZXNwb25zZUJvZHkgPSBKU09OLnN0cmluZ2lmeShqc29uKTtcbiAgY29uc3QgcGFyc2VkVXJsID0gdXJsLnBhcnNlKGV2ZW50LlJlc3BvbnNlVVJMKTtcbiAgY29uc3QgcmVxID0ge1xuICAgIGhvc3RuYW1lOiBwYXJzZWRVcmwuaG9zdG5hbWUsXG4gICAgcGF0aDogcGFyc2VkVXJsLnBhdGgsXG4gICAgbWV0aG9kOiAnUFVUJyxcbiAgICBoZWFkZXJzOiB7ICdjb250ZW50LXR5cGUnOiAnJywgJ2NvbnRlbnQtbGVuZ3RoJzogcmVzcG9uc2VCb2R5Lmxlbmd0aCB9LFxuICB9O1xuXG4gIGNvbnN0IHJldHJ5T3B0aW9ucyA9IHtcbiAgICBhdHRlbXB0czogNSxcbiAgICBzbGVlcDogMTAwMCxcbiAgfTtcbiAgYXdhaXQgd2l0aFJldHJpZXMocmV0cnlPcHRpb25zLCBleHRlcm5hbC5zZW5kSHR0cFJlcXVlc3QpKHJlcSwgcmVzcG9uc2VCb2R5KTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZGVmYXVsdFNlbmRIdHRwUmVxdWVzdChvcHRpb25zOiBodHRwcy5SZXF1ZXN0T3B0aW9ucywgcmVzcG9uc2VCb2R5OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICB0cnkge1xuICAgICAgY29uc3QgcmVxdWVzdCA9IGh0dHBzLnJlcXVlc3Qob3B0aW9ucywgXyA9PiByZXNvbHZlKCkpO1xuICAgICAgcmVxdWVzdC5vbignZXJyb3InLCByZWplY3QpO1xuICAgICAgcmVxdWVzdC53cml0ZShyZXNwb25zZUJvZHkpO1xuICAgICAgcmVxdWVzdC5lbmQoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICByZWplY3QoZSk7XG4gICAgfVxuICB9KTtcbn1cblxuZnVuY3Rpb24gZGVmYXVsdExvZyhmbXQ6IHN0cmluZywgLi4ucGFyYW1zOiBhbnlbXSkge1xuICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgbm8tY29uc29sZVxuICBjb25zb2xlLmxvZyhmbXQsIC4uLnBhcmFtcyk7XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmV0cnlPcHRpb25zIHtcbiAgLyoqIEhvdyBtYW55IHJldHJpZXMgKHdpbGwgYXQgbGVhc3QgdHJ5IG9uY2UpICovXG4gIHJlYWRvbmx5IGF0dGVtcHRzOiBudW1iZXI7XG4gIC8qKiBTbGVlcCBiYXNlLCBpbiBtcyAqL1xuICByZWFkb25seSBzbGVlcDogbnVtYmVyO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gd2l0aFJldHJpZXM8QSBleHRlbmRzIEFycmF5PGFueT4sIEI+KG9wdGlvbnM6IFJldHJ5T3B0aW9ucywgZm46ICguLi54czogQSkgPT4gUHJvbWlzZTxCPik6ICguLi54czogQSkgPT4gUHJvbWlzZTxCPiB7XG4gIHJldHVybiBhc3luYyAoLi4ueHM6IEEpID0+IHtcbiAgICBsZXQgYXR0ZW1wdHMgPSBvcHRpb25zLmF0dGVtcHRzO1xuICAgIGxldCBtcyA9IG9wdGlvbnMuc2xlZXA7XG4gICAgd2hpbGUgKHRydWUpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiBhd2FpdCBmbiguLi54cyk7XG4gICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGlmIChhdHRlbXB0cy0tIDw9IDApIHtcbiAgICAgICAgICB0aHJvdyBlO1xuICAgICAgICB9XG4gICAgICAgIGF3YWl0IHNsZWVwKE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIG1zKSk7XG4gICAgICAgIG1zICo9IDI7XG4gICAgICB9XG4gICAgfVxuICB9O1xufVxuXG5hc3luYyBmdW5jdGlvbiBzbGVlcChtczogbnVtYmVyKTogUHJvbWlzZTx2b2lkPiB7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgob2spID0+IHNldFRpbWVvdXQob2ssIG1zKSk7XG59Il19 \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e/index.js b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e/index.js deleted file mode 100644 index 4d66880dac823..0000000000000 --- a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e/index.js +++ /dev/null @@ -1,85 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.handler = void 0; -// eslint-disable-next-line import/no-extraneous-dependencies -const aws_sdk_1 = require("aws-sdk"); -const AUTO_DELETE_IMAGES_TAG = 'aws-cdk:auto-delete-images'; -const ecr = new aws_sdk_1.ECR(); -async function handler(event) { - switch (event.RequestType) { - case 'Create': - case 'Update': - return onUpdate(event); - case 'Delete': - return onDelete(event.ResourceProperties?.RepositoryName); - } -} -exports.handler = handler; -async function onUpdate(event) { - const updateEvent = event; - const oldRepositoryName = updateEvent.OldResourceProperties?.RepositoryName; - const newRepositoryName = updateEvent.ResourceProperties?.RepositoryName; - const repositoryNameHasChanged = newRepositoryName != null && oldRepositoryName != null && newRepositoryName !== oldRepositoryName; - /* If the name of the repository has changed, CloudFormation will try to delete the repository - and create a new one with the new name. So we have to delete the images in the - repository so that this operation does not fail. */ - if (repositoryNameHasChanged) { - return onDelete(oldRepositoryName); - } -} -/** - * Recursively delete all images in the repository - * - * @param ECR.ListImagesRequest the repositoryName & nextToken if presented - */ -async function emptyRepository(params) { - const listedImages = await ecr.listImages(params).promise(); - const imageIds = listedImages?.imageIds ?? []; - const nextToken = listedImages.nextToken ?? null; - if (imageIds.length === 0) { - return; - } - await ecr.batchDeleteImage({ - repositoryName: params.repositoryName, - imageIds, - }).promise(); - if (nextToken) { - await emptyRepository({ - ...params, - nextToken, - }); - } -} -async function onDelete(repositoryName) { - if (!repositoryName) { - throw new Error('No RepositoryName was provided.'); - } - const response = await ecr.describeRepositories({ repositoryNames: [repositoryName] }).promise(); - const repository = response.repositories?.find(repo => repo.repositoryName === repositoryName); - if (!await isRepositoryTaggedForDeletion(repository?.repositoryArn)) { - process.stdout.write(`Repository does not have '${AUTO_DELETE_IMAGES_TAG}' tag, skipping cleaning.\n`); - return; - } - try { - await emptyRepository({ repositoryName }); - } - catch (e) { - if (e.name !== 'RepositoryNotFoundException') { - throw e; - } - // Repository doesn't exist. Ignoring - } -} -/** - * The repository will only be tagged for deletion if it's being deleted in the same - * deployment as this Custom Resource. - * - * If the Custom Resource is ever deleted before the repository, it must be because - * `autoDeleteImages` has been switched to false, in which case the tag would have - * been removed before we get to this Delete event. - */ -async function isRepositoryTaggedForDeletion(repositoryArn) { - const response = await ecr.listTagsForResource({ resourceArn: repositoryArn }).promise(); - return response.tags?.some(tag => tag.Key === AUTO_DELETE_IMAGES_TAG && tag.Value === 'true'); -} -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2REFBNkQ7QUFDN0QscUNBQThCO0FBRTlCLE1BQU0sc0JBQXNCLEdBQUcsNEJBQTRCLENBQUM7QUFFNUQsTUFBTSxHQUFHLEdBQUcsSUFBSSxhQUFHLEVBQUUsQ0FBQztBQUVmLEtBQUssVUFBVSxPQUFPLENBQUMsS0FBa0Q7SUFDOUUsUUFBUSxLQUFLLENBQUMsV0FBVyxFQUFFO1FBQ3pCLEtBQUssUUFBUSxDQUFDO1FBQ2QsS0FBSyxRQUFRO1lBQ1gsT0FBTyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDekIsS0FBSyxRQUFRO1lBQ1gsT0FBTyxRQUFRLENBQUMsS0FBSyxDQUFDLGtCQUFrQixFQUFFLGNBQWMsQ0FBQyxDQUFDO0tBQzdEO0FBQ0gsQ0FBQztBQVJELDBCQVFDO0FBRUQsS0FBSyxVQUFVLFFBQVEsQ0FBQyxLQUFrRDtJQUN4RSxNQUFNLFdBQVcsR0FBRyxLQUEwRCxDQUFDO0lBQy9FLE1BQU0saUJBQWlCLEdBQUcsV0FBVyxDQUFDLHFCQUFxQixFQUFFLGNBQWMsQ0FBQztJQUM1RSxNQUFNLGlCQUFpQixHQUFHLFdBQVcsQ0FBQyxrQkFBa0IsRUFBRSxjQUFjLENBQUM7SUFDekUsTUFBTSx3QkFBd0IsR0FBRyxpQkFBaUIsSUFBSSxJQUFJLElBQUksaUJBQWlCLElBQUksSUFBSSxJQUFJLGlCQUFpQixLQUFLLGlCQUFpQixDQUFDO0lBRW5JOzswREFFc0Q7SUFDdEQsSUFBSSx3QkFBd0IsRUFBRTtRQUM1QixPQUFPLFFBQVEsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO0tBQ3BDO0FBQ0gsQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxLQUFLLFVBQVUsZUFBZSxDQUFDLE1BQTZCO0lBQzFELE1BQU0sWUFBWSxHQUFHLE1BQU0sR0FBRyxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUU1RCxNQUFNLFFBQVEsR0FBRyxZQUFZLEVBQUUsUUFBUSxJQUFJLEVBQUUsQ0FBQztJQUM5QyxNQUFNLFNBQVMsR0FBRyxZQUFZLENBQUMsU0FBUyxJQUFJLElBQUksQ0FBQztJQUNqRCxJQUFJLFFBQVEsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFO1FBQ3pCLE9BQU87S0FDUjtJQUVELE1BQU0sR0FBRyxDQUFDLGdCQUFnQixDQUFDO1FBQ3pCLGNBQWMsRUFBRSxNQUFNLENBQUMsY0FBYztRQUNyQyxRQUFRO0tBQ1QsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBRWIsSUFBSSxTQUFTLEVBQUU7UUFDYixNQUFNLGVBQWUsQ0FBQztZQUNwQixHQUFHLE1BQU07WUFDVCxTQUFTO1NBQ1YsQ0FBQyxDQUFDO0tBQ0o7QUFDSCxDQUFDO0FBRUQsS0FBSyxVQUFVLFFBQVEsQ0FBQyxjQUFzQjtJQUM1QyxJQUFJLENBQUMsY0FBYyxFQUFFO1FBQ25CLE1BQU0sSUFBSSxLQUFLLENBQUMsaUNBQWlDLENBQUMsQ0FBQztLQUNwRDtJQUVELE1BQU0sUUFBUSxHQUFHLE1BQU0sR0FBRyxDQUFDLG9CQUFvQixDQUFDLEVBQUUsZUFBZSxFQUFFLENBQUMsY0FBYyxDQUFDLEVBQUUsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQ2pHLE1BQU0sVUFBVSxHQUFHLFFBQVEsQ0FBQyxZQUFZLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLGNBQWMsS0FBSyxjQUFjLENBQUMsQ0FBQztJQUUvRixJQUFJLENBQUMsTUFBTSw2QkFBNkIsQ0FBQyxVQUFVLEVBQUUsYUFBYyxDQUFDLEVBQUU7UUFDcEUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsNkJBQTZCLHNCQUFzQiw2QkFBNkIsQ0FBQyxDQUFDO1FBQ3ZHLE9BQU87S0FDUjtJQUNELElBQUk7UUFDRixNQUFNLGVBQWUsQ0FBQyxFQUFFLGNBQWMsRUFBRSxDQUFDLENBQUM7S0FDM0M7SUFBQyxPQUFPLENBQUMsRUFBRTtRQUNWLElBQUksQ0FBQyxDQUFDLElBQUksS0FBSyw2QkFBNkIsRUFBRTtZQUM1QyxNQUFNLENBQUMsQ0FBQztTQUNUO1FBQ0QscUNBQXFDO0tBQ3RDO0FBQ0gsQ0FBQztBQUVEOzs7Ozs7O0dBT0c7QUFDSCxLQUFLLFVBQVUsNkJBQTZCLENBQUMsYUFBcUI7SUFDaEUsTUFBTSxRQUFRLEdBQUcsTUFBTSxHQUFHLENBQUMsbUJBQW1CLENBQUMsRUFBRSxXQUFXLEVBQUUsYUFBYSxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUN6RixPQUFPLFFBQVEsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLEdBQUcsS0FBSyxzQkFBc0IsSUFBSSxHQUFHLENBQUMsS0FBSyxLQUFLLE1BQU0sQ0FBQyxDQUFDO0FBQ2hHLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgaW1wb3J0L25vLWV4dHJhbmVvdXMtZGVwZW5kZW5jaWVzXG5pbXBvcnQgeyBFQ1IgfSBmcm9tICdhd3Mtc2RrJztcblxuY29uc3QgQVVUT19ERUxFVEVfSU1BR0VTX1RBRyA9ICdhd3MtY2RrOmF1dG8tZGVsZXRlLWltYWdlcyc7XG5cbmNvbnN0IGVjciA9IG5ldyBFQ1IoKTtcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGhhbmRsZXIoZXZlbnQ6IEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlRXZlbnQpIHtcbiAgc3dpdGNoIChldmVudC5SZXF1ZXN0VHlwZSkge1xuICAgIGNhc2UgJ0NyZWF0ZSc6XG4gICAgY2FzZSAnVXBkYXRlJzpcbiAgICAgIHJldHVybiBvblVwZGF0ZShldmVudCk7XG4gICAgY2FzZSAnRGVsZXRlJzpcbiAgICAgIHJldHVybiBvbkRlbGV0ZShldmVudC5SZXNvdXJjZVByb3BlcnRpZXM/LlJlcG9zaXRvcnlOYW1lKTtcbiAgfVxufVxuXG5hc3luYyBmdW5jdGlvbiBvblVwZGF0ZShldmVudDogQVdTTGFtYmRhLkNsb3VkRm9ybWF0aW9uQ3VzdG9tUmVzb3VyY2VFdmVudCkge1xuICBjb25zdCB1cGRhdGVFdmVudCA9IGV2ZW50IGFzIEFXU0xhbWJkYS5DbG91ZEZvcm1hdGlvbkN1c3RvbVJlc291cmNlVXBkYXRlRXZlbnQ7XG4gIGNvbnN0IG9sZFJlcG9zaXRvcnlOYW1lID0gdXBkYXRlRXZlbnQuT2xkUmVzb3VyY2VQcm9wZXJ0aWVzPy5SZXBvc2l0b3J5TmFtZTtcbiAgY29uc3QgbmV3UmVwb3NpdG9yeU5hbWUgPSB1cGRhdGVFdmVudC5SZXNvdXJjZVByb3BlcnRpZXM/LlJlcG9zaXRvcnlOYW1lO1xuICBjb25zdCByZXBvc2l0b3J5TmFtZUhhc0NoYW5nZWQgPSBuZXdSZXBvc2l0b3J5TmFtZSAhPSBudWxsICYmIG9sZFJlcG9zaXRvcnlOYW1lICE9IG51bGwgJiYgbmV3UmVwb3NpdG9yeU5hbWUgIT09IG9sZFJlcG9zaXRvcnlOYW1lO1xuXG4gIC8qIElmIHRoZSBuYW1lIG9mIHRoZSByZXBvc2l0b3J5IGhhcyBjaGFuZ2VkLCBDbG91ZEZvcm1hdGlvbiB3aWxsIHRyeSB0byBkZWxldGUgdGhlIHJlcG9zaXRvcnlcbiAgICAgYW5kIGNyZWF0ZSBhIG5ldyBvbmUgd2l0aCB0aGUgbmV3IG5hbWUuIFNvIHdlIGhhdmUgdG8gZGVsZXRlIHRoZSBpbWFnZXMgaW4gdGhlXG4gICAgIHJlcG9zaXRvcnkgc28gdGhhdCB0aGlzIG9wZXJhdGlvbiBkb2VzIG5vdCBmYWlsLiAqL1xuICBpZiAocmVwb3NpdG9yeU5hbWVIYXNDaGFuZ2VkKSB7XG4gICAgcmV0dXJuIG9uRGVsZXRlKG9sZFJlcG9zaXRvcnlOYW1lKTtcbiAgfVxufVxuXG4vKipcbiAqIFJlY3Vyc2l2ZWx5IGRlbGV0ZSBhbGwgaW1hZ2VzIGluIHRoZSByZXBvc2l0b3J5XG4gKlxuICogQHBhcmFtIEVDUi5MaXN0SW1hZ2VzUmVxdWVzdCB0aGUgcmVwb3NpdG9yeU5hbWUgJiBuZXh0VG9rZW4gaWYgcHJlc2VudGVkXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGVtcHR5UmVwb3NpdG9yeShwYXJhbXM6IEVDUi5MaXN0SW1hZ2VzUmVxdWVzdCkge1xuICBjb25zdCBsaXN0ZWRJbWFnZXMgPSBhd2FpdCBlY3IubGlzdEltYWdlcyhwYXJhbXMpLnByb21pc2UoKTtcblxuICBjb25zdCBpbWFnZUlkcyA9IGxpc3RlZEltYWdlcz8uaW1hZ2VJZHMgPz8gW107XG4gIGNvbnN0IG5leHRUb2tlbiA9IGxpc3RlZEltYWdlcy5uZXh0VG9rZW4gPz8gbnVsbDtcbiAgaWYgKGltYWdlSWRzLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybjtcbiAgfVxuXG4gIGF3YWl0IGVjci5iYXRjaERlbGV0ZUltYWdlKHtcbiAgICByZXBvc2l0b3J5TmFtZTogcGFyYW1zLnJlcG9zaXRvcnlOYW1lLFxuICAgIGltYWdlSWRzLFxuICB9KS5wcm9taXNlKCk7XG5cbiAgaWYgKG5leHRUb2tlbikge1xuICAgIGF3YWl0IGVtcHR5UmVwb3NpdG9yeSh7XG4gICAgICAuLi5wYXJhbXMsXG4gICAgICBuZXh0VG9rZW4sXG4gICAgfSk7XG4gIH1cbn1cblxuYXN5bmMgZnVuY3Rpb24gb25EZWxldGUocmVwb3NpdG9yeU5hbWU6IHN0cmluZykge1xuICBpZiAoIXJlcG9zaXRvcnlOYW1lKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdObyBSZXBvc2l0b3J5TmFtZSB3YXMgcHJvdmlkZWQuJyk7XG4gIH1cblxuICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGVjci5kZXNjcmliZVJlcG9zaXRvcmllcyh7IHJlcG9zaXRvcnlOYW1lczogW3JlcG9zaXRvcnlOYW1lXSB9KS5wcm9taXNlKCk7XG4gIGNvbnN0IHJlcG9zaXRvcnkgPSByZXNwb25zZS5yZXBvc2l0b3JpZXM/LmZpbmQocmVwbyA9PiByZXBvLnJlcG9zaXRvcnlOYW1lID09PSByZXBvc2l0b3J5TmFtZSk7XG5cbiAgaWYgKCFhd2FpdCBpc1JlcG9zaXRvcnlUYWdnZWRGb3JEZWxldGlvbihyZXBvc2l0b3J5Py5yZXBvc2l0b3J5QXJuISkpIHtcbiAgICBwcm9jZXNzLnN0ZG91dC53cml0ZShgUmVwb3NpdG9yeSBkb2VzIG5vdCBoYXZlICcke0FVVE9fREVMRVRFX0lNQUdFU19UQUd9JyB0YWcsIHNraXBwaW5nIGNsZWFuaW5nLlxcbmApO1xuICAgIHJldHVybjtcbiAgfVxuICB0cnkge1xuICAgIGF3YWl0IGVtcHR5UmVwb3NpdG9yeSh7IHJlcG9zaXRvcnlOYW1lIH0pO1xuICB9IGNhdGNoIChlKSB7XG4gICAgaWYgKGUubmFtZSAhPT0gJ1JlcG9zaXRvcnlOb3RGb3VuZEV4Y2VwdGlvbicpIHtcbiAgICAgIHRocm93IGU7XG4gICAgfVxuICAgIC8vIFJlcG9zaXRvcnkgZG9lc24ndCBleGlzdC4gSWdub3JpbmdcbiAgfVxufVxuXG4vKipcbiAqIFRoZSByZXBvc2l0b3J5IHdpbGwgb25seSBiZSB0YWdnZWQgZm9yIGRlbGV0aW9uIGlmIGl0J3MgYmVpbmcgZGVsZXRlZCBpbiB0aGUgc2FtZVxuICogZGVwbG95bWVudCBhcyB0aGlzIEN1c3RvbSBSZXNvdXJjZS5cbiAqXG4gKiBJZiB0aGUgQ3VzdG9tIFJlc291cmNlIGlzIGV2ZXIgZGVsZXRlZCBiZWZvcmUgdGhlIHJlcG9zaXRvcnksIGl0IG11c3QgYmUgYmVjYXVzZVxuICogYGF1dG9EZWxldGVJbWFnZXNgIGhhcyBiZWVuIHN3aXRjaGVkIHRvIGZhbHNlLCBpbiB3aGljaCBjYXNlIHRoZSB0YWcgd291bGQgaGF2ZVxuICogYmVlbiByZW1vdmVkIGJlZm9yZSB3ZSBnZXQgdG8gdGhpcyBEZWxldGUgZXZlbnQuXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGlzUmVwb3NpdG9yeVRhZ2dlZEZvckRlbGV0aW9uKHJlcG9zaXRvcnlBcm46IHN0cmluZykge1xuICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGVjci5saXN0VGFnc0ZvclJlc291cmNlKHsgcmVzb3VyY2VBcm46IHJlcG9zaXRvcnlBcm4gfSkucHJvbWlzZSgpO1xuICByZXR1cm4gcmVzcG9uc2UudGFncz8uc29tZSh0YWcgPT4gdGFnLktleSA9PT0gQVVUT19ERUxFVEVfSU1BR0VTX1RBRyAmJiB0YWcuVmFsdWUgPT09ICd0cnVlJyk7XG59Il19 \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.assets.json b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.assets.json index b3044ee6c440e..309ab8a13dc43 100644 --- a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.assets.json +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.assets.json @@ -1,20 +1,20 @@ { "version": "31.0.0", "files": { - "9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e": { + "6150227d515909a73f0bcde4b9e19b4b206cc65634027053380d700f6e53f08e": { "source": { - "path": "asset.9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e", + "path": "asset.6150227d515909a73f0bcde4b9e19b4b206cc65634027053380d700f6e53f08e", "packaging": "zip" }, "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e.zip", + "objectKey": "6150227d515909a73f0bcde4b9e19b4b206cc65634027053380d700f6e53f08e.zip", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } }, - "4b76dc2c8dce081997458543a2578e2be9ad8a97158b98f186b0359daf217360": { + "fae74e473c2d47b28ba8bb9bdcf00530bc1f6b9032ff87dc77d277c8b9c100a5": { "source": { "path": "aws-ecr-integ-stack.template.json", "packaging": "file" @@ -22,7 +22,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "4b76dc2c8dce081997458543a2578e2be9ad8a97158b98f186b0359daf217360.json", + "objectKey": "fae74e473c2d47b28ba8bb9bdcf00530bc1f6b9032ff87dc77d277c8b9c100a5.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.template.json b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.template.json index 3337ffe427e5c..e755da927abf5 100644 --- a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.template.json +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/aws-ecr-integ-stack.template.json @@ -63,20 +63,17 @@ "Effect": "Allow", "Action": [ "ecr:BatchDeleteImage", - "ecr:ListImages" - ], - "Resource": [ - "*" - ] - }, - { - "Effect": "Allow", - "Action": [ "ecr:DescribeRepositories", + "ecr:ListImages", "ecr:ListTagsForResource" ], "Resource": [ - "*" + { + "Fn::GetAtt": [ + "Repo02AC86CF", + "Arn" + ] + } ] } ] @@ -92,7 +89,7 @@ "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, - "S3Key": "9631303d93bca290c0dc23afdd33080f3c5f2fa03ebf3dd73a028853023cf12e.zip" + "S3Key": "6150227d515909a73f0bcde4b9e19b4b206cc65634027053380d700f6e53f08e.zip" }, "Timeout": 900, "MemorySize": 128, diff --git a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/manifest.json b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/manifest.json index c75b473e1126c..849b1be4fed32 100644 --- a/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-ecr/test/integ.repository-auto-delete-images.js.snapshot/manifest.json @@ -17,7 +17,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}/4b76dc2c8dce081997458543a2578e2be9ad8a97158b98f186b0359daf217360.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/fae74e473c2d47b28ba8bb9bdcf00530bc1f6b9032ff87dc77d277c8b9c100a5.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ From 6dfd51424b71d35eff5c725caf789b81ac8d0e35 Mon Sep 17 00:00:00 2001 From: Randy Ridgley Date: Tue, 21 Mar 2023 17:56:06 -0400 Subject: [PATCH 6/6] Update packages/@aws-cdk/aws-ecr/lib/repository.ts Co-authored-by: Mitchell Valine --- packages/@aws-cdk/aws-ecr/lib/repository.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-ecr/lib/repository.ts b/packages/@aws-cdk/aws-ecr/lib/repository.ts index 4ead5901c69b2..7992af2774b30 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository.ts @@ -4,8 +4,17 @@ import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; import { - ArnFormat, IResource, Lazy, RemovalPolicy, Resource, Stack, - Tags, Token, TokenComparison, CustomResource, CustomResourceProvider, + ArnFormat, + IResource, + Lazy, + RemovalPolicy, + Resource, + Stack, + Tags, + Token, + TokenComparison, + CustomResource, + CustomResourceProvider, CustomResourceProviderRuntime, } from '@aws-cdk/core'; import { IConstruct, Construct } from 'constructs';