Skip to content

Merge pull request #26 from Danathar/renovate-fork-fix #152

Merge pull request #26 from Danathar/renovate-fork-fix

Merge pull request #26 from Danathar/renovate-fork-fix #152

Workflow file for this run

---
name: Build container image
on:
pull_request:
branches:
- main
paths-ignore:
- 'README.md'
- 'docs/**'
schedule:
- cron: '00 05 * * 0' # 5:00am UTC Sunday; about 1:00am Eastern during daylight time
push:
branches:
- main
paths-ignore:
- '**/README.md'
- 'docs/**'
workflow_dispatch:
env:
IMAGE_NAME: "${{ github.event.repository.name }}" # output image name, usually same as repo name
IMAGE_REGISTRY: "ghcr.io/${{ github.repository_owner }}" # do not edit
DEFAULT_TAG: "latest"
concurrency:
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
build_push:
name: Build and push image
runs-on: ubuntu-24.04
timeout-minutes: 90
permissions:
contents: read
packages: write
steps:
- name: Prepare environment
run: |
# Lowercase the image uri
echo "IMAGE_REGISTRY=${IMAGE_REGISTRY,,}" >> ${GITHUB_ENV}
echo "IMAGE_NAME=${IMAGE_NAME,,}" >> ${GITHUB_ENV}
# These stage versions are pinned by https://github.com/renovatebot/renovate
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
# This is optional, but if you see that your builds are way too big for the runners, you can enable this by uncommenting the following lines:
- name: Maximize build space
uses: ublue-os/remove-unwanted-software@695eb75bc387dbcd9685a8e72d23439d8686cba6
# Ubuntu 24.04's Podman does not preserve Chunkah's layer annotations
# when pushing. Use the same newer toolchain as Aurora until the hosted
# runner includes a sufficiently new Podman release. The step skips
# itself once the runner's podman major version is >= 5, so it retires
# automatically when GitHub updates the hosted image.
- name: Update Podman
run: |
set -eux
need_major=5
have_major=$(podman --version | grep -oE '[0-9]+' | head -n 1)
if [ "${have_major}" -ge "${need_major}" ]; then
echo "Runner podman is already >= ${need_major}; skipping cross-release install"
exit 0
fi
echo "deb http://azure.archive.ubuntu.com/ubuntu resolute universe main" | sudo tee /etc/apt/sources.list.d/resolute.list
sudo apt-get update
sudo apt-get install -y --allow-downgrades \
crun/resolute \
buildah/resolute \
podman/resolute \
skopeo/resolute
podman --version
# GitHub-hosted runners keep most of their free space on /mnt (~66G),
# while / has far less headroom. Building the image and then re-layering
# it with Chunkah keeps two copies of the root filesystem in podman's
# storage at once, which can exhaust /. Relocate podman's rootless
# storage onto /mnt so the rechunk has room. The image-copy temp dir is
# deliberately left on / (freed by relocating storage) so the two large
# consumers land on separate disks instead of competing for one.
- name: Move container storage to the large runner disk
run: |
set -eux
sudo mkdir -p /mnt/containers
sudo chown "$(id -u):$(id -g)" /mnt/containers
mkdir -p "${HOME}/.local/share"
rm -rf "${HOME}/.local/share/containers"
ln -s /mnt/containers "${HOME}/.local/share/containers"
df -h / /mnt
- name: Get current date
id: date
run: |
echo "date=$(date -u +%Y\-%m\-%d\T%H\:%M\:%S\Z)" >> $GITHUB_OUTPUT
- name: Image Metadata
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
id: metadata
with:
# This generates all the tags for your image, you can add custom tags here too!
# Default tags are "$DEFAULT_TAG" and "$DEFAULT_TAG.$date".
tags: |
type=raw,value=${{ env.DEFAULT_TAG }}
type=raw,value=${{ env.DEFAULT_TAG }}.{{date 'YYYYMMDD'}}
type=raw,value={{date 'YYYYMMDD'}}
type=sha,enable=${{ github.event_name == 'pull_request' }}
type=ref,event=pr
labels: |
org.opencontainers.image.created=${{ steps.date.outputs.date }}
org.opencontainers.image.description=Aurora DX image with ZFS support
org.opencontainers.image.documentation=https://raw.githubusercontent.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/${{ github.sha }}/README.md
org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/blob/${{ github.sha }}/Containerfile
org.opencontainers.image.title=${{ env.IMAGE_NAME }}
org.opencontainers.image.url=https://github.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/tree/${{ github.sha }}
org.opencontainers.image.vendor=${{ github.repository_owner }}
org.opencontainers.image.version=${{ env.DEFAULT_TAG }}.{{date 'YYYYMMDD'}}
containers.bootc=1
sep-tags: " "
sep-annotations: " "
- name: Build Image
id: build_image
uses: redhat-actions/buildah-build@7a95fa7ee0f02d552a32753e7414641a04307056 # v2
with:
containerfiles: |
./Containerfile
# Postfix image name with -custom to make it a little more descriptive
# Syntax: https://docs.github.com/en/actions/learn-github-actions/expressions#format
image: ${{ env.IMAGE_NAME }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
oci: false
# Post-process the completed image so the ZFS and replacement-kernel
# content is chunked along with the inherited Aurora filesystem.
#
# Chunkah's output used to be piped straight into `podman load`, which
# forced the source image and the re-layered copy to coexist in one
# container storage (~2x the unpacked image) and exhausted the runner
# disk intermittently. Instead, buffer the compressed archive to a file
# on / , delete the source image to free its layers, and only then load
# the archive with its unpack temp dir pointed at /mnt. Neither disk
# ever has to hold two unpacked copies of the image at once.
- name: Rechunk Image with Chunkah
env:
CHUNKAH_IMAGE: quay.io/coreos/chunkah:v0.6.0
run: |
set -euo pipefail
source_image="localhost/${IMAGE_NAME}:${DEFAULT_TAG}"
chunked_image="localhost/${IMAGE_NAME}:chunkah"
archive=/tmp/chunkah-oci.tar
export CHUNKAH_CONFIG_STR
CHUNKAH_CONFIG_STR="$(podman inspect "${source_image}")"
echo "==> Disk usage before rechunk" && df -h / /mnt
podman run --rm \
--mount=type=image,src="${source_image}",target=/chunkah \
-e CHUNKAH_CONFIG_STR \
"${CHUNKAH_IMAGE}" build \
--verbose \
--compressed \
--max-layers 128 \
--prune /sysroot/ \
--label ostree.commit- \
--label ostree.final-diffid- \
--tag "${chunked_image}" > "${archive}"
# The archive is self-contained, so nothing left in container
# storage is needed anymore: not the source image, not the Aurora
# base or akmods images (tagged, so a plain prune would keep
# them), not the Chunkah image. Remove them all so the load starts
# against an empty store.
podman image prune -af
echo "==> Disk usage after pruning container storage" && df -h / /mnt
# podman unpacks oci archives into TMPDIR before applying them;
# keep that on /mnt next to container storage instead of /var/tmp.
# /mnt is root-owned on the hosted runners, hence sudo + chown.
sudo mkdir -p /mnt/tmp
sudo chown "$(id -u):$(id -g)" /mnt/tmp
TMPDIR=/mnt/tmp podman load -i "${archive}"
rm -f "${archive}"
echo "==> Disk usage after load" && df -h / /mnt
for tag in ${{ steps.metadata.outputs.tags }}; do
podman tag "${chunked_image}" "${IMAGE_NAME}:${tag}"
done
# These `if` statements are so that pull requests for your custom images do not make it publish any packages under your name without you knowing
# They also check if the runner is on the default branch so that things like the merge queue (if you enable it), are going to work
- name: Login to GitHub Container Registry
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
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 }}
- name: Push To GHCR
uses: redhat-actions/push-to-registry@5ed88d269cf581ea9ef6dd6806d01562096bee9c # v2
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
id: push
env:
REGISTRY_USER: ${{ github.actor }}
REGISTRY_PASSWORD: ${{ github.token }}
with:
registry: ${{ env.IMAGE_REGISTRY }}
image: ${{ env.IMAGE_NAME }}
# Push exactly one tag. The remaining tags are copied from the
# pushed digest in the next step so every tag is byte-identical.
tags: ${{ env.DEFAULT_TAG }}
username: ${{ env.REGISTRY_USER }}
password: ${{ env.REGISTRY_PASSWORD }}
# podman can emit different manifest bytes for sequential pushes of
# the same local image: the first push compresses and uploads, later
# pushes reuse cached blob info, and the resulting manifests can carry
# different digests. That split what `latest` and the date tags pointed
# at and left `latest` outside the signed digest. Copy the single
# pushed manifest to the remaining tags server-side instead;
# --preserve-digests makes skopeo fail rather than rewrite the
# manifest, so every tag is identical by construction.
- name: Propagate tags from the pushed digest
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
run: |
set -euo pipefail
digest="${{ steps.push.outputs.digest }}"
if [ -z "${digest}" ]; then
echo "push step did not expose a digest" >&2
exit 1
fi
for tag in ${{ steps.metadata.outputs.tags }}; do
if [ "${tag}" = "${DEFAULT_TAG}" ]; then
continue
fi
skopeo copy --preserve-digests \
"docker://${IMAGE_REGISTRY}/${IMAGE_NAME}@${digest}" \
"docker://${IMAGE_REGISTRY}/${IMAGE_NAME}:${tag}"
done
# All tags in this job point at one manifest pushed from one local
# image. Enforce that as an invariant instead of trusting it: if any
# published tag resolves to a different digest than the one about to
# be signed, fail before signing rather than publish a half-signed
# tag set.
- name: Verify pushed tags share one digest
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
run: |
set -euo pipefail
expected="${{ steps.push.outputs.digest }}"
if [ -z "${expected}" ]; then
echo "push step did not expose a digest" >&2
exit 1
fi
for tag in ${{ steps.metadata.outputs.tags }}; do
actual=$(skopeo inspect --format '{{.Digest}}' "docker://${IMAGE_REGISTRY}/${IMAGE_NAME}:${tag}")
if [ "${actual}" != "${expected}" ]; then
echo "Tag ${tag} resolves to ${actual}, expected ${expected}" >&2
exit 1
fi
echo "Tag ${tag} -> ${actual} OK"
done
# This section is optional and only needs to be enabled if you plan on distributing
# your project for 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@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
with:
cosign-release: 'v2.6.1'
- name: Sign container image
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
run: |
cosign sign -y --key env://COSIGN_PRIVATE_KEY \
"${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.push.outputs.digest }}"
env:
COSIGN_EXPERIMENTAL: false
COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }}