DEV Community

Steven Smiley
Steven Smiley

Posted on

Deploying and Tagging ECR Container Images in AWS CodePipeline

AWS CodePipeline is not the most popular or robust pipeline tool out there, but it's conveniently available in AWS and works decently well for most scenarios. I have, however, often had to create custom steps where I would have expected there to be pre-built integrations. Fortunately, CodePipeline is flexible enough to support building your own steps through both AWS CodeBuild and AWS Lambda, and I have two examples that I expect would be useful to many others I'd like to share.

Pipeline overview

For this scenario, let's imagine we have container images that are already built and pushed to Amazon ECR. We want to (1) deploy them to Amazon ECS and then (2) tag the image to denote the environment the image is running in.

Image description

Deploying from ECR to ECS

The CodePipeline action to deploy a container to ECS requires an input FileName of a JSON file with the target service's container name and the image to be deployed. Unfortunately, the ECR source action does not output this file in the correct format.

In this case, we need to add a CodeBuild step between the Source action and the Deploy action to output the required file in the correct format. The BuildSpec for the CodeBuild project includes a single command:

version: 0.2
phases:
  build:
    commands:
      - printf '[{"name":"%s","imageUri":"%s:latest"}]' "$TASK_FAMILY" "$REPOSITORY_URI" > imagedefinitions.json
artifacts:
  files: imagedefinitions.json
Enter fullscreen mode Exit fullscreen mode

This will write the required JSON file and output it as an artifact that can be referenced during the Deploy action.

Tagging ECR Images in CodePipeline

It can be valuable to indicate which images are actually deployed in your environment, particularly which have been promoted to production. This allows vulnerability management tooling and personnel to assess the risk of vulnerabilities identified in an image -- if the image is deployed to production that's a real risk compared to an old image that just stale in ECR.

We can build an AWS Lambda function to tag images when triggered by CodePipeline. First, the CodePipeline action configuration needs to pass the function the required parameters in the UserParameters field.

- Name: TagImageWithProd
              RunOrder: 3
              ActionTypeId:
                Category: Invoke
                Owner: AWS
                Provider: Lambda
                Version: "1"
              Configuration:
                FunctionName: 
                  Fn::ImportValue: !Sub ${PipelineTagStack}-FunctionName
                UserParameters: "{\"RepositoryName\": \"#{ImageVariables.RepositoryName}\", \"ImageTag\": \"#{ImageVariables.ImageTag}\" , \"NewImageTag\": \"prod\", \"ImageDigest\": \"#{ImageVariables.ImageDigest}\", \"ImageURI\": \"#{ImageVariables.ImageURI}\"}"
Enter fullscreen mode Exit fullscreen mode

In the Lambda function, we can parse the required parameters from the CodePipeline event.

codepipeline_job_id = event["CodePipeline.job"]["id"]
    user_parameters = json.loads(
        event["CodePipeline.job"]["data"]["actionConfiguration"]["configuration"][
            "UserParameters"
        ]
    )

image_uri = user_parameters["ImageURI"]
repository_name = user_parameters["RepositoryName"]
image_tag = user_parameters["ImageTag"]
new_image_tag = user_parameters["NewImageTag"]
Enter fullscreen mode Exit fullscreen mode

To tag the image using the ECR PutImage API, however, we will also need the image's manifest. So first we retrieve that using ECR Batch Get Image.

image_manifest = ecr_client.batch_get_image(
            repositoryName=repository_name,
            imageIds=[
                {
                    "imageTag": image_tag,
                }
            ],
        )["images"][0]["imageManifest"]
Enter fullscreen mode Exit fullscreen mode

Now we have everything we need, and can tag the image in ECR.

ecr_client.put_image(
            repositoryName=repository_name,
            imageTag=new_image_tag,
            imageManifest=image_manifest,
        )
Enter fullscreen mode Exit fullscreen mode

And finally, don't forget to tell CodePipeline that your action has succeeded (or failed), or the pipeline will wait around for about 15 minutes.

codepipeline_client.put_job_success_result(jobId=codepipeline_job_id)
codepipeline_client.put_job_failure_result(
            jobId=codepipeline_job_id,
            failureDetails={
                "type": "JobFailed",
                "message": "Error tagging image",
            },
        )
Enter fullscreen mode Exit fullscreen mode

Although I would have preferred these actions to be available without extra work, the AWS's flexibility again makes it work without too much trouble.

Top comments (0)