Delete ECR images using CloudFormation Custom Resources

Delete ECR images using CloudFormation custom resources

Delete ECR images using CloudFormation Custom Resources

If you use CloudFormation to create an ECR and push an image to it, you may encounter an error during the CloudFormation stack.
This is due to an attempt to delete the ECR with the image still in place.

With the image remaining in ECR, the stack deletion fails.

In this case, we will use CloudFormation custom resources to automatically remove the images from the ECR repository when deleting the CloudFormation stack.
By deleting the image in advance, we aim to ensure that the stack is deleted without error.

Environment

Diagram of delete ECR images using CloudFormation custom resources

Create a CloudFormation stack and define two resources inside it.

The first is the ECR.
Push images to this repository.

The second is a Lambda function.
This function is set up as a custom resource.
It should be configured to automatically remove images from the ECR repository when the stack is deleting.
The runtime for the function is Python 3.8.

CloudFormation template files

Build the above configuration with CloudFormation.
The CloudFormation templates are located at the following URL

https://github.com/awstut-an-r/awstut-fa/tree/main/078

Explanation of key points of the template files

This page focuses on how to remove an ECR image using a custom resource.

For more information on CloudFormation custom resources, please refer to the following page

あわせて読みたい
Introduction to CloudFormation Custom Resources 【Configuration to check behavior of CloudFormation Custom resources】 One of the features of CloudFormation is custom resources. Custom resources enable you...

Lambda functions to invoke with custom resources

Resources:
  Function:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import os

          account_id = os.environ['ACCOUNT_ID']
          ecr_repository_name = os.environ['ECR_REPOSITORY_NAME']

          ecr_client = boto3.client('ecr')

          DELETE = 'Delete'
          response_data = {}

          def lambda_handler(event, context):
            try:
              if event['RequestType'] == DELETE:
                list_images_response = ecr_client.list_images(
                  registryId=account_id,
                  repositoryName=ecr_repository_name
                  )

                image_ids = list_images_response['imageIds']

                if len(image_ids) == 0:
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
                  return

                batch_delete_image_response = ecr_client.batch_delete_image(
                  registryId=account_id,
                  repositoryName=ecr_repository_name,
                  imageIds=image_ids
                  )
                print(batch_delete_image_response)

              cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)

            except Exception as e:
              print(e)
              cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
      Environment:
        Variables:
          ACCOUNT_ID: !Ref AWS::AccountId
          ECR_REPOSITORY_NAME: !Ref ECRRepositoryName
      FunctionName: !Sub "${Prefix}-function"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole.Arn
Code language: YAML (yaml)

There are no special settings for the function itself.
The environment variable is the key point.
The parameters for deleting an image (repository name or account ID) are passed to the function as environment variables.

Refer to the value of event[‘RequestType’] to implement the process according to the stack operation.
“Delete” is used when deleting the stack.
So use an if statement to ensure that the following processing is performed when the stack is deleting.

  • Obtain a list of images stored in the repository using the list_images method.
  • If the number of images is 0, the process is aborted.
  • In the batch_delete_image method, delete the images.

A function invocation completion message must be returned to the CloudFormation stack.
In this case, we use cfnresponse.send to implement this.

There is one issue with the object deletion we have implemented this time.
It is not intended to delete more than 1000 images.
When boto3’s list_images tries to retrieve more than 1000 images, it returns a token to retrieve the remaining data.
In this code, that part of the process is not yet implemented.

The following is the IAM role for the above function.

Resources:
  FunctionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: !Sub "${Prefix}-ECRDeleteImagesPolicy"
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - ecr:BatchDeleteImage
                  - ecr:ListImages
                Resource:
                  - !Ref ECRRepositoryArn
Code language: YAML (yaml)

As we saw earlier, the function will list and delete images stored in the ECR.
Therefore, we grant permission to allow these.

Custom Resources

Resources:
  CustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !Ref FunctionArn
Code language: YAML (yaml)

Specify the ARN of the aforementioned Lambda function in the ServiceToken property.
This setting will cause the function to be invoked each time the CloudFormation stack is operated.

ECR

Resources:
  ECRRepository:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: !Ref Prefix
Code language: YAML (yaml)

ECR Repository.
No special configuration is required.

Architecting

Use CloudFormation to build this environment and check the actual behavior.

Create CloudFormation stacks and check resources in stacks

Create a CloudFormation stacks.
For information on how to create stacks and check each stack, please refer to the following page

あわせて読みたい
CloudFormation’s nested stack 【How to build an environment with a nested CloudFormation stack】 Examine nested stacks in CloudFormation. CloudFormation allows you to nest stacks. Nested ...

After checking the resources in each stack, information on the main resources created this time is as follows

  • ECR: fa-078

Check Action

Image push

Now that everything is ready, push the image to ECR.

$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin [account-id].dkr.ecr.ap-northeast-1.amazonaws.com
...
Login Succeeded


$ docker build -t fa-078 .
...
Successfully tagged fa-078:latest


$ docker tag fa-078:latest [account-id].dkr.ecr.ap-northeast-1.amazonaws.com/fa-078:latest


$ docker push [account-id].dkr.ecr.ap-northeast-1.amazonaws.com/fa-078:latest
The push refers to repository [[account-id].dkr.ecr.ap-northeast-1.amazonaws.com/fa-078]
209cb42bdfb7: Pushed
latest: digest: sha256:4adf0089f316607778fd6a5e073205b767bd849ac8a2234921fddc4351139b96 size: 529Code language: JavaScript (javascript)

Image build/push successfully executed.

Check the ECR.

Detail of ECR 1.

The image is indeed stored.

Delete CloudFormation stack

Attempt to delete the stack with the image still in the ECR.

Detail of ECR 2.

Wait until the deletion process is complete.

Detail of ECR 3.

Stack deletion is completed.
This means that when the stack is deleted, the CloudFormation custom resource has preemptively deleted the remaining images in the ECR repository.

Checking the Operation of the Lambda function associated with the custom resource.

Detail of CloudFormation Custom Resources Action.

The logs delivered to CloudWatch Logs show two things.

The first is that we are getting a list of images stored in the ECR.
In this case, we can read that one image is stored.

The second is that this function has acted as a CloudFormation custom resource.
It can be read that a message is sent to the CloudFormation stack using the cfnresponse module.

This means that when the stack is deleted, the image is automatically removed from the ECR repository before the stack is deleted.

Summary

We have seen how to use CloudFormation custom resources to automatically delete images from the ECR repository when deleting the CloudFormation stack.