chore: update workflow to support multi-architecture builds #170
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 | |
schedule: | |
- cron: "05 10 * * *" # 10:05am UTC everyday | |
merge_group: | |
push: | |
branches: | |
- main | |
paths-ignore: | |
- "**/README.md" | |
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" | |
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 | |
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 | |
# Alias sudo to no-op | |
alias sudo=: | |
- 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: Maximize build space | |
# uses: ublue-os/remove-unwanted-software@517622d6452028f266b7ba4cc9a123b5f58a6b53 # v7 | |
# with: | |
# remove-codeql: true | |
- 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 in podman | |
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 | |
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3 | |
# if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) | |
with: | |
registry: ghcr.io | |
username: ${{ github.actor }} | |
password: ${{ secrets.GITHUB_TOKEN }} | |
# 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 container 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 current 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 | |
# Apply labels to the manifest | |
for label in $(echo $LABELS | tr ' ' '\n'); do | |
podman manifest annotate $MANIFEST --annotation $label | |
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 |