From d158d45d2abc06a3b40465b09f10089be40b43f6 Mon Sep 17 00:00:00 2001 From: "J. Q." <55899496+jawadqur@users.noreply.github.com> Date: Wed, 29 Apr 2026 12:25:03 -0500 Subject: [PATCH] Refactor image build and push workflow Refactor image build and push workflow to improve variable handling and streamline steps. --- .../workflows/image_build_push_native.yaml | 306 ++++++++++++------ 1 file changed, 203 insertions(+), 103 deletions(-) diff --git a/.github/workflows/image_build_push_native.yaml b/.github/workflows/image_build_push_native.yaml index 86c3ca0e..4a3ffc43 100644 --- a/.github/workflows/image_build_push_native.yaml +++ b/.github/workflows/image_build_push_native.yaml @@ -34,7 +34,7 @@ on: BUILD_PLATFORMS: required: false type: string - default: "linux/amd64, linux/arm64" + default: "linux/amd64,linux/arm64" secrets: ECR_AWS_ACCESS_KEY_ID: required: true @@ -56,13 +56,17 @@ jobs: runner: ubuntu-22.04 - platform: linux/arm64 runner: ubuntu-22.04-arm - runs-on: ${{ matrix.runner || 'ubuntu-22.04' }} + + runs-on: ${{ matrix.runner }} + steps: - - name: Prepare + - name: Prepare platform variables + shell: bash run: | - platform=${{ matrix.platform }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + set -euo pipefail + platform="${{ matrix.platform }}" + echo "PLATFORM_PAIR=${platform//\//-}" >> "$GITHUB_ENV" - name: Checkout uses: actions/checkout@v6 @@ -70,41 +74,39 @@ jobs: persist-credentials: false fetch-depth: 0 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Set Variables + - name: Set image variables shell: bash run: | + set -euo pipefail + echo "OVERRIDE_REPO_NAME = ${{ inputs.OVERRIDE_REPO_NAME }}" echo "OVERRIDE_TAG_NAME = ${{ inputs.OVERRIDE_TAG_NAME }}" - if [[ -z "${{ inputs.OVERRIDE_TAG_NAME }}" ]] - then - echo "No OVERRIDE_TAG_NAME input provided, defaulting to current branch/tag name..." - echo "IMAGE_TAG=$(echo ${GITHUB_REF#refs/*/} | tr / _)" - echo "IMAGE_TAG=$(echo ${GITHUB_REF#refs/*/} | tr / _)" >> $GITHUB_ENV + if [[ -z "${{ inputs.OVERRIDE_TAG_NAME }}" ]]; then + image_tag="$(echo "${GITHUB_REF#refs/*/}" | tr / _)" + echo "No OVERRIDE_TAG_NAME input provided, defaulting to current branch/tag name: ${image_tag}" + else + image_tag="${{ inputs.OVERRIDE_TAG_NAME }}" + echo "OVERRIDE_TAG_NAME provided, using IMAGE_TAG: ${image_tag}" + fi + + if [[ -z "${{ inputs.OVERRIDE_REPO_NAME }}" ]]; then + repo_name="$(echo "$GITHUB_REPOSITORY" | awk -F / '{print $2}')" + echo "No OVERRIDE_REPO_NAME input provided, defaulting to repo name: ${repo_name}" else - echo "OVERRIDE_TAG_NAME provided, using it for IMAGE_TAG..." - echo "IMAGE_TAG=${{ inputs.OVERRIDE_TAG_NAME }}" - echo "IMAGE_TAG=${{ inputs.OVERRIDE_TAG_NAME }}" >> $GITHUB_ENV + repo_name="${{ inputs.OVERRIDE_REPO_NAME }}" + echo "OVERRIDE_REPO_NAME provided, using REPO_NAME: ${repo_name}" fi - if [[ -z "${{ inputs.OVERRIDE_REPO_NAME }}" ]] - then - echo "No OVERRIDE_REPO_NAME input provided, defaulting to repo name..." - echo "REPO_NAME=$(echo $GITHUB_REPOSITORY | awk -F / '{print $2}')" - echo "REPO_NAME=$(echo $GITHUB_REPOSITORY | awk -F / '{print $2}')" >> $GITHUB_ENV + echo "IMAGE_TAG=${image_tag}" >> "$GITHUB_ENV" + echo "REPO_NAME=${repo_name}" >> "$GITHUB_ENV" + + if [[ "${{ inputs.USE_QUAY_ONLY }}" == "true" ]]; then + echo "IMAGE_REPOS=quay.io/cdis/${repo_name}" >> "$GITHUB_ENV" else - echo "OVERRIDE_REPO_NAME provided, using it for REPO_NAME..." - echo "REPO_NAME=${{ inputs.OVERRIDE_REPO_NAME }}" - echo "REPO_NAME=${{ inputs.OVERRIDE_REPO_NAME }}" >> $GITHUB_ENV + echo "IMAGE_REPOS=quay.io/cdis/${repo_name},${{ inputs.AWS_ECR_REGISTRY }}/gen3/${repo_name}" >> "$GITHUB_ENV" fi - # https://github.com/docker/login-action#quayio - name: Login to Quay.io uses: docker/login-action@v3 with: @@ -112,7 +114,6 @@ jobs: username: ${{ secrets.QUAY_USERNAME }} password: ${{ secrets.QUAY_ROBOT_TOKEN }} - # https://github.com/docker/login-action#aws-public-elastic-container-registry-ecr - name: Login to ECR if: ${{ !inputs.USE_QUAY_ONLY }} uses: docker/login-action@v3 @@ -123,6 +124,12 @@ jobs: env: AWS_REGION: ${{ inputs.AWS_REGION }} + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Extract metadata id: meta uses: docker/metadata-action@v5 @@ -131,21 +138,9 @@ jobs: quay.io/cdis/${{ env.REPO_NAME }} ${{ !inputs.USE_QUAY_ONLY && format('{0}/gen3/{1}', inputs.AWS_ECR_REGISTRY, env.REPO_NAME) || '' }} - - name: Set image repositories - id: set-repos - run: | - if [ "${{ inputs.USE_QUAY_ONLY }}" = "true" ]; then - echo "repos=quay.io/cdis/${{ env.REPO_NAME }}" >> $GITHUB_ENV - else - echo "repos=quay.io/cdis/${{ env.REPO_NAME }},${{ inputs.AWS_ECR_REGISTRY }}/gen3/${{ env.REPO_NAME }}" >> $GITHUB_ENV - fi - - - name: Build and push by digest + - name: Build and push platform image by digest id: build uses: docker/build-push-action@v6 - # You may get ECR-push errors when first adding the workflow to a github repo. - # If so, run the following in dev/qa to create the ECR repository: - # qaplanetv1@cdistest_dev_admin:~$ aws ecr create-repository --repository-name "gen3/" --image-scanning-configuration scanOnPush=true with: context: ${{ inputs.DOCKERFILE_BUILD_CONTEXT }} file: ${{ inputs.DOCKERFILE_LOCATION }} @@ -153,36 +148,76 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max platforms: ${{ matrix.platform }} - outputs: type=image,"name=${{ env.repos }}",push-by-digest=true,name-canonical=true,push=true + outputs: type=image,"name=${{ env.IMAGE_REPOS }}",push-by-digest=true,name-canonical=true,push=true - - name: Export digest + - name: Export digest artifact + shell: bash run: | - mkdir -p ${{ runner.temp }}/digests + set -euo pipefail + + mkdir -p "${{ runner.temp }}/digests" + digest="${{ steps.build.outputs.digest }}" - touch "${{ runner.temp }}/digests/${digest#sha256:}" + digest="${digest#sha256:}" + + if [[ -z "${digest}" ]]; then + echo "ERROR: build-push-action did not return a digest" + exit 1 + fi + + echo "Exporting digest: sha256:${digest}" + touch "${{ runner.temp }}/digests/${digest}" - - name: Upload digest + - name: Upload digest artifact uses: actions/upload-artifact@v4 with: name: digests-${{ env.PLATFORM_PAIR }} path: ${{ runner.temp }}/digests/* overwrite: true if-no-files-found: error - retention-days: 1 + merge: + name: Merge Multi-Arch Manifests runs-on: ubuntu-latest needs: - build + steps: - - name: Download digests + - name: Download digest artifacts uses: actions/download-artifact@v4 with: path: ${{ runner.temp }}/digests pattern: digests-* merge-multiple: true - # https://github.com/docker/login-action#quayio + - name: Set image variables + shell: bash + run: | + set -euo pipefail + + echo "OVERRIDE_REPO_NAME = ${{ inputs.OVERRIDE_REPO_NAME }}" + echo "OVERRIDE_TAG_NAME = ${{ inputs.OVERRIDE_TAG_NAME }}" + + if [[ -z "${{ inputs.OVERRIDE_TAG_NAME }}" ]]; then + image_tag="$(echo "${GITHUB_REF#refs/*/}" | tr / _)" + echo "No OVERRIDE_TAG_NAME input provided, defaulting to current branch/tag name: ${image_tag}" + else + image_tag="${{ inputs.OVERRIDE_TAG_NAME }}" + echo "OVERRIDE_TAG_NAME provided, using IMAGE_TAG: ${image_tag}" + fi + + if [[ -z "${{ inputs.OVERRIDE_REPO_NAME }}" ]]; then + repo_name="$(echo "$GITHUB_REPOSITORY" | awk -F / '{print $2}')" + echo "No OVERRIDE_REPO_NAME input provided, defaulting to repo name: ${repo_name}" + else + repo_name="${{ inputs.OVERRIDE_REPO_NAME }}" + echo "OVERRIDE_REPO_NAME provided, using REPO_NAME: ${repo_name}" + fi + + echo "IMAGE_TAG=${image_tag}" >> "$GITHUB_ENV" + echo "REPO_NAME=${repo_name}" >> "$GITHUB_ENV" + - name: Login to Quay.io uses: docker/login-action@v3 with: @@ -190,7 +225,6 @@ jobs: username: ${{ secrets.QUAY_USERNAME }} password: ${{ secrets.QUAY_ROBOT_TOKEN }} - # https://github.com/docker/login-action#aws-public-elastic-container-registry-ecr - name: Login to ECR if: ${{ !inputs.USE_QUAY_ONLY }} uses: docker/login-action@v3 @@ -201,72 +235,138 @@ jobs: env: AWS_REGION: ${{ inputs.AWS_REGION }} - - name: Set Variables + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: List downloaded digests + working-directory: ${{ runner.temp }}/digests shell: bash run: | - echo "OVERRIDE_REPO_NAME = ${{ inputs.OVERRIDE_REPO_NAME }}" - echo "OVERRIDE_TAG_NAME = ${{ inputs.OVERRIDE_TAG_NAME }}" + set -euo pipefail - if [[ -z "${{ inputs.OVERRIDE_TAG_NAME }}" ]] - then - echo "No OVERRIDE_TAG_NAME input provided, defaulting to current branch/tag name..." - echo "IMAGE_TAG=$(echo ${GITHUB_REF#refs/*/} | tr / _)" - echo "IMAGE_TAG=$(echo ${GITHUB_REF#refs/*/} | tr / _)" >> $GITHUB_ENV - else - echo "OVERRIDE_TAG_NAME provided, using it for IMAGE_TAG..." - echo "IMAGE_TAG=${{ inputs.OVERRIDE_TAG_NAME }}" - echo "IMAGE_TAG=${{ inputs.OVERRIDE_TAG_NAME }}" >> $GITHUB_ENV - fi + echo "Downloaded digest artifacts:" + ls -la - if [[ -z "${{ inputs.OVERRIDE_REPO_NAME }}" ]] - then - echo "No OVERRIDE_REPO_NAME input provided, defaulting to repo name..." - echo "REPO_NAME=$(echo $GITHUB_REPOSITORY | awk -F / '{print $2}')" - echo "REPO_NAME=$(echo $GITHUB_REPOSITORY | awk -F / '{print $2}')" >> $GITHUB_ENV - else - echo "OVERRIDE_REPO_NAME provided, using it for REPO_NAME..." - echo "REPO_NAME=${{ inputs.OVERRIDE_REPO_NAME }}" - echo "REPO_NAME=${{ inputs.OVERRIDE_REPO_NAME }}" >> $GITHUB_ENV + digest_count="$(find . -maxdepth 1 -type f | wc -l | tr -d ' ')" + + if [[ "${digest_count}" -eq 0 ]]; then + echo "ERROR: no digest artifacts were downloaded" + exit 1 fi - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + echo "Found ${digest_count} digest artifact(s)" - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: | - quay.io/cdis/${{ env.REPO_NAME }} - ${{ !inputs.USE_QUAY_ONLY && format('{0}/gen3/{1}', inputs.AWS_ECR_REGISTRY, env.REPO_NAME) || '' }} - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} + - name: Wait for Quay digests to become available + working-directory: ${{ runner.temp }}/digests + shell: bash + run: | + set -euo pipefail + + for digest_file in *; do + digest="${digest_file#sha256:}" + ref="quay.io/cdis/${{ env.REPO_NAME }}@sha256:${digest}" + + echo "Checking Quay digest: ${ref}" + + for attempt in {1..20}; do + if docker buildx imagetools inspect "${ref}" >/dev/null 2>&1; then + echo "Digest is available in Quay: ${ref}" + break + fi + + if [[ "${attempt}" -eq 20 ]]; then + echo "ERROR: digest was not available in Quay after retries: ${ref}" + exit 1 + fi + + echo "Digest not available yet in Quay. Attempt ${attempt}/20. Retrying..." + sleep 6 + done + done + + - name: Wait for ECR digests to become available + if: ${{ !inputs.USE_QUAY_ONLY }} + working-directory: ${{ runner.temp }}/digests + shell: bash + run: | + set -euo pipefail - - name: Create manifest list and push to registries + for digest_file in *; do + digest="${digest_file#sha256:}" + ref="${{ inputs.AWS_ECR_REGISTRY }}/gen3/${{ env.REPO_NAME }}@sha256:${digest}" + + echo "Checking ECR digest: ${ref}" + + for attempt in {1..20}; do + if docker buildx imagetools inspect "${ref}" >/dev/null 2>&1; then + echo "Digest is available in ECR: ${ref}" + break + fi + + if [[ "${attempt}" -eq 20 ]]; then + echo "ERROR: digest was not available in ECR after retries: ${ref}" + exit 1 + fi + + echo "Digest not available yet in ECR. Attempt ${attempt}/20. Retrying..." + sleep 6 + done + done + + - name: Create manifest list and push to Quay working-directory: ${{ runner.temp }}/digests + shell: bash run: | - # Push to Quay + set -euo pipefail + + quay_refs=() + + for digest_file in *; do + digest="${digest_file#sha256:}" + quay_refs+=("quay.io/cdis/${{ env.REPO_NAME }}@sha256:${digest}") + done + + echo "Creating Quay manifest:" + printf ' %s\n' "${quay_refs[@]}" + docker buildx imagetools create \ - -t quay.io/cdis/${{ env.REPO_NAME }}:${{ env.IMAGE_TAG }} \ - $(printf 'quay.io/cdis/${{ env.REPO_NAME }}@sha256:%s ' *) - - # Conditionally push to ECR - if [ "${{ inputs.USE_QUAY_ONLY }}" != "true" ]; then - docker buildx imagetools create \ - -t ${{ inputs.AWS_ECR_REGISTRY }}/gen3/${{ env.REPO_NAME }}:${{ env.IMAGE_TAG }} \ - $(printf '${{ inputs.AWS_ECR_REGISTRY }}/gen3/${{ env.REPO_NAME }}@sha256:%s ' *) - fi + -t "quay.io/cdis/${{ env.REPO_NAME }}:${{ env.IMAGE_TAG }}" \ + "${quay_refs[@]}" - - name: Inspect image + - name: Create manifest list and push to ECR + if: ${{ !inputs.USE_QUAY_ONLY }} + working-directory: ${{ runner.temp }}/digests + shell: bash + run: | + set -euo pipefail + + ecr_refs=() + + for digest_file in *; do + digest="${digest_file#sha256:}" + ecr_refs+=("${{ inputs.AWS_ECR_REGISTRY }}/gen3/${{ env.REPO_NAME }}@sha256:${digest}") + done + + echo "Creating ECR manifest:" + printf ' %s\n' "${ecr_refs[@]}" + + docker buildx imagetools create \ + -t "${{ inputs.AWS_ECR_REGISTRY }}/gen3/${{ env.REPO_NAME }}:${{ env.IMAGE_TAG }}" \ + "${ecr_refs[@]}" + + - name: Inspect Quay image + shell: bash run: | + set -euo pipefail + docker buildx imagetools inspect \ - quay.io/cdis/${{ env.REPO_NAME }}:${{ env.IMAGE_TAG }} + "quay.io/cdis/${{ env.REPO_NAME }}:${{ env.IMAGE_TAG }}" - - name: Inspect image (ECR) + - name: Inspect ECR image if: ${{ !inputs.USE_QUAY_ONLY }} + shell: bash run: | + set -euo pipefail + docker buildx imagetools inspect \ - ${{ inputs.AWS_ECR_REGISTRY }}/gen3/${{ env.REPO_NAME }}:${{ env.IMAGE_TAG }} + "${{ inputs.AWS_ECR_REGISTRY }}/gen3/${{ env.REPO_NAME }}:${{ env.IMAGE_TAG }}"