chore: update workflow to support multi-architecture builds #185
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
name: Build Image | |
on: | |
pull_request: | |
branches: | |
- main | |
paths-ignore: | |
- "**.md" | |
schedule: | |
- cron: "05 10 * * *" # 10:05am UTC everyday | |
merge_group: | |
workflow_dispatch: | |
env: | |
IMAGE_NAME: "rs-main-test" # the name of the image produced by this build, matches repo names | |
IMAGE_DESC: "CentOS Stream-based image for basing off of " | |
IMAGE_REGISTRY: "ghcr.io/${{ github.repository_owner }}" | |
DEFAULT_TAG: "latest" | |
CENTOS_VERSION: "stream10" | |
# PRs should only build for amd64. The rest are pushed, so we should build for all platforms | |
# PLATFORMS: ${{ github.event_name == 'pull_request' && 'amd64' || 'amd64, arm64' }} | |
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: ${{ matrix.platform == 'amd64' && 'ubuntu-24.04' || format('runs-on,runner=1cpu-linux-{0},run-id={1}', matrix.arch, github.run_id) }} | |
needs: generate_matrix | |
container: | |
image: redhat/ubi9:latest | |
options: --privileged | |
strategy: | |
fail-fast: false | |
matrix: ${{fromJson(needs.generate_matrix.outputs.matrix)}} | |
permissions: | |
contents: read | |
packages: write | |
id-token: write | |
steps: | |
- name: Setup Container | |
run: | | |
dnf install -y \ | |
git \ | |
podman \ | |
skopeo | |
# Rechunk requires sudo, so we need to create a dummy sudo | |
echo -e "#!/bin/bash\nexec \"\$@\"" > /usr/bin/sudo | |
chmod +x /usr/bin/sudo | |
# Create a directory Rechunk expects | |
mkdir -p /home/runner/work/main/main | |
- name: Checkout | |
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 | |
- name: Setup Just | |
uses: extractions/setup-just@dd310ad5a97d8e7b41793f8ef055398d51ad4de6 # v2 | |
- name: Check Just Syntax | |
shell: bash | |
run: just check | |
- name: Build Image | |
id: build-image | |
shell: bash | |
run: | | |
just=$(which just) | |
$just build "${IMAGE_NAME}" "${DEFAULT_TAG}" | |
# Reprocess raw-img using rechunker which will delete it | |
- name: Run Rechunker | |
id: rechunk | |
uses: hhd-dev/rechunk@602e6d62558ab23e15e8764ce06e26c0f328da71 # v1.0.1 | |
with: | |
rechunk: "ghcr.io/hhd-dev/rechunk:v1.0.1" | |
ref: "localhost/${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}" | |
prev-ref: "${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}" | |
skip_compression: true | |
version: ${{ env.CENTOS_VERSION }} | |
- name: Load Image | |
id: load | |
run: | | |
IMAGE=$(podman pull ${{ steps.rechunk.outputs.ref }}) | |
rm -rf ${{ steps.rechunk.outputs.location }} | |
podman image tag $IMAGE ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }} | |
IMAGE=${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }} | |
IMAGE_DIGEST=$(podman image inspect --format '{{.Digest}}' $IMAGE) | |
echo "image=$IMAGE" >> $GITHUB_OUTPUT | |
echo "digest=$IMAGE_DIGEST" >> $GITHUB_OUTPUT | |
- name: Login to GitHub Container Registry | |
# if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) | |
env: | |
registry: ghcr.io | |
username: ${{ github.actor }} | |
password: ${{ secrets.GITHUB_TOKEN }} | |
run: | | |
echo ${{ secrets.GITHUB_TOKEN }} | podman login -u ${{ github.actor }} --password-stdin $registry | |
mkdir -p ~/.docker | |
cat /run/containers/0/auth.json > ~/.docker/config.json | |
# Push the image to GHCR (Image Registry) | |
- name: Push to GHCR | |
# if: github.event_name != 'pull_request' | |
id: push | |
env: | |
IMAGE_REGISTRY: ${{ env.IMAGE_REGISTRY }} | |
IMAGE_NAME: ${{ env.IMAGE_NAME }} | |
IMAGE_DIGEST: ${{ steps.load.outputs.digest }} | |
PLATFORM: ${{ matrix.platform }} | |
run: | | |
podman tag ${{ env.IMAGE_REGISTRY }}/${IMAGE_NAME}:${DEFAULT_TAG} $IMAGE_REGISTRY/$IMAGE_NAME:$DEFAULT_TAG-$PLATFORM | |
for i in {1..3}; do | |
podman push --digestfile=/tmp/digestfile $IMAGE_REGISTRY/$IMAGE_NAME:$DEFAULT_TAG-$PLATFORM && break || sleep $((5 * i)); | |
done | |
REMOTE_IMAGE_DIGEST=$(cat /tmp/digestfile) | |
echo "remote_image_digest=$REMOTE_IMAGE_DIGEST" >> $GITHUB_OUTPUT | |
cat /tmp/digestfile | |
# This section is optional and only needs to be enabled in you plan on distributing | |
# your project to others to consume. You will need to create a public and private key | |
# using Cosign and save the private key as a repository secret in Github for this workflow | |
# to consume. For more details, review the image signing section of the README. | |
- name: Install Cosign | |
uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 | |
#if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) | |
- name: Sign Image | |
#if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) | |
run: | | |
IMAGE_FULL="${{ env.IMAGE_REGISTRY }}/${IMAGE_NAME}" | |
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 | |
permissions: | |
contents: read | |
packages: write | |
id-token: write | |
steps: | |
- name: Get Build Date | |
id: date | |
run: | | |
# Should generate a timestamp like what is defined on the ArtifactHub documentation | |
# E.G: 2022-02-08T15:38:15Z' | |
# https://artifacthub.io/docs/topics/repositories/container-images/ | |
# https://linux.die.net/man/1/date | |
echo "date=$(date -u +%Y\-%m\-%d\T%H\:%M\:%S\Z)" >> $GITHUB_OUTPUT | |
- name: Image Metadata | |
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5 | |
id: metadata | |
with: | |
tags: | | |
type=raw,value=latest | |
type=raw,value=latest.{{date 'YYYYMMDD'}} | |
type=raw,value={{date 'YYYYMMDD'}} | |
type=raw,value=${{ env.CENTOS_VERSION }} | |
type=raw,value=${{ env.CENTOS_VERSION }}.{{date 'YYYYMMDD'}} | |
type=sha,enable=${{ github.event_name == 'pull_request' }} | |
type=ref,event=pr | |
labels: | | |
io.artifacthub.package.readme-url=https://raw.githubusercontent.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/refs/heads/main/README.md | |
org.opencontainers.image.created=${{ steps.date.outputs.date }} | |
org.opencontainers.image.description=${{ env.IMAGE_DESC }} | |
org.opencontainers.image.documentation=https://github.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} | |
org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/blob/main/Containerfile | |
org.opencontainers.image.title=${{ env.IMAGE_NAME }} | |
org.opencontainers.image.url=https://github.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} | |
org.opencontainers.image.vendor=${{ github.repository_owner }} | |
org.opencontainers.image.version=${{ env.CENTOS_VERSION }} | |
io.artifacthub.package.deprecated=false | |
io.artifacthub.package.keywords=bootc,centos,ublue,universal-blue | |
io.artifacthub.package.license=Apache-2.0 | |
io.artifacthub.package.logo-url=https://avatars.githubusercontent.com/u/120078124?s=200&v=4 | |
io.artifacthub.package.maintainers=[{\"name\":\"tulilirockz\",\"email\":\"[email protected]\"},{\"name\":\"castrojo\", \"email\": \"[email protected]\"}] | |
io.artifacthub.package.prerelease=true | |
containers.bootc=1 | |
sep-tags: " " | |
sep-annotations: " " | |
- name: Fetch Build Outputs | |
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 | |
with: | |
pattern: ${{ env.IMAGE_NAME }}-* | |
merge-multiple: true | |
path: /tmp/artifacts | |
- name: Load Outputs | |
id: load-outputs | |
run: | | |
DIGESTS_JSON=$(jq -n '{}') | |
for digest_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 }} | |
LABELS: ${{ steps.metadata.outputs.labels }} | |
run: | | |
DIGESTS=$(echo "$DIGESTS_JSON" | jq -c '.') | |
PLATFORMS=(${{ env.PLATFORMS }}) | |
for platform in ${PLATFORMS[@]}; do | |
digest=$(echo $DIGESTS | jq -r ".$platform") | |
echo "Adding ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}@$digest for $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: Push Manifest | |
env: | |
MANIFEST: ${{ steps.create-manifest.outputs.MANIFEST }} | |
TAGS: ${{ steps.metadata.outputs.tags }} | |
IMAGE_REGISTRY: ${{ env.IMAGE_REGISTRY }} | |
IMAGE_NAME: ${{ env.IMAGE_NAME }} | |
run: | | |
for tag in $(echo $TAGS | tr ' ' '\n'); do | |
podman manifest push --all=false $MANIFEST $IMAGE_REGISTRY/$IMAGE_NAME:$tag | |
done |