-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ecr): add option to auto delete images upon ECR repository remov…
…al (#24572) This request fixes the ECR Repository resource to allow setting a flag on the resource to auto delete the images in the repository. This is similar to the way S3 handles the autoDeleteObjects attribute. This code base starts from a stalled PR [#15932](#15932). This also takes into account the functionality added into S3 to create tag to not delete images if the flag is flipped from true to false. Closes [#12618](#12618) References closed and not merged PR [#15932](#15932) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
- Loading branch information
1 parent
d4717cf
commit 7de5b00
Showing
15 changed files
with
1,461 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
94 changes: 94 additions & 0 deletions
94
packages/@aws-cdk/aws-ecr/lib/auto-delete-images-handler/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// 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': | ||
break; | ||
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 && 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: 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'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.