Skip to content

Publish GardenLinux Images #60

Publish GardenLinux Images

Publish GardenLinux Images #60

name: Publish GardenLinux Images
on:
schedule:
- cron: '0 0 * * 0'
workflow_dispatch:
inputs:
version:
description: 'GardenLinux version (leave empty for latest release)'
required: false
type: string
jobs:
resolve-version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.resolve.outputs.version }}
should_publish: ${{ steps.resolve.outputs.should_publish }}
steps:
- name: Resolve version
id: resolve
run: |
if [ -n "${{ inputs.version }}" ]; then
VERSION="${{ inputs.version }}"
echo "Using manually specified version: $VERSION"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "should_publish=true" >> "$GITHUB_OUTPUT"
exit 0
fi
VERSION=$(curl -s https://api.github.com/repos/gardenlinux/gardenlinux/releases/latest | jq -r '.tag_name')
if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then
echo "::error::Failed to resolve latest GardenLinux release"
exit 1
fi
echo "Resolved latest GardenLinux release: $VERSION"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
# Only check for duplicates on scheduled runs, manual dispatch always publishes.
if [ "${{ github.event_name }}" = "schedule" ]; then
if docker manifest inspect "ghcr.io/${{ github.repository_owner }}/gardenlinux/gardener:${VERSION}-kvm-amd64" > /dev/null 2>&1; then
echo "Version $VERSION already published, skipping"
echo "should_publish=false" >> "$GITHUB_OUTPUT"
exit 0
fi
fi
echo "should_publish=true" >> "$GITHUB_OUTPUT"
publish:
needs: resolve-version
if: needs.resolve-version.outputs.should_publish == 'true'
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- variant: metal
image_name: gardenlinux
asset_pattern: baremetal_pxe
- variant: kvm
image_name: gardenlinux/gardener
asset_pattern: kvm-gardener_prod
- variant: metal
image_name: gardenlinux/gardener
asset_pattern: baremetal-gardener_pxe
- variant: metal
image_name: gardenlinux/capi
asset_pattern: baremetal-capi
env:
VERSION: ${{ needs.resolve-version.outputs.version }}
IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.image_name }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Fetch artifacts from GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
ASSETS=$(curl -sH "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/gardenlinux/gardenlinux/releases/tags/$VERSION" \
| jq -r '.assets[].name')
for ARCH in amd64 arm64; do
ASSET=$(echo "$ASSETS" | grep -E "^${{ matrix.asset_pattern }}-${ARCH}-.*\.tar\.xz$" | head -1)
if [ -z "$ASSET" ]; then
echo "::error::Could not find asset matching ${{ matrix.asset_pattern }}-${ARCH}-*.tar.xz"
exit 1
fi
# The asset base name (without .tar.xz) is the prefix for all
# files inside the archive.
ASSET_BASE="${ASSET%.tar.xz}"
echo "Downloading $ASSET"
mkdir -p "artifacts/${ARCH}"
curl -sL "https://github.com/gardenlinux/gardenlinux/releases/download/${VERSION}/${ASSET}" \
-o "artifacts/${ARCH}/${ASSET}"
echo "Extracting $ASSET"
tar -xf "artifacts/${ARCH}/${ASSET}" -C "artifacts/${ARCH}"
rm "artifacts/${ARCH}/${ASSET}"
if [ "${{ matrix.variant }}" = "kvm" ]; then
RAW_FILE="artifacts/${ARCH}/${ASSET_BASE}.raw"
if [ ! -f "$RAW_FILE" ]; then
echo "::error::Expected .raw file not found at ${RAW_FILE}"
exit 1
fi
echo "KVM_${ARCH^^}_RAW=${RAW_FILE}" >> "$GITHUB_ENV"
else
# Metal: extract the nested PXE tarball to get initrd, vmlinuz, root.squashfs.
PXE_TARBALL="artifacts/${ARCH}/${ASSET_BASE}.pxe.tar.gz"
if [ ! -f "$PXE_TARBALL" ]; then
echo "::error::Could not find nested PXE tarball at ${PXE_TARBALL}"
exit 1
fi
tar -xzf "$PXE_TARBALL" -C "artifacts/${ARCH}"
for BIN in initrd vmlinuz root.squashfs; do
if [ ! -f "artifacts/${ARCH}/${BIN}" ]; then
echo "::error::Could not find ${BIN} for ${ARCH}"
exit 1
fi
done
echo "METAL_${ARCH^^}_DIR=artifacts/${ARCH}" >> "$GITHUB_ENV"
echo "METAL_${ARCH^^}_ASSET_BASE=${ASSET_BASE}" >> "$GITHUB_ENV"
fi
done
- name: Create cmdline files (KVM)
if: matrix.variant == 'kvm'
run: |
echo -n "gl.url=/dev/disk/by-id/virtio-machineboot gl.live=1 gl.ovl=/:tmpfs cgroup_enable=memory swapaccount=1 ignition.firstboot=1 ignition.platform.id=qemu consoleblank=0 console=tty0 console=ttyS0,115200 earlyprintk=ttyS0,115200" > amd64.cmdline
echo -n "gl.url=/dev/disk/by-id/virtio-machineboot gl.live=1 gl.ovl=/:tmpfs cgroup_enable=memory swapaccount=1 ignition.firstboot=1 ignition.platform.id=qemu consoleblank=0 console=tty0 console=ttyAMA0,115200 earlyprintk=ttyAMA0,115200" > arm64.cmdline
- name: Install systemd-ukify (Metal)
if: matrix.variant == 'metal'
run: |
sudo apt-get update -qq
sudo apt-get install -y systemd-ukify
- name: Build UKI (Metal)
if: matrix.variant == 'metal'
run: |
set -euo pipefail
for ARCH in amd64 arm64; do
ARCH_UPPER="${ARCH^^}"
DIR_VAR="METAL_${ARCH_UPPER}_DIR"
DIR="${!DIR_VAR}"
BASE_VAR="METAL_${ARCH_UPPER}_ASSET_BASE"
ASSET_BASE="${!BASE_VAR}"
# Extract the arch-correct EFI stub from the rootfs tar.
if [ "$ARCH" = "amd64" ]; then
STUB_NAME="linuxx64.efi.stub"
else
STUB_NAME="linuxaa64.efi.stub"
fi
STUB_PATH="usr/lib/systemd/boot/efi/${STUB_NAME}"
tar -xf "${DIR}/${ASSET_BASE}.tar" -C "${DIR}" "${STUB_PATH}"
EFI_STUB="${DIR}/${STUB_PATH}"
# Build initrd-uki by embedding squashfs into initrd
cp "${DIR}/initrd" "${DIR}/initrd-uki"
(cd "${DIR}" && echo root.squashfs | cpio -H newc -o | xz --check=crc32) >> "${DIR}/initrd-uki"
CMDLINE="initrd=initrd gl.ovl=/:tmpfs gl.live=1 ip=any console=ttyS0,115200 console=tty0 earlyprintk=ttyS0,115200 consoleblank=0 ignition.firstboot=1 ignition.config.url=http://boot.onmetal.de:8083/ignition ignition.config.url.append.uuid=true ignition.platform.id=metal"
ukify build \
--linux "${DIR}/vmlinuz" \
--initrd "${DIR}/initrd-uki" \
--stub "$EFI_STUB" \
--cmdline "$CMDLINE" \
--output "${DIR}/uki.img"
echo "Built UKI for ${ARCH}"
done
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version: 'stable'
- name: Install ironcore-image
run: |
go install github.com/ironcore-dev/ironcore-image/cmd@latest
mv "$(go env GOPATH)/bin/cmd" "$(go env GOPATH)/bin/ironcore-image"
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
- name: Build image (KVM)
if: matrix.variant == 'kvm'
run: |
ironcore-image build \
--tag "${IMAGE}:${VERSION}-kvm" \
--config "arch=amd64,rootfs=${KVM_AMD64_RAW},cmdline=./amd64.cmdline" \
--config "arch=arm64,rootfs=${KVM_ARM64_RAW},cmdline=./arm64.cmdline"
- name: Build image (Metal)
if: matrix.variant == 'metal'
run: |
ironcore-image build \
--tag "${IMAGE}:${VERSION}-metal" \
--config "arch=amd64,squashfs=${METAL_AMD64_DIR}/root.squashfs,initramfs=${METAL_AMD64_DIR}/initrd,kernel=${METAL_AMD64_DIR}/vmlinuz,uki=${METAL_AMD64_DIR}/uki.img" \
--config "arch=arm64,squashfs=${METAL_ARM64_DIR}/root.squashfs,initramfs=${METAL_ARM64_DIR}/initrd,kernel=${METAL_ARM64_DIR}/vmlinuz,uki=${METAL_ARM64_DIR}/uki.img"
- name: Push image
run: |
ironcore-image push "${IMAGE}:${VERSION}-${{ matrix.variant }}" --push-sub-manifests