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.
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
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}\"}"
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"]
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"]
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,
)
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",
},
)
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)