Promote RC Images to Release in ECR with GitHub Actions

Goal

Automatically promote the latest release candidate image (x.y.z-rc) in AWS ECR to production (x.y.z) using a GitHub Actions workflow.

Workflow

name: Promote Latest RC Images to Release. Deploy to prod.

on:
  workflow_dispatch:

permissions:
  contents: read
  id-token: write

jobs:
  promote-images:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        repository: 
          - atoss-identity-secrets-setup
          - atoss-identity-svc

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::XXXXXXXXXX:role/github/atoss-identity-deploy-role
          aws-region: eu-central-1

      - name: Promote rc image to release
        uses: ./.github/workflows/actions/promote-latest-rc-img-to-release
        with:
          repository: ${{ matrix.repository }}
          region: eu-central-1

      - name: Output the promoted tags
        run: |
          echo "Promoted rc tag: ${{ steps.promote-image.outputs.rc_tag }}"
          echo "Release tag: ${{ steps.promote-image.outputs.release_tag }}"          

Reusable Action

Create the following files in /.github/workflows/actions/promote-latest-rc-img-to-release:

Dockerfile

FROM amazonlinux:2
RUN yum install -y jq aws-cli docker
COPY action.sh /action.sh
RUN chmod +x /action.sh

ENTRYPOINT ["/action.sh"]

action.yaml

name: Promote ECR RC Image to Release
description: Adds x.y.z tag to the latest x.y.z-rc image in an AWS ECR repository.
inputs:
  repository:
    description: 'ECR repository name'
    required: true
  region:
    description: 'AWS region'
    required: true
outputs:
  rc_tag:
    description: 'The RC tag that was promoted'
  release_tag:
    description: 'The release tag'
runs:
  using: 'docker'
  image: 'Dockerfile'
  args:
    - ${{ inputs.repository }}
    - ${{ inputs.region }}

action.sh

The script finds the latest -rc tag, checks if the release version already exists, and promotes it by pulling, retagging, and pushing the image:

##!/bin/bash

set -e

ECR_REPOSITORY=$1
REGION=$2

aws ecr get-login-password --region "$REGION" | docker login --username AWS --password-stdin "$AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com"

## List all images in the repository into images.json file
aws ecr describe-images --repository-name "$ECR_REPOSITORY" --region "$REGION" --query 'imageDetails[*].imageTags' --output json > images.json

LATEST_RC_TAG=$(jq -r '.[] | select(.[0] | test("^[0-9]+\\.[0-9]+\\.[0-9]+-rc$")) | .[0]' images.json | sort -Vr | head -n 1)

echo "Latest RC image found "$LATEST_RC_TAG

## Remove `-rc` to get release version
RELEASE_VERSION=$(echo "$LATEST_RC_TAG" | sed 's/-rc//')

RELEASE_VERSION_EXISTS=$(jq -e '.[] | any(.[] == "'"$RELEASE_VERSION"'")' images.json > /dev/null && echo "true" || echo "false")

if [ "$RELEASE_VERSION_EXISTS" = "true" ]; then
  echo "Release tag $RELEASE_VERSION already exists. Skipping retagging."
  echo "::set-output name=rc_tag::$LATEST_RC_TAG"
  echo "::set-output name=release_tag::$RELEASE_VERSION"
  exit 0
fi

## Pull the latest rc image
docker pull "$AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$ECR_REPOSITORY:$LATEST_RC_TAG"

## Tag the rc image with the release version
docker tag "$AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$ECR_REPOSITORY:$LATEST_RC_TAG" \
  "$AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$ECR_REPOSITORY:$RELEASE_VERSION"

## Push the new release image
docker push "$AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$ECR_REPOSITORY:$RELEASE_VERSION"

## Set outputs
echo "::set-output name=rc_tag::$LATEST_RC_TAG"
echo "::set-output name=release_tag::$RELEASE_VERSION"