From a9f0b8ad103feea1ff2e3c20c8d145be209e672d Mon Sep 17 00:00:00 2001 From: Drew Minnear Date: Wed, 3 Sep 2025 13:46:10 -0400 Subject: [PATCH] build arm64 and amd64 on separate runners --- .github/workflows/container-test.yml | 3 +- .github/workflows/docker-publish.yml | 212 +++++++++++++++++++++------ 2 files changed, 166 insertions(+), 49 deletions(-) diff --git a/.github/workflows/container-test.yml b/.github/workflows/container-test.yml index aaf17c5..919b60d 100644 --- a/.github/workflows/container-test.yml +++ b/.github/workflows/container-test.yml @@ -2,8 +2,7 @@ name: "Container build and test" on: - pull_request: - branches: ["main"] + workflow_call: permissions: read-all diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 8ae8ace..ffadb57 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -1,87 +1,190 @@ --- name: Docker build and push to quay -permissions: read-all on: push: - branches: ['main'] - tags: ['v*.*.*'] + branches: ["main"] + # Publish semver tags as releases. + tags: ["v*.*.*"] + pull_request: + branches: ["main"] + + workflow_dispatch: + + repository_dispatch: + types: [dependency-updated] env: + # Use docker.io for Docker Hub if empty REGISTRY: quay.io + # Generic image name for build artifacts, will be retagged for specific repos + QUAY_IMAGE_NAME: localhost/utility-container jobs: - build-container-and-push: - runs-on: ubuntu-latest + test: + uses: ./.github/workflows/container-test.yml + + build-container: + needs: [test] strategy: matrix: - image: - - name: hybridcloudpatterns/utility-container - username_secret: LEGACY_QUAY_USERNAME - password_secret: LEGACY_QUAY_PASSWORD - - name: validatedpatterns/utility-container + include: + - targetarch: amd64 + runner: ubuntu-latest + platform: linux/amd64 + alttargetarch: x86_64 + opttargetarch: "" + extrarpms: "" + - targetarch: arm64 + runner: ubuntu-24.04-arm + platform: linux/arm64 + alttargetarch: aarch64 + opttargetarch: "arm64-" + extrarpms: "gcc python3-devel glibc-devel libxcrypt-devel" + + runs-on: ${{ matrix.runner }} + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + # Set up BuildKit Docker container builder for docker save functionality + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + + # https://github.com/docker/build-push-action + # Build Docker image with Buildx. We always disable the cache as + # it sometimes would not pull the new git changes for the cluster_utils,v1 + - name: Build Docker image + id: build-and-push + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 + with: + context: . + file: Containerfile + platforms: ${{ matrix.platform }} + push: false + tags: ${{ env.REGISTRY }}/${{ env.QUAY_IMAGE_NAME }}:build-${{ github.run_id }}-${{ matrix.targetarch }} + no-cache: true + outputs: type=docker,dest=/tmp/image-${{ matrix.targetarch }}.tar + build-args: | + TARGETARCH=${{ matrix.targetarch }} + ALTTARGETARCH=${{ matrix.alttargetarch }} + OPTTARGETARCH=${{ matrix.opttargetarch }} + EXTRARPMS=${{ matrix.extrarpms }} + + # Upload image as artifact + - name: Upload image artifact + if: github.event_name != 'pull_request' + uses: actions/upload-artifact@v4 + with: + name: image-${{ matrix.targetarch }}-${{ github.run_id }} + path: /tmp/image-${{ matrix.targetarch }}.tar + retention-days: 1 + + create-multiarch-manifest: + needs: [build-container] + if: github.event_name != 'pull_request' + strategy: + matrix: + include: + - image_name: validatedpatterns/utility-container username_secret: QUAY_USERNAME password_secret: QUAY_PASSWORD + - image_name: hybridcloudpatterns/utility-container + username_secret: LEGACY_QUAY_USERNAME + password_secret: LEGACY_QUAY_PASSWORD + + runs-on: ubuntu-latest permissions: contents: read - packages: write # This is used to complete the identity challenge # with sigstore/fulcio when running outside of PRs. id-token: write steps: - - name: Checkout repository - uses: actions/checkout@v5 + # Download image artifacts + - name: Download AMD64 image + uses: actions/download-artifact@v4 + with: + name: image-amd64-${{ github.run_id }} + path: /tmp/ + + - name: Download ARM64 image + uses: actions/download-artifact@v4 + with: + name: image-arm64-${{ github.run_id }} + path: /tmp/ - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + # Load images into Docker and retag for this repo + - name: Load and retag images + run: | + docker load --input /tmp/image-amd64.tar + docker load --input /tmp/image-arm64.tar + + # Retag loaded images for this specific repo + OLD_IMAGE_BASE="${{ env.REGISTRY }}/${{ env.QUAY_IMAGE_NAME }}" + NEW_IMAGE_BASE="${{ env.REGISTRY }}/${{ matrix.image_name }}" + + docker tag "${OLD_IMAGE_BASE}:build-${{ github.run_id }}-amd64" "${NEW_IMAGE_BASE}:build-${{ github.run_id }}-amd64" + docker tag "${OLD_IMAGE_BASE}:build-${{ github.run_id }}-arm64" "${NEW_IMAGE_BASE}:build-${{ github.run_id }}-arm64" # Install the cosign tool # https://github.com/sigstore/cosign-installer - name: Install cosign uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2 with: - cosign-release: 'v2.2.4' + cosign-release: "v2.2.4" - # Set up BuildKit Docker container builder to be able to build - # multi-platform images and export cache - # https://github.com/docker/setup-buildx-action - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - - # Login against a Docker registry - # https://github.com/docker/login-action - name: Log into registry ${{ env.REGISTRY }} uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 with: registry: ${{ env.REGISTRY }} - username: ${{ secrets[matrix.image.username_secret] }} - password: ${{ secrets[matrix.image.password_secret] }} + username: ${{ secrets[matrix.username_secret] }} + password: ${{ secrets[matrix.password_secret] }} + + # Push individual architecture images to registry + - name: Push architecture-specific images + run: | + IMAGE_BASE="${{ env.REGISTRY }}/${{ matrix.image_name }}" + docker push "${IMAGE_BASE}:build-${{ github.run_id }}-amd64" + docker push "${IMAGE_BASE}:build-${{ github.run_id }}-arm64" - # Extract metadata (tags, labels) for Docker - # https://github.com/docker/metadata-action - - name: Extract Docker metadata + # Extract metadata for multi-arch manifest + - name: Extract Docker metadata for manifest id: meta uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 with: - images: ${{ env.REGISTRY }}/${{ matrix.image.name }} + images: ${{ env.REGISTRY }}/${{ matrix.image_name }} + flavor: | + latest=${{ github.ref == 'refs/heads/main' }} - # Build and push Docker image with Buildx - # https://github.com/docker/build-push-action - - name: Build and push Docker image - id: build-and-push - uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 - with: - context: . - file: Containerfile - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - # Sign the resulting Docker image digest. + # Create and push multi-arch manifest + - name: Create and push multi-arch manifest + id: push-manifest + run: | + IMAGE_BASE="${{ env.REGISTRY }}/${{ matrix.image_name }}" + + # Get the tags from metadata + TAGS="${{ steps.meta.outputs.tags }}" + + for TAG in $TAGS; do + echo "Creating manifest for: $TAG" + + # Use docker buildx imagetools to create multi-arch manifest + docker buildx imagetools create -t "$TAG" \ + "${IMAGE_BASE}:build-${{ github.run_id }}-amd64" \ + "${IMAGE_BASE}:build-${{ github.run_id }}-arm64" + done + + # Get the digest of the first tag for signing + FIRST_TAG=$(echo "$TAGS" | head -n1) + DIGEST=$(docker buildx imagetools inspect "$FIRST_TAG" --format '{{json .}}' | jq -r '.manifest.digest') + echo "digest=$DIGEST" >> "$GITHUB_OUTPUT" + + # Sign the resulting Docker image digest # This will only write to the public Rekor transparency log when the Docker # repository is public to avoid leaking data. If you would like to publish # transparency data even for private images, pass --force to cosign below. @@ -90,7 +193,22 @@ jobs: env: # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable TAGS: ${{ steps.meta.outputs.tags }} - DIGEST: ${{ steps.build-and-push.outputs.digest }} + DIGEST: ${{ steps.push-manifest.outputs.digest }} # This step uses the identity token to provision an ephemeral certificate # against the sigstore community Fulcio instance. run: echo "${TAGS}" | xargs -I "{}" cosign sign --yes "{}@${DIGEST}" + + # Clean up temporary architecture-specific images + - name: Clean up temporary images + if: always() + run: | + IMAGE_BASE="${{ env.REGISTRY }}/${{ matrix.image_name }}" + + # Try to delete temporary images, ignore errors if they don't exist + docker run --rm quay.io/skopeo/stable delete \ + --creds "${{ secrets[matrix.username_secret] }}:${{ secrets[matrix.password_secret] }}" \ + docker://"${IMAGE_BASE}:build-${{ github.run_id }}-amd64" || true + + docker run --rm quay.io/skopeo/stable delete \ + --creds "${{ secrets[matrix.username_secret] }}:${{ secrets[matrix.password_secret] }}" \ + docker://"${IMAGE_BASE}:build-${{ github.run_id }}-arm64" || true