Skip to content

Commit 7540d73

Browse files
authored
Publish per-artifact layers as multi-arch OCI indexes: (tinkerbell#39)
## Description Each artifact file is now its own layer so registries deduplicate shared blobs between per-arch and combined images. All three images (amd64, arm64, combined) are multi-arch OCI indexes. - Add --target flag (amd64/arm64/both) separate from --arch - Remove index subcommand; publish creates all three images - Update tag to tag all three images via tag_all - Add recap messages to publish, pull, and tag - Fix crane tarball filename in Dockerfile.release - Update CI to use target matrix and remove create-artifact-index job Fixes: # ## How Has This Been Tested? ## How are existing users impacted? What migration steps/scripts do we need? ## Checklist: I have: - [ ] updated the documentation and/or roadmap (if required) - [ ] added unit or e2e tests - [ ] provided instructions on how to upgrade
2 parents e40f755 + c1dc649 commit 7540d73

7 files changed

Lines changed: 323 additions & 143 deletions

File tree

.github/workflows/ci.yml

Lines changed: 33 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -364,14 +364,15 @@ jobs:
364364
# -------------------------------------------------------------------
365365
publish-artifacts:
366366
if: github.ref == 'refs/heads/main'
367-
runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
367+
runs-on: ${{ matrix.target == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
368368
needs: [build-iso]
369369
strategy:
370370
fail-fast: false
371371
matrix:
372-
arch: [amd64, arm64]
372+
target: [amd64, arm64, both]
373373
env:
374-
ARCH: ${{ matrix.arch }}
374+
ARCH: ${{ matrix.target == 'both' && 'amd64' || matrix.target }}
375+
TARGET: ${{ matrix.target }}
375376
steps:
376377
- name: Checkout code
377378
uses: actions/checkout@v6
@@ -381,55 +382,50 @@ jobs:
381382
- name: Load shared config
382383
run: cat .github/config.env >> "$GITHUB_ENV"
383384

384-
- name: Download kernel artifacts
385+
- name: Download kernel artifacts (amd64)
386+
if: matrix.target == 'amd64' || matrix.target == 'both'
385387
uses: actions/download-artifact@v4
386388
with:
387-
name: kernel-${{ matrix.arch }}
389+
name: kernel-amd64
388390
path: mkosi.output
389391

390-
- name: Download initramfs artifacts
392+
- name: Download initramfs artifacts (amd64)
393+
if: matrix.target == 'amd64' || matrix.target == 'both'
391394
uses: actions/download-artifact@v4
392395
with:
393-
name: initramfs-${{ matrix.arch }}
396+
name: initramfs-amd64
394397
path: out
395398

396-
- name: Download ISO artifact
399+
- name: Download ISO artifact (amd64)
400+
if: matrix.target == 'amd64' || matrix.target == 'both'
397401
uses: actions/download-artifact@v4
398402
with:
399-
name: iso-${{ matrix.arch }}
403+
name: iso-amd64
400404
path: out
401405

402-
- name: Install Python dependencies
403-
run: pip install -r requirements.txt
404-
405-
- name: Log in to GHCR
406-
uses: docker/login-action@v3
406+
- name: Download kernel artifacts (arm64)
407+
if: matrix.target == 'arm64' || matrix.target == 'both'
408+
uses: actions/download-artifact@v4
407409
with:
408-
registry: ghcr.io
409-
username: ${{ github.actor }}
410-
password: ${{ secrets.GITHUB_TOKEN }}
411-
412-
- name: Install crane
413-
uses: imjasonh/setup-crane@v0.4
410+
name: kernel-arm64
411+
path: mkosi.output
414412

415-
- name: Publish artifacts to GHCR
416-
run: ./build.py release publish
413+
- name: Download initramfs artifacts (arm64)
414+
if: matrix.target == 'arm64' || matrix.target == 'both'
415+
uses: actions/download-artifact@v4
416+
with:
417+
name: initramfs-arm64
418+
path: out
417419

418-
# -------------------------------------------------------------------
419-
# Create multi-arch OCI index from per-arch artifact manifests
420-
# -------------------------------------------------------------------
421-
create-artifact-index:
422-
runs-on: ubuntu-latest
423-
if: github.ref == 'refs/heads/main'
424-
needs: [publish-artifacts]
425-
steps:
426-
- name: Checkout code
427-
uses: actions/checkout@v6
420+
- name: Download ISO artifact (arm64)
421+
if: matrix.target == 'arm64' || matrix.target == 'both'
422+
uses: actions/download-artifact@v4
428423
with:
429-
fetch-depth: 0
424+
name: iso-arm64
425+
path: out
430426

431-
- name: Load shared config
432-
run: cat .github/config.env >> "$GITHUB_ENV"
427+
- name: Install Python dependencies
428+
run: pip install -r requirements.txt
433429

434430
- name: Log in to GHCR
435431
uses: docker/login-action@v3
@@ -441,8 +437,5 @@ jobs:
441437
- name: Install crane
442438
uses: imjasonh/setup-crane@v0.4
443439

444-
- name: Install Python dependencies
445-
run: pip install -r requirements.txt
446-
447-
- name: Create multi-arch artifact index
448-
run: ./build.py release index
440+
- name: Publish artifacts to GHCR
441+
run: ./build.py release publish

.github/workflows/release.yml

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,21 @@ jobs:
3333
- name: Install Python dependencies
3434
run: pip install -r requirements.txt
3535

36-
- name: Pull release artifacts (amd64)
36+
- name: Pull release artifacts (both)
3737
env:
38-
ARCH: amd64
3938
VERSION_EXCLUDE: ${{ github.ref_name }}
40-
run: ./build.py release pull --pull-output artifacts/amd64
41-
42-
- name: Pull release artifacts (arm64)
43-
env:
44-
ARCH: arm64
45-
VERSION_EXCLUDE: ${{ github.ref_name }}
46-
run: ./build.py release pull --pull-output artifacts/arm64
39+
run: ./build.py release pull --target both --pull-output artifacts/both
4740

4841
- name: Create GitHub Release
4942
env:
5043
GH_TOKEN: ${{ github.token }}
5144
run: |
5245
gh release create "${{ github.ref_name }}" \
53-
artifacts/amd64/* \
54-
artifacts/arm64/* \
46+
artifacts/both/* \
5547
--generate-notes \
5648
--title "${{ github.ref_name }}"
5749
58-
- name: Tag OCI artifact index with version
50+
- name: Tag OCI artifacts with version
5951
env:
6052
VERSION_EXCLUDE: ${{ github.ref_name }}
6153
run: ./build.py release tag ${{ github.ref_name }}

Dockerfile.release

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
1414
ca-certificates \
1515
curl \
1616
tar \
17-
&& rm -rf /var/lib/apt/lists/*
17+
&& rm -rf /var/lib/apt/lists/* \
18+
&& git config --global --add safe.directory /work
1819

1920
# Install crane (with checksum verification)
2021
RUN DPKG_ARCH="$(dpkg --print-architecture)" \
@@ -25,12 +26,12 @@ RUN DPKG_ARCH="$(dpkg --print-architecture)" \
2526
esac \
2627
&& CRANE_TARBALL="go-containerregistry_Linux_${CRANE_ARCH}.tar.gz" \
2728
&& CRANE_BASE_URL="https://github.com/google/go-containerregistry/releases/download/${CRANE_VERSION}" \
28-
&& curl -fsSL "${CRANE_BASE_URL}/${CRANE_TARBALL}" -o /tmp/crane.tar.gz \
29+
&& curl -fsSL "${CRANE_BASE_URL}/${CRANE_TARBALL}" -o /tmp/${CRANE_TARBALL} \
2930
&& curl -fsSL "${CRANE_BASE_URL}/checksums.txt" -o /tmp/checksums.txt \
3031
&& grep " ${CRANE_TARBALL}$" /tmp/checksums.txt | (cd /tmp && sha256sum -c -) \
31-
&& tar -xz -f /tmp/crane.tar.gz -C /usr/local/bin crane \
32+
&& tar -xz -f /tmp/${CRANE_TARBALL} -C /usr/local/bin crane \
3233
&& chmod +x /usr/local/bin/crane \
33-
&& rm -f /tmp/crane.tar.gz /tmp/checksums.txt \
34+
&& rm -f /tmp/${CRANE_TARBALL} /tmp/checksums.txt \
3435
&& crane version
3536

3637
# Install Python dependencies

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,52 @@ Output artifacts are placed in `out/`:
9292
- `out/captainos-<arch>.iso` — UEFI-bootable ISO image
9393
- `out/sha256sums-<arch>.txt` — SHA-256 checksums
9494
95+
## Release
96+
97+
CI publishes build artifacts as OCI images on every push to `main`. Pushing a version tag (`v*`) creates a GitHub Release with downloadable files and tags the OCI images with the release version.
98+
99+
### OCI artifact images
100+
101+
Three multi-arch OCI indexes are published per build:
102+
103+
| Image | Tag | Contents |
104+
| --- | --- | --- |
105+
| amd64-only | `vX.Y.Z-<sha7>-amd64` | vmlinuz, initramfs, ISO, checksums (amd64) |
106+
| arm64-only | `vX.Y.Z-<sha7>-arm64` | vmlinuz, initramfs, ISO, checksums (arm64) |
107+
| combined | `vX.Y.Z-<sha7>` | all artifacts from both architectures |
108+
109+
Each artifact file is pushed as its own OCI layer. Deterministic tar creation (zeroed metadata) ensures identical layer digests across per-arch and combined images, so registries deduplicate shared blobs — the combined image adds zero additional storage.
110+
111+
All three images are multi-arch OCI indexes with `linux/amd64` and `linux/arm64` platform entries pointing to the same content, so any platform can pull them. Images are compatible with:
112+
113+
- **containerd** — valid `rootfs.diff_ids` in the config; Kubernetes image-volume mounts work
114+
- **crane export** — extracts individual artifact files for release workflows
115+
116+
### GitHub Release
117+
118+
When a `v*` tag is pushed, the release workflow:
119+
120+
1. Pulls the combined OCI image (both architectures)
121+
2. Attaches all artifacts as downloadable files on the GitHub Release page:
122+
- `vmlinuz-amd64`, `initramfs-amd64.cpio.zst`, `captainos-amd64.iso`, `sha256sums-amd64.txt`
123+
- `vmlinuz-arm64`, `initramfs-arm64.cpio.zst`, `captainos-arm64.iso`, `sha256sums-arm64.txt`
124+
3. Tags all three OCI images with the clean release version (`vX.Y.Z`, `vX.Y.Z-amd64`, `vX.Y.Z-arm64`)
125+
126+
### Release subcommands
127+
128+
```bash
129+
# Publish artifacts as a multi-arch OCI image
130+
./build.py release publish --target amd64
131+
132+
# Pull and extract artifacts
133+
./build.py release pull --target both --pull-output ./out/release/
134+
135+
# Tag all artifact images with a release version
136+
./build.py release tag v1.0.0
137+
```
138+
139+
Run `./build.py release <subcommand> -h` for full flag reference.
140+
95141
## Build modes
96142
97143
Each stage can be executed in one of three modes:
@@ -137,11 +183,14 @@ Each stage can be executed in one of three modes:
137183
│ ├── kernel.py # Kernel compilation logic
138184
│ ├── tools.py # Binary tool downloader
139185
│ ├── artifacts.py # Artifact collection & checksums
186+
│ ├── oci.py # OCI artifact publish/pull/tag
187+
│ ├── crane.py # crane CLI wrapper
140188
│ ├── iso.py # ISO image assembly
141189
│ ├── qemu.py # QEMU boot testing
142190
│ ├── log.py # Colored logging
143191
│ └── util.py # Shared helpers & arch mapping
144192
├── Dockerfile # Builder container definition
193+
├── Dockerfile.release # Lightweight container for OCI release ops
145194
├── mkosi.conf # mkosi image configuration
146195
├── mkosi.postinst # Post-install hooks (symlinks, cleanup)
147196
├── mkosi.finalize # Final image adjustments

0 commit comments

Comments
 (0)