diff --git a/.github/workflows/container-publish.yml b/.github/workflows/container-publish.yml index 0f7fd03..d85f409 100644 --- a/.github/workflows/container-publish.yml +++ b/.github/workflows/container-publish.yml @@ -17,149 +17,60 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ${{ matrix.arch.runner }} strategy: fail-fast: false matrix: - include: - - os: rockylinux - version: "8" - context: rockylinux-8 - file: rockylinux-8/Containerfile - - os: rockylinux - version: "8.9" - context: rockylinux-8 - file: rockylinux-8/Containerfile-8.9 - - os: rockylinux - version: "8.10" - context: rockylinux-8 - file: rockylinux-8/Containerfile-8.10 - - os: rockylinux - version: "9" - context: rockylinux-9 - file: rockylinux-9/Containerfile - - os: rockylinux - version: "9.6" - context: rockylinux-9 - file: rockylinux-9/Containerfile-9.6 - - os: rockylinux - version: "9.7" - context: rockylinux-9 - file: rockylinux-9/Containerfile-9.7 - - os: rockylinux - version: "10" - context: rockylinux-10 - file: rockylinux-10/Containerfile - - os: rockylinux - version: "10.0" - context: rockylinux-10 - file: rockylinux-10/Containerfile-10.0 - - os: rockylinux - version: "10.1" - context: rockylinux-10 - file: rockylinux-10/Containerfile-10.1 - - os: almalinux - version: "8" - context: almalinux-8 - file: almalinux-8/Containerfile - - os: almalinux - version: "8.9" - context: almalinux-8 - file: almalinux-8/Containerfile-8.9 - - os: almalinux - version: "8.10" - context: almalinux-8 - file: almalinux-8/Containerfile-8.10 - - os: almalinux - version: "9" - context: almalinux-9 - file: almalinux-9/Containerfile - - os: almalinux - version: "9.6" - context: almalinux-9 - file: almalinux-9/Containerfile-9.6 - - os: almalinux - version: "9.7" - context: almalinux-9 - file: almalinux-9/Containerfile-9.7 - - os: almalinux - version: "10" - context: almalinux-10 - file: almalinux-10/Containerfile - - os: almalinux - version: "10.0" - context: almalinux-10 - file: almalinux-10/Containerfile-10.0 - - os: almalinux - version: "10.1" - context: almalinux-10 - file: almalinux-10/Containerfile-10.1 - - os: leap - version: "15" - context: leap - file: leap/Containerfile-15 - - os: leap - version: "16" - context: leap - file: leap/Containerfile-16.0 - - os: leap - version: "16.0" - context: leap - file: leap/Containerfile-16.0 - - os: tumbleweed - version: latest - context: tumbleweed - file: tumbleweed/Containerfile - - os: debian - version: "12" - context: debian - file: debian/Containerfile-12 - - os: debian - version: "13" - context: debian - file: debian/Containerfile-13 - - os: openeuler - version: "24.03" - context: openeuler-24.03 - file: openeuler-24.03/Containerfile - - os: ubuntu - version: "24.04" - context: ubuntu - file: ubuntu/Containerfile-24.04 - - os: ubuntu - version: "26.04" - context: ubuntu - file: ubuntu/Containerfile-26.04 + arch: + - {platform: linux/amd64, runner: ubuntu-latest, platform_pair: linux-amd64} + - {platform: linux/arm64, runner: ubuntu-24.04-arm, platform_pair: linux-arm64} + image: + - {os: rockylinux, version: "8", context: rockylinux-8, file: rockylinux-8/Containerfile} + - {os: rockylinux, version: "8.9", context: rockylinux-8, file: rockylinux-8/Containerfile-8.9} + - {os: rockylinux, version: "8.10", context: rockylinux-8, file: rockylinux-8/Containerfile-8.10} + - {os: rockylinux, version: "9", context: rockylinux-9, file: rockylinux-9/Containerfile} + - {os: rockylinux, version: "9.7", context: rockylinux-9, file: rockylinux-9/Containerfile-9.7} + - {os: rockylinux, version: "9.8", context: rockylinux-9, file: rockylinux-9/Containerfile-9.8} + - {os: rockylinux, version: "10", context: rockylinux-10, file: rockylinux-10/Containerfile} + - {os: rockylinux, version: "10.1", context: rockylinux-10, file: rockylinux-10/Containerfile-10.1} + - {os: rockylinux, version: "10.2", context: rockylinux-10, file: rockylinux-10/Containerfile-10.2} + - {os: almalinux, version: "8", context: almalinux-8, file: almalinux-8/Containerfile} + - {os: almalinux, version: "8.9", context: almalinux-8, file: almalinux-8/Containerfile-8.9} + - {os: almalinux, version: "8.10", context: almalinux-8, file: almalinux-8/Containerfile-8.10} + - {os: almalinux, version: "9", context: almalinux-9, file: almalinux-9/Containerfile} + - {os: almalinux, version: "9.7", context: almalinux-9, file: almalinux-9/Containerfile-9.7} + - {os: almalinux, version: "9.8", context: almalinux-9, file: almalinux-9/Containerfile-9.8} + - {os: almalinux, version: "10", context: almalinux-10, file: almalinux-10/Containerfile} + - {os: almalinux, version: "10.1", context: almalinux-10, file: almalinux-10/Containerfile-10.1} + - {os: almalinux, version: "10.2", context: almalinux-10, file: almalinux-10/Containerfile-10.2} + - {os: leap, version: "15", context: leap, file: leap/Containerfile-15} + - {os: leap, version: "16", context: leap, file: leap/Containerfile-16.0} + - {os: leap, version: "16.0", context: leap, file: leap/Containerfile-16.0} + - {os: tumbleweed, version: "latest", context: tumbleweed, file: tumbleweed/Containerfile} + - {os: debian, version: "12", context: debian, file: debian/Containerfile-12} + - {os: debian, version: "13", context: debian, file: debian/Containerfile-13} + - {os: openeuler, version: "24.03", context: openeuler-24.03, file: openeuler-24.03/Containerfile} + - {os: ubuntu, version: "24.04", context: ubuntu, file: ubuntu/Containerfile-24.04} + - {os: ubuntu, version: "26.04", context: ubuntu, file: ubuntu/Containerfile-26.04} permissions: contents: read packages: write - # This is used to complete the identity challenge - # with sigstore/fulcio when running outside of PRs. + # Required for keyless signing with sigstore/fulcio when not in a PR. id-token: write - steps: - name: Checkout repository uses: actions/checkout@v5 - - name: Make Containerfiles + # Some OS directories generate versioned Containerfiles from templates. + - name: Generate versioned Containerfiles run: | - make -C rockylinux-8 - make -C rockylinux-9 - make -C rockylinux-10 - make -C almalinux-8 - make -C almalinux-9 - make -C almalinux-10 + if [ -f "${{ matrix.image.context }}/Makefile" ]; then + make -C "${{ matrix.image.context }}" + fi - # Install the cosign tool - # https://github.com/sigstore/cosign-installer - - name: Install Cosign - if: github.event_name != 'pull_request' - uses: sigstore/cosign-installer@v3.5.0 - - # Workaround: https://github.com/docker/build-push-action/issues/461 - name: Setup Docker buildx uses: docker/setup-buildx-action@v4 @@ -179,31 +90,96 @@ jobs: id: meta uses: docker/metadata-action@v6 with: - images: | - ghcr.io/${{ github.repository_owner }}/warewulf-${{ matrix.os }} - tags: | - type=schedule - type=raw,${{ matrix.version }} + images: ghcr.io/${{ github.repository_owner }}/warewulf-${{ matrix.image.os }} - # Build and push Docker image with Buildx + # Build single-platform image and push by content-addressed digest. + # No tag is written here; the merge job assembles the manifest index. # https://github.com/docker/build-push-action - - name: Build and push image - id: build-and-push + - name: Build and push image by digest + id: build uses: docker/build-push-action@v7 with: - context: ${{ matrix.context }} - file: ${{ matrix.file }} - platforms: linux/amd64,linux/arm64 - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} + context: ${{ matrix.image.context }} + file: ${{ matrix.image.file }} + platforms: ${{ matrix.arch.platform }} + outputs: type=image,name=ghcr.io/${{ github.repository_owner }}/warewulf-${{ matrix.image.os }},oci-mediatypes=true,push-by-digest=true,name-canonical=true,push=${{ github.event_name != 'pull_request' }} labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=${{ matrix.image.os }}-${{ matrix.image.version }}-${{ matrix.arch.platform }} + cache-to: type=gha,mode=max,scope=${{ matrix.image.os }}-${{ matrix.image.version }}-${{ matrix.arch.platform }} - # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable - - name: Sign the images with GitHub OIDC Token + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + echo "${{ matrix.image.os }}:${{ matrix.image.version }}" > "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ matrix.image.os }}-${{ matrix.image.version }}-${{ matrix.arch.platform_pair }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + + # Combine per-arch digests into a multi-arch manifest index, then sign. + # On pull requests, logs the imagetools commands that would run without executing them. + merge: + runs-on: ubuntu-latest + needs: build + + permissions: + contents: read + packages: write + id-token: write + + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + pattern: digests-* + merge-multiple: true + path: /tmp/digests + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v4 + + - name: Log into registry ghcr.io + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Install the cosign tool + # https://github.com/sigstore/cosign-installer + - name: Install Cosign if: github.event_name != 'pull_request' - env: - DIGEST: ${{ steps.build-and-push.outputs.digest }} - TAGS: ${{ steps.meta.outputs.tags }} - run: cosign sign --yes "${TAGS}@${DIGEST}" + uses: sigstore/cosign-installer@v3.5.0 + + # Each digest file is named by its hash; its content is "{os}:{version}". + # Group digests by image, create one manifest index per image, then sign. + # On PRs, log the imagetools commands instead of running them. + # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable + - name: Create manifest lists and sign + run: | + declare -A image_digests + for f in /tmp/digests/*; do + hash=$(basename "$f") + read -r os_version < "$f" + os="${os_version%%:*}"; version="${os_version#*:}" + repo="ghcr.io/${{ github.repository_owner }}/warewulf-${os}" + image_digests["${os}:${version}"]+=" ${repo}@sha256:${hash}" + done + + for key in "${!image_digests[@]}"; do + os="${key%%:*}"; version="${key#*:}" + image="ghcr.io/${{ github.repository_owner }}/warewulf-${os}:${version}" + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "[dry-run] docker buildx imagetools create -t ${image} ${image_digests[$key]}" + else + docker buildx imagetools create -t "${image}" ${image_digests["$key"]} + digest=$(docker buildx imagetools inspect "${image}" --format '{{.Manifest.Digest}}') + cosign sign --yes "${image}@${digest}" + fi + done diff --git a/README.md b/README.md index 540385e..36cdd86 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ These containers are published on the [GitHub container registry][1]. * [AlmaLinux 10](almalinux-10) * [openEuler 24.03](openeuler-24.03) * [openSUSE Leap](leap) +* [openSUSE Tumbleweed](tumbleweed) +* [Debian](debian) +* [Ubuntu](ubuntu) ## Additional examples diff --git a/almalinux-10/Makefile b/almalinux-10/Makefile index c0cfa78..2b9cdfe 100644 --- a/almalinux-10/Makefile +++ b/almalinux-10/Makefile @@ -1,6 +1,6 @@ .PHONY: all -all: Containerfile-10.0 all: Containerfile-10.1 +all: Containerfile-10.2 .PHONY: clean clean: @@ -9,5 +9,5 @@ clean: Containerfile-10.%: Containerfile-vault env release=10.$* envsubst '$$release' $@ -Containerfile-10.1: Containerfile-fixed - env release=10.1 envsubst '$$release' $@ +Containerfile-10.2: Containerfile-fixed + env release=10.2 envsubst '$$release' $@ diff --git a/almalinux-9/Makefile b/almalinux-9/Makefile index 16fcce7..b632ff5 100644 --- a/almalinux-9/Makefile +++ b/almalinux-9/Makefile @@ -1,6 +1,6 @@ .PHONY: all -all: Containerfile-9.6 all: Containerfile-9.7 +all: Containerfile-9.8 .PHONY: clean clean: @@ -9,5 +9,5 @@ clean: Containerfile-9.%: Containerfile-vault env release=9.$* envsubst '$$release' $@ -Containerfile-9.7: Containerfile-fixed - env release=9.7 envsubst '$$release' $@ +Containerfile-9.8: Containerfile-fixed + env release=9.8 envsubst '$$release' $@ diff --git a/rockylinux-10/Makefile b/rockylinux-10/Makefile index c0cfa78..2b9cdfe 100644 --- a/rockylinux-10/Makefile +++ b/rockylinux-10/Makefile @@ -1,6 +1,6 @@ .PHONY: all -all: Containerfile-10.0 all: Containerfile-10.1 +all: Containerfile-10.2 .PHONY: clean clean: @@ -9,5 +9,5 @@ clean: Containerfile-10.%: Containerfile-vault env release=10.$* envsubst '$$release' $@ -Containerfile-10.1: Containerfile-fixed - env release=10.1 envsubst '$$release' $@ +Containerfile-10.2: Containerfile-fixed + env release=10.2 envsubst '$$release' $@ diff --git a/rockylinux-9/Makefile b/rockylinux-9/Makefile index 16fcce7..b632ff5 100644 --- a/rockylinux-9/Makefile +++ b/rockylinux-9/Makefile @@ -1,6 +1,6 @@ .PHONY: all -all: Containerfile-9.6 all: Containerfile-9.7 +all: Containerfile-9.8 .PHONY: clean clean: @@ -9,5 +9,5 @@ clean: Containerfile-9.%: Containerfile-vault env release=9.$* envsubst '$$release' $@ -Containerfile-9.7: Containerfile-fixed - env release=9.7 envsubst '$$release' $@ +Containerfile-9.8: Containerfile-fixed + env release=9.8 envsubst '$$release' $@