From 94aa5845f7231b82995ed76f1abbe9e3fa67ee5c Mon Sep 17 00:00:00 2001 From: Fabian Koller Date: Thu, 6 Nov 2025 09:44:13 +0100 Subject: [PATCH 1/6] ci: upload sci_usi artifact on dev build --- .github/workflows/dev.yml | 37 ++++++++++++++++++++++++++++++-- .github/workflows/upload_oci.yml | 21 +++++++++++++----- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index c6edf08b..8008d512 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -21,9 +21,15 @@ jobs: with: submodules: recursive - - name: use VERSION file to support dev build on rel-branch + - name: Derive version id: version - run: echo "VERSION=$(cat VERSION)" >> $GITHUB_OUTPUT + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "VERSION=today" >> $GITHUB_OUTPUT + else + echo "VERSION=$(cat VERSION)" >> $GITHUB_OUTPUT + fi + build: needs: [set_version] uses: gardenlinux/gardenlinux/.github/workflows/build.yml@3b5ec77e4873462d56658c4ecd56caa0ff63542b @@ -39,3 +45,30 @@ jobs: # aws_kms_role: ${{ secrets.KMS_SIGNING_IAM_ROLE }} # aws_oidc_session: ${{ secrets.AWS_OIDC_SESSION }} # secureboot_db_kms_arn: ${{ secrets.SECUREBOOT_DB_KMS_ARN }} + + meta: + name: Compute image metadata + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.event.action != 'closed' }} + runs-on: ubuntu-latest + outputs: + UPLOAD_VERSION: ${{ steps.meta.outputs.upload_version }} + steps: + - name: Derive image version (pr-123-HASH) + id: meta + run: | + PR_NUMBER=${{ github.event.pull_request.number }} + COMMIT_HASH=${{ github.event.pull_request.head.sha }} + UPLOAD_VERSION="pr-${PR_NUMBER}-${COMMIT_HASH}" + echo "upload_version=${UPLOAD_VERSION}" >> $GITHUB_OUTPUT + + upload: + name: Upload PR image to OCI + needs: [build, meta, set_version] + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.event.action != 'closed' }} + uses: ./.github/workflows/upload_oci.yml + with: + version: ${{ needs.set_version.outputs.VERSION }} + upload_version: ${{ needs.meta.outputs.UPLOAD_VERSION }} + flavor_filter: '--include-only "metal-sci_usi-amd64"' + additional_semver_tag: false + secrets: inherit diff --git a/.github/workflows/upload_oci.yml b/.github/workflows/upload_oci.yml index 78d772e6..d0a1c772 100644 --- a/.github/workflows/upload_oci.yml +++ b/.github/workflows/upload_oci.yml @@ -5,12 +5,21 @@ on: version: type: string default: today + upload_version: + type: string + default: "" + additional_semver_tag: + type: boolean + default: true + flavor_filter: + type: string + default: '--exclude "bare-*"' jobs: generate_matrix_publish: name: Generate flavors matrix to publish uses: gardenlinux/gardenlinux/.github/workflows/build_flavors_matrix.yml@3b5ec77e4873462d56658c4ecd56caa0ff63542b with: - flags: '--exclude "bare-*" --no-arch --json-by-arch --build --test' + flags: '${{ inputs.flavor_filter }} --no-arch --json-by-arch --build --test' upload_gl_artifacts: name: upload to OCI needs: [ generate_matrix_publish ] @@ -62,17 +71,19 @@ jobs: --dir "${CNAME}" \ --container "ghcr.io/${{ github.repository }}" \ --arch ${{ matrix.arch }} \ - --version ${{ inputs.version }} \ + --version ${{ inputs.upload_version || inputs.version }} \ --cname "${CNAME}" \ --cosign_file digest.txt \ --manifest_file "oci_manifest_entry_${CNAME}.json" - name: Add additional semver tag + if: ${{ inputs.additional_semver_tag }} run: | echo ${{ secrets.GITHUB_TOKEN }} | oras login -u ${{ github.repository_owner }} --password-stdin ghcr.io + UPLOAD_VER="${{ inputs.upload_version || inputs.version }}" CNAME2=${CNAME//_/-} CNAME2=${CNAME2//-${{ inputs.version }}/} - echo "Adding additional tag: ${{ inputs.version }}-${CNAME2}-${{ matrix.arch }}" - oras tag ghcr.io/${{ github.repository }}:${{ inputs.version }}-${CNAME}-${{ matrix.arch }} ${{ inputs.version }}-${CNAME2}-${{ matrix.arch }} + echo "Adding additional tag: ${UPLOAD_VER}-${CNAME2}-${{ matrix.arch }}" + oras tag ghcr.io/${{ github.repository }}:${UPLOAD_VER}-${CNAME}-${{ matrix.arch }} ${UPLOAD_VER}-${CNAME2}-${{ matrix.arch }} - uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # pin@v4.2.3 with: path: oci_manifest_entry_${{ env.CNAME }}.json @@ -125,5 +136,5 @@ jobs: gl-oci update-index \ --container "ghcr.io/${{ github.repository }}" \ - --version ${{ inputs.version }} \ + --version ${{ inputs.upload_version || inputs.version }} \ --manifest_folder manifests From f59b7ba131d6a3ad0a17d4736036a05d36a5f991 Mon Sep 17 00:00:00 2001 From: Fabian Koller Date: Fri, 7 Nov 2025 13:14:58 +0100 Subject: [PATCH 2/6] ci: test uploaded dev build --- .github/workflows/dev.yml | 16 +++++++++++++--- .github/workflows/test.yml | 6 ++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 8008d512..a2cfb694 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -52,14 +52,16 @@ jobs: runs-on: ubuntu-latest outputs: UPLOAD_VERSION: ${{ steps.meta.outputs.upload_version }} + COMMIT_SHA: ${{ steps.meta.outputs.sha }} steps: - - name: Derive image version (pr-123-HASH) + - name: Derive image version id: meta run: | PR_NUMBER=${{ github.event.pull_request.number }} - COMMIT_HASH=${{ github.event.pull_request.head.sha }} - UPLOAD_VERSION="pr-${PR_NUMBER}-${COMMIT_HASH}" + UPLOAD_VERSION="pr-${PR_NUMBER}" echo "upload_version=${UPLOAD_VERSION}" >> $GITHUB_OUTPUT + SHA="${GITHUB_SHA::8}" + echo "sha=${SHA}" >> $GITHUB_OUTPUT upload: name: Upload PR image to OCI @@ -72,3 +74,11 @@ jobs: flavor_filter: '--include-only "metal-sci_usi-amd64"' additional_semver_tag: false secrets: inherit + + test: + name: Test PR image + needs: [set_version, meta, upload] + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.event.action != 'closed' }} + uses: ./.github/workflows/test.yml + with: + image_tag: "pr-${{ github.event.pull_request.number }}-metal-sci_usi-amd64-${{ needs.set_version.outputs.VERSION }}-${{ needs.meta.outputs.COMMIT_SHA }}-amd64" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 76a1587e..83a0f461 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,6 +14,12 @@ on: description: "Image tag to test (must be usi-sci)" type: string default: "" + workflow_call: + inputs: + image_tag: + description: "Image tag to test (must be usi-sci)" + type: string + required: true jobs: test: From d32ec48ae12b1b38cd3742cb10c59026475a7133 Mon Sep 17 00:00:00 2001 From: Fabian Koller Date: Mon, 10 Nov 2025 13:21:07 +0100 Subject: [PATCH 3/6] ci: allow supplying custom OCI_TAG during persist --- .github/actions/test/integration/build/action.yml | 1 + .../test/integration/build/dev-user-butane.yaml | 4 +++- .github/workflows/dev.yml | 9 ++++++--- .github/workflows/upload_oci.yml | 11 +++-------- features/_usi/initrd.include/usr/bin/persist | 13 ++++++------- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/actions/test/integration/build/action.yml b/.github/actions/test/integration/build/action.yml index faf2200c..adcf018c 100644 --- a/.github/actions/test/integration/build/action.yml +++ b/.github/actions/test/integration/build/action.yml @@ -103,6 +103,7 @@ runs: PASSWORD=$(openssl passwd "password") echo "PASSWORD=$PASSWORD" >> $GITHUB_ENV sed -i "s|PASSWORD_GOES_HERE|$PASSWORD|g" ./.github/actions/test/integration/build/dev-user-butane.yaml + sed -i "s|TAG_GOES_HERE|${{ inputs.image_tag }}|g" ./.github/actions/test/integration/build/dev-user-butane.yaml butane --pretty --strict ./.github/actions/test/integration/build/dev-user-butane.yaml > "/opt/${TAG}.ign" - name: Download ubuntu cloud image diff --git a/.github/actions/test/integration/build/dev-user-butane.yaml b/.github/actions/test/integration/build/dev-user-butane.yaml index 885a3165..b8486454 100644 --- a/.github/actions/test/integration/build/dev-user-butane.yaml +++ b/.github/actions/test/integration/build/dev-user-butane.yaml @@ -50,12 +50,14 @@ storage: contents: inline: | NON_HUGEPAGES_PM=1000 - # pull the image from GHCR instead of keppel - path: /opt/persist/gl-oci.conf mode: 0644 contents: inline: | + # pull the image from GHCR instead of keppel OCI_REPO=ghcr.io/gardenlinux/gardenlinux-ccloud + # point to a custom tag to download for the persist step + OCI_TAG=TAG_GOES_HERE # Enable unlimited core dumps for all systemd services - path: /etc/systemd/system.conf.d/10-core.conf mode: 0644 diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index a2cfb694..920a279f 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -21,7 +21,7 @@ jobs: with: submodules: recursive - - name: Derive version + - name: use VERSION file to support dev build on rel-branch id: version run: | if [ "${{ github.event_name }}" = "pull_request" ]; then @@ -72,7 +72,6 @@ jobs: version: ${{ needs.set_version.outputs.VERSION }} upload_version: ${{ needs.meta.outputs.UPLOAD_VERSION }} flavor_filter: '--include-only "metal-sci_usi-amd64"' - additional_semver_tag: false secrets: inherit test: @@ -81,4 +80,8 @@ jobs: if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.event.action != 'closed' }} uses: ./.github/workflows/test.yml with: - image_tag: "pr-${{ github.event.pull_request.number }}-metal-sci_usi-amd64-${{ needs.set_version.outputs.VERSION }}-${{ needs.meta.outputs.COMMIT_SHA }}-amd64" + image_tag: "${{ needs.meta.outputs.UPLOAD_VERSION }}-metal-sci-usi-amd64-${{ needs.meta.outputs.COMMIT_SHA }}-amd64" + +# TODO on PR close, clean up everything in form of +# oras repo tags ghcr.io/gardenlinux/gardenlinux-ccloud | grep -v -E '^[0-9]{4}\.[0-9]+.*$' +# oras repo tags ghcr.io/gardenlinux/gardenlinux-ccloud | grep -E '^PREFIX$' diff --git a/.github/workflows/upload_oci.yml b/.github/workflows/upload_oci.yml index d0a1c772..f68456db 100644 --- a/.github/workflows/upload_oci.yml +++ b/.github/workflows/upload_oci.yml @@ -8,9 +8,6 @@ on: upload_version: type: string default: "" - additional_semver_tag: - type: boolean - default: true flavor_filter: type: string default: '--exclude "bare-*"' @@ -71,19 +68,17 @@ jobs: --dir "${CNAME}" \ --container "ghcr.io/${{ github.repository }}" \ --arch ${{ matrix.arch }} \ - --version ${{ inputs.upload_version || inputs.version }} \ + --version ${{ inputs.version }} \ --cname "${CNAME}" \ --cosign_file digest.txt \ --manifest_file "oci_manifest_entry_${CNAME}.json" - name: Add additional semver tag - if: ${{ inputs.additional_semver_tag }} run: | echo ${{ secrets.GITHUB_TOKEN }} | oras login -u ${{ github.repository_owner }} --password-stdin ghcr.io - UPLOAD_VER="${{ inputs.upload_version || inputs.version }}" CNAME2=${CNAME//_/-} CNAME2=${CNAME2//-${{ inputs.version }}/} - echo "Adding additional tag: ${UPLOAD_VER}-${CNAME2}-${{ matrix.arch }}" - oras tag ghcr.io/${{ github.repository }}:${UPLOAD_VER}-${CNAME}-${{ matrix.arch }} ${UPLOAD_VER}-${CNAME2}-${{ matrix.arch }} + echo "Adding additional tag: ${{ inputs.upload_version || inputs.version }}-${CNAME2}-${{ matrix.arch }}" + oras tag ghcr.io/${{ github.repository }}:${{ inputs.version }}-${CNAME}-${{ matrix.arch }} ${{ inputs.upload_version || inputs.version }}-${CNAME2}-${{ matrix.arch }} - uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # pin@v4.2.3 with: path: oci_manifest_entry_${{ env.CNAME }}.json diff --git a/features/_usi/initrd.include/usr/bin/persist b/features/_usi/initrd.include/usr/bin/persist index 66171ee7..280ce539 100755 --- a/features/_usi/initrd.include/usr/bin/persist +++ b/features/_usi/initrd.include/usr/bin/persist @@ -62,20 +62,19 @@ if [ ! -f "$OS_RELEASE_FILE" ]; then echo "File not found '$OS_RELEASE_FILE', exiting" exit 1 fi - -# source optional config file -[ -f /sysroot/opt/persist/gl-oci.conf ] && . /sysroot/opt/persist/gl-oci.conf -OCI_REPO="${OCI_REPO:-keppel.global.cloud.sap/ccloud-ghcr-io-mirror/gardenlinux/gardenlinux-ccloud}" -OCI_ARCH="${OCI_ARCH:-amd64}" - # source os-release file . "$OS_RELEASE_FILE" # oras needs a proper HOME variable export HOME=/root +# source optional config file +[ -f /sysroot/opt/persist/gl-oci.conf ] && . /sysroot/opt/persist/gl-oci.conf +OCI_REPO="${OCI_REPO:-keppel.global.cloud.sap/ccloud-ghcr-io-mirror/gardenlinux/gardenlinux-ccloud}" +OCI_ARCH="${OCI_ARCH:-amd64}" + # setup OCI_TAG, UKI_SHA and fetch UKI -OCI_TAG="$GARDENLINUX_VERSION-$VARIANT_ID-$GARDENLINUX_COMMIT_ID-$OCI_ARCH" +OCI_TAG=${OCI_TAG:-"$GARDENLINUX_VERSION-$VARIANT_ID-$GARDENLINUX_COMMIT_ID-$OCI_ARCH"} OCI_TAG=${OCI_TAG//_/-} # replace underscores with dashes UKI_SHA=$(oras manifest fetch "$OCI_REPO:${OCI_TAG}" | jq -r '.layers[] | select(.mediaType=="application/io.gardenlinux.uki") | .digest') oras blob fetch "$OCI_REPO@$UKI_SHA" -o "$esp_dir/EFI/Linux/uki.efi" From 9d0513a9eb8508d34d6ebd68ebfeab76b369d6c5 Mon Sep 17 00:00:00 2001 From: Fabian Koller Date: Tue, 11 Nov 2025 10:37:40 +0100 Subject: [PATCH 4/6] ci: clean up PR OCIs after PR close --- .github/workflows/dev.yml | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 920a279f..c68d4fd9 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -12,6 +12,7 @@ on: - "docs/**" jobs: set_version: + if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }} runs-on: ubuntu-latest outputs: VERSION: ${{ steps.version.outputs.VERSION }} @@ -32,6 +33,7 @@ jobs: build: needs: [set_version] + if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }} uses: gardenlinux/gardenlinux/.github/workflows/build.yml@3b5ec77e4873462d56658c4ecd56caa0ff63542b with: version: ${{ needs.set_version.outputs.VERSION }} @@ -82,6 +84,28 @@ jobs: with: image_tag: "${{ needs.meta.outputs.UPLOAD_VERSION }}-metal-sci-usi-amd64-${{ needs.meta.outputs.COMMIT_SHA }}-amd64" -# TODO on PR close, clean up everything in form of -# oras repo tags ghcr.io/gardenlinux/gardenlinux-ccloud | grep -v -E '^[0-9]{4}\.[0-9]+.*$' -# oras repo tags ghcr.io/gardenlinux/gardenlinux-ccloud | grep -E '^PREFIX$' + cleanup_images: + name: Cleanup PR images + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.event.action == 'closed' }} + runs-on: ubuntu-latest + steps: + - name: oras + uses: oras-project/setup-oras@v1 + with: + version: 1.2.2 + - name: Cleanup OCI images + run: | + PR_NUMBER=${{ github.event.pull_request.number }} + UPLOAD_VERSION="pr-${PR_NUMBER}" + oras logout ghcr.io + echo "${{ secrets.GITHUB_TOKEN }}" | oras login ghcr.io -u "${{ github.actor }}" --password-stdin + tags=$(oras repo tags ghcr.io/gardenlinux/gardenlinux-ccloud | grep -E "^${UPLOAD_VERSION}(-.*)?$" || true) + if [ -n "$tags" ]; then + for tag in $tags; do + echo "Deleting tag: $tag" + oras manifest delete ghcr.io/gardenlinux/gardenlinux-ccloud:$tag || echo "Failed to delete $tag" + done + echo "Cleanup completed for PR ${PR_NUMBER}" + else + echo "No images found to cleanup for PR ${PR_NUMBER}" + fi From cf373835ca9206ce5a6d0f445d2231d13fdc5163 Mon Sep 17 00:00:00 2001 From: Fabian Koller Date: Tue, 11 Nov 2025 11:55:52 +0100 Subject: [PATCH 5/6] ci: explicitly trigger dev workflow on PR close --- .github/workflows/dev.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index c68d4fd9..885357ae 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -7,6 +7,7 @@ on: - "**/README.md" - "docs/**" pull_request: + types: [opened, synchronize, reopened, closed] paths-ignore: - "**/README.md" - "docs/**" @@ -103,7 +104,7 @@ jobs: if [ -n "$tags" ]; then for tag in $tags; do echo "Deleting tag: $tag" - oras manifest delete ghcr.io/gardenlinux/gardenlinux-ccloud:$tag || echo "Failed to delete $tag" + oras manifest delete --force ghcr.io/gardenlinux/gardenlinux-ccloud:$tag || echo "Failed to delete $tag" done echo "Cleanup completed for PR ${PR_NUMBER}" else From 6133fe33bd69e6d9716c964a85c6ecfa87943ddf Mon Sep 17 00:00:00 2001 From: Fabian Koller Date: Tue, 11 Nov 2025 12:07:48 +0100 Subject: [PATCH 6/6] ci: switch from ORAS manifest delete to Github API delete oras manifest delete is not possible with GHCR https://docs.github.com/en/packages/learn-github-packages/deleting-and-restoring-a-package --- .github/workflows/dev.yml | 60 ++++++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 885357ae..0ee231fd 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -90,23 +90,51 @@ jobs: if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.event.action == 'closed' }} runs-on: ubuntu-latest steps: - - name: oras - uses: oras-project/setup-oras@v1 - with: - version: 1.2.2 - - name: Cleanup OCI images + - name: Cleanup OCI images via GitHub API run: | PR_NUMBER=${{ github.event.pull_request.number }} UPLOAD_VERSION="pr-${PR_NUMBER}" - oras logout ghcr.io - echo "${{ secrets.GITHUB_TOKEN }}" | oras login ghcr.io -u "${{ github.actor }}" --password-stdin - tags=$(oras repo tags ghcr.io/gardenlinux/gardenlinux-ccloud | grep -E "^${UPLOAD_VERSION}(-.*)?$" || true) - if [ -n "$tags" ]; then - for tag in $tags; do - echo "Deleting tag: $tag" - oras manifest delete --force ghcr.io/gardenlinux/gardenlinux-ccloud:$tag || echo "Failed to delete $tag" - done - echo "Cleanup completed for PR ${PR_NUMBER}" - else - echo "No images found to cleanup for PR ${PR_NUMBER}" + + all_version_ids="" + page=1 + page_size=100 + + # Collect all matching version IDs across pages + while true; do + response=$(curl -s -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/orgs/gardenlinux/packages/container/gardenlinux-ccloud/versions?per_page=$page_size&page=$page") + + page_ids=$(echo "$response" | jq -r --arg prefix "${UPLOAD_VERSION}" ' + .[] | select(.metadata.container.tags[]? | test("^" + $prefix + "(-.*)?$")) | .id + ') + if [ -n "$page_ids" ]; then + all_version_ids="$all_version_ids $page_ids" + fi + + page_count=$(echo "$response" | jq '. | length') + if [ "$page_count" -lt "$page_size" ]; then + # Stop if this was the last page + break + fi + + page=$((page + 1)) + done + + if [ -z "$all_version_ids" ]; then + echo "No images found for PR ${PR_NUMBER}" + exit 0 fi + + for version_id in $all_version_ids; do + echo "Deleting version $version_id" + http_code=$(curl -s -w "%{http_code}" -o /dev/null -X DELETE \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/orgs/gardenlinux/packages/container/gardenlinux-ccloud/versions/$version_id") + if [ "$http_code" != "204" ]; then + echo "Failed to delete version $version_id (HTTP $http_code)" + fi + done