From 303d8a30d04d3be5590dcc88820e89e4452cccdb Mon Sep 17 00:00:00 2001 From: Robert Sturla Date: Fri, 20 Dec 2024 16:49:10 +0000 Subject: [PATCH] chore: prepare repo for multi-arch builds --- .github/workflows/build.yml | 237 +++++++++++++++++++++++++++--------- 1 file changed, 180 insertions(+), 57 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8d6894d..8e08bb1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,28 +14,51 @@ on: workflow_dispatch: env: - IMAGE_NAME: "main" + IMAGE_NAME: "rs-main-test" IMAGE_DESC: "CentOS Stream-based images" IMAGE_REGISTRY: "ghcr.io/${{ github.repository_owner }}" DEFAULT_TAG: "latest" CENTOS_VERSION: "stream10" LOGO_URL: "https://avatars.githubusercontent.com/u/120078124?s=200&v=4" README_URL: "https://raw.githubusercontent.com/${{ github.repository }}/main/README.md" + PLATFORMS: "amd64,arm64" concurrency: group: ${{ github.workflow }}-${{ github.ref || github.run_id }} cancel-in-progress: true jobs: + generate_matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Set matrix + id: set-matrix + run: | + # turn the comma separated string into a list + platforms=(${{ env.PLATFORMS }}) + + MATRIX="{\"include\":[]}" + for platform in "${platforms[@]}"; do + MATRIX=$(echo $MATRIX | jq ".include += [{\"platform\": \"$platform\"}]") + done + + echo "matrix=$(echo $MATRIX | jq -c '.')" >> $GITHUB_OUTPUT + build_push: name: Build and push image runs-on: ubuntu-24.04 - + needs: generate_matrix + strategy: + fail-fast: false + matrix: ${{fromJson(needs.generate_matrix.outputs.matrix)}} permissions: contents: read packages: write id-token: write - + env: + DEFAULT_TAG: local steps: # Checkout push-to-registry action GitHub repository - name: Checkout Push to Registry action @@ -54,48 +77,6 @@ jobs: with: remove-codeql: true - - name: Generate tags - id: generate-tags - shell: bash - run: | - # Generate a timestamp for creating an image version history - TIMESTAMP="$(date +%Y%m%d)" - COMMIT_TAGS=() - BUILD_TAGS=() - - # Have tags for tracking builds during pull request - SHA_SHORT="${GITHUB_SHA::7}" - COMMIT_TAGS+=("pr-${{ github.event.number }}") - COMMIT_TAGS+=("${SHA_SHORT}") - - # Append matching timestamp tags to keep a version history - for TAG in "${BUILD_TAGS[@]}"; do - BUILD_TAGS+=("${TAG}-${TIMESTAMP}") - done - - BUILD_TAGS+=("${TIMESTAMP}") - BUILD_TAGS+=("${DEFAULT_TAG}") - BUILD_TAGS+=("${CENTOS_VERSION}") - BUILD_TAGS+=("${CENTOS_VERSION}.${TIMESTAMP}") - - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - echo "Generated the following commit tags: " - for TAG in "${COMMIT_TAGS[@]}"; do - echo "${TAG}" - done - - alias_tags=("${COMMIT_TAGS[@]}") - else - alias_tags=("${BUILD_TAGS[@]}") - fi - - echo "Generated the following build tags: " - for TAG in "${BUILD_TAGS[@]}"; do - echo "${TAG}" - done - - echo "alias_tags=${alias_tags[*]}" >> $GITHUB_OUTPUT - - name: Build Image id: build-image shell: bash @@ -120,12 +101,16 @@ jobs: io.artifacthub.package.logo-url=${{ env.LOGO_URL }} - name: Load in podman and tag + id: load run: | IMAGE=$(podman pull ${{ steps.rechunk.outputs.ref }}) sudo rm -rf ${{ steps.rechunk.outputs.output }} - for tag in ${{ steps.generate-tags.outputs.alias_tags }}; do - podman tag $IMAGE ${{ env.IMAGE_NAME }}:$tag - done + + podman image tag $IMAGE ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }} + + IMAGE_DIGEST=$(podman inspect --format '{{.Digest}}' $IMAGE) + echo "image=$IMAGE" >> $GITHUB_OUTPUT + echo "digest=$IMAGE_DIGEST" >> $GITHUB_OUTPUT # Workaround bug where capital letters in your GitHub username make it impossible to push to GHCR. # https://github.com/macbre/push-to-ghcr/issues/12 @@ -144,15 +129,18 @@ jobs: # Push the image to GHCR (Image Registry) - name: Push To GHCR - uses: redhat-actions/push-to-registry@v2 - if: github.event_name != 'pull_request' + # if: github.event_name != 'pull_request' id: push - with: - registry: ${{ steps.registry_case.outputs.lowercase }} - image: ${{ env.IMAGE_NAME }} - tags: ${{ steps.generate-tags.outputs.alias_tags }} - extra-args: | - --disable-content-trust + env: + IMAGE_REGISTRY: ${{ steps.registry_case.outputs.lowercase }} + IMAGE_NAME: ${{ env.IMAGE_NAME }} + IMAGE_DIGEST: ${{ steps.load.outputs.digest }} + run: | + for i in {1..3}; do + podman push --digestfile=/tmp/digestfile $IMAGE_REGISTRY/$IMAGE_NAME@${IMAGE_DIGEST} && break || sleep $((5 * i)); + done + REMOTE_IMAGE_DIGEST=$(cat /tmp/digestfile) + echo "remote_image_digest=$REMOTE_IMAGE_DIGEST" >> $GITHUB_OUTPUT - name: Install Cosign uses: sigstore/cosign-installer@v3.7.0 @@ -162,8 +150,143 @@ jobs: if: github.event_name != 'pull_request' run: | IMAGE_FULL="${{ steps.registry_case.outputs.lowercase }}/${IMAGE_NAME}" - cosign sign -y --key env://COSIGN_PRIVATE_KEY ${IMAGE_FULL}@${TAGS} + cosign sign -y --key env://COSIGN_PRIVATE_KEY ${IMAGE_FULL}@${{ steps.push.outputs.remote_image_digest }} env: TAGS: ${{ steps.push.outputs.digest }} COSIGN_EXPERIMENTAL: false COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }} + + - name: Create job outputs + env: + IMAGE_NAME: ${{ env.IMAGE_NAME }} + PLATFORM: ${{ matrix.platform }} + DIGEST: ${{ steps.push.outputs.remote_image_digest }} + run: | + mkdir -p /tmp/outputs/digests + echo "${DIGEST}" > /tmp/outputs/digests/${IMAGE_NAME}-${PLATFORM}.txt + + - name: Upload Output Artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ env.IMAGE_NAME }}-${{ matrix.platform }} + retention-days: 1 + if-no-files-found: error + path: | + /tmp/outputs/digests/*.txt + + + manifest: + runs-on: ubuntu-latest + needs: + - build_push + steps: + - name: Generate tags + id: generate-tags + shell: bash + run: | + # Generate a timestamp for creating an image version history + TIMESTAMP="$(date +%Y%m%d)" + COMMIT_TAGS=() + BUILD_TAGS=() + + # Have tags for tracking builds during pull request + SHA_SHORT="${GITHUB_SHA::7}" + COMMIT_TAGS+=("pr-${{ github.event.number }}") + COMMIT_TAGS+=("${SHA_SHORT}") + + # Append matching timestamp tags to keep a version history + for TAG in "${BUILD_TAGS[@]}"; do + BUILD_TAGS+=("${TAG}-${TIMESTAMP}") + done + + BUILD_TAGS+=("${TIMESTAMP}") + BUILD_TAGS+=("${DEFAULT_TAG}") + BUILD_TAGS+=("${CENTOS_VERSION}") + BUILD_TAGS+=("${CENTOS_VERSION}.${TIMESTAMP}") + + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + echo "Generated the following commit tags: " + for TAG in "${COMMIT_TAGS[@]}"; do + echo "${TAG}" + done + + alias_tags=("${COMMIT_TAGS[@]}") + else + alias_tags=("${BUILD_TAGS[@]}") + fi + + echo "Generated the following build tags: " + for TAG in "${BUILD_TAGS[@]}"; do + echo "${TAG}" + done + + echo "alias_tags=${alias_tags[*]}" >> $GITHUB_OUTPUT + + - name: Fetch Build Outputs + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 + with: + pattern: ${{ env.IMAGE_NAME }}-*.txt + merge-multiple: true + path: /tmp/artifacts + + - name: Load Outputs + id: load-outputs + run: | + DIGESTS_JSON=$(jq -n '{}') + + for file in /tmp/artifacts/*.txt; do + # Extract the platform from the file name + PLATFORM=$(basename $digest_file | rev | cut -d'-' -f1 | rev | cut -d'.' -f1) + DIGEST=$(cat $digest_file) + + # Add the platform and digest to the JSON object + DIGESTS_JSON=$(echo "$DIGESTS_JSON" | jq --arg key "$PLATFORM" --arg value "$DIGEST" '. + {($key): $value}') + done + + echo "DIGESTS_JSON=$(echo $DIGESTS_JSON | jq -c '.')" >> $GITHUB_OUTPUT + + - name: Create Manifest + id: create-manifest + run: | + podman manifest create ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }} + echo "MANIFEST=${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}" >> $GITHUB_OUTPUT + + - name: Populate Manifest + env: + MANIFEST: ${{ steps.create-manifest.outputs.MANIFEST }} + DIGESTS_JSON: ${{ steps.load-outputs.outputs.DIGESTS_JSON }} + run: | + DIGESTS=$(echo "$DIGESTS_JSON" | jq -c '.') + PLATFORMS=(${{ env.PLATFORMS }}) + + for platform in ${PLATFORMS[@]}; do + digest=$(echo $DIGESTS | jq -r ".$platform") + podman manifest add $MANIFEST ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:$digest --arch $platform + done + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Tag Manifest + id: tag-manifest + run: | + MANIFEST_TAGS=() + for tag in ${{ steps.generate-tags.outputs.alias_tags }}; do + podman manifest tag ${{ steps.create-manifest.outputs.MANIFEST }} ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:$tag + MANIFEST_TAGS+=(${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:$tag) + done + MANIFEST_TAGS_JSON=$(printf '%s\n' "${MANIFEST_TAGS[@]}" | jq -R . | jq -cs .) + echo "MANIFEST_TAGS_JSON=${MANIFEST_TAGS_JSON}" >> $GITHUB_OUTPUT + + - name: Push Manifest + env: + MANIFEST: ${{ steps.create-manifest.outputs.MANIFEST }} + MANIFEST_TAGS_JSON: ${{ steps.tag-manifest.outputs.MANIFEST_TAGS_JSON }} + run: | + for tag in $(echo $MANIFEST_TAGS_JSON | jq -r '.[]'); do + podman manifest push --all=false $MANIFEST $tag + done