From dd2b0e2ca7c901fc5be6c41b085776956cbc7acc Mon Sep 17 00:00:00 2001 From: ryosuke-nakayama Date: Fri, 3 Oct 2025 11:02:38 +0900 Subject: [PATCH 01/25] Add a changelog feature that shows the RPM diff compared to the previous release in the container build workflow --- .github/workflows/build-test-push.yml | 289 +++++++++++++++++++++++++- 1 file changed, 288 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index 31bb7f5..e2403b4 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -87,7 +87,6 @@ env: registries_production: 'docker.io/almalinux, quay.io/almalinuxorg, ghcr.io/almalinux' registries_testing: 'quay.io/almalinuxautobot' - jobs: init-data: name: Set matrix, prepare variables @@ -348,6 +347,181 @@ jobs: && ( test "${{ matrix.image_types }}" != "micro" && rpm -q gpg-pubkey) || true " done + - + name: Detect previous released tag for this image type + if: env.check_update != 0 + id: detect-prev + env: + VMJ: ${{ matrix.version_major }} + IMT: ${{ matrix.image_types }} + VMIN: ${{ env.version_minor }} + DATE: ${{ env.date_stamp }} + run: | + IMAGE_REPO="${IMAGE_NAMES%%,*}" + + # Fetch tag list and pick latest dated tag (-YYYYMMDD + TAG_JSON=$(skopeo list-tags docker://"${IMAGE_REPO}" || echo '{}') + CAND=$(echo "${TAG_JSON}" | jq -r '.Tags // [] | .[]' | grep -E "^${VMJ}${VMIN}-[0-9]{8}$" | sort -r | while read -r T; do + D="${T##*-}" + if [ "$D" -lt "${DATE}" ]; then + echo "$T" + break + fi + done) + + # If none found, treat as first release (no comparison target) + if [ -z "${CAND}" ]; then + echo "prev_image=" >> $GITHUB_ENV + echo "prev_tag=" >> $GITHUB_ENV + echo "prev_found=false" >> $GITHUB_OUTPUT + echo "No previous dated tag found for ${IMAGE_REPO}" >&2 + exit 0 + fi + + echo "prev_image=${IMAGE_REPO}" >> $GITHUB_ENV + echo "prev_tag=${CAND}" >> $GITHUB_ENV + echo "prev_found=true" >> $GITHUB_OUTPUT + echo "Previous: ${IMAGE_REPO}:${CAND}" + + - + name: Generate RPM lists (new vs previous) per platform + if: env.check_update != 0 + id: rpm-lists + env: + IMG_NEW_DIGEST: ${{ steps.build-images.outputs.digest }} + IMG_PREV: ${{ env.prev_image }} + TAG_PREV: ${{ env.prev_tag }} + PLATFORMS: ${{ env.platforms }} + VMJ: ${{ matrix.version_major }} + IMT: ${{ matrix.image_types }} + run: | + set -euo pipefail + mkdir -p rpm_lists/${VMJ}/${IMT} + + list_rpms() { + local ref="$1" plat="$2" outfile="$3" + # Save both package name and full version + docker run --platform="${plat}" --rm "${ref}" /bin/bash -c \ + "rpm -qa --queryformat '%{NAME}\t%{VERSION}-%{RELEASE}\n' | sort" \ + > "${outfile}" + # Also create a names-only list + cut -f1 "${outfile}" | sort -u > "${outfile%.txt}.names" + } + + for p in ${PLATFORMS//,/ }; do + plat=$(echo "$p" | xargs) + + # New image: refer by digest + list_rpms "${IMG_NEW_DIGEST}" "${plat}" "rpm_lists/${VMJ}/${IMT}/new_${plat//\//_}.txt" + + # Old image (if present): : + if [ -n "${IMG_PREV:-}" ] && [ -n "${TAG_PREV:-}" ]; then + docker pull --platform "${plat}" "${IMG_PREV}:${TAG_PREV}" >/dev/null 2>&1 || true + list_rpms "${IMG_PREV}:${TAG_PREV}" "${plat}" "rpm_lists/${VMJ}/${IMT}/old_${plat//\//_}.txt" || \ + : # Continue even if platform not provided previously + fi + done + + - + name: Diff RPM sets and build Markdown changelog + if: env.check_update != 0 + id: rpm-diff + env: + PLATFORMS: ${{ env.platforms }} + VMJ: ${{ matrix.version_major }} + IMT: ${{ matrix.image_types }} + PREV_TAG: ${{ env.prev_tag }} + DTS: ${{ needs.init-data.outputs.date_time_stamp }} + run: | + set -euo pipefail + out="rpm_diff_${VMJ}_${IMT}.md" + echo "**AlmaLinux ${VMJ} ${IMT}** RPM changelog (build \`${DTS}\`)" > "${out}" + if [ -n "${PREV_TAG}" ]; then + echo "" >> "${out}" + echo "- Compared to previous: \`${PREV_TAG}\`" >> "${out}" + else + echo "" >> "${out}" + echo "- No previous dated tag found. Treating all as **Added**." >> "${out}" + fi + echo "" >> "${out}" + + any_section=false + for p in ${PLATFORMS//,/ }; do + plat=$(echo "$p" | xargs) + key="${plat//\//_}" + new="rpm_lists/${VMJ}/${IMT}/new_${key}.txt" + old="rpm_lists/${VMJ}/${IMT}/old_${key}.txt" + + [ ! -f "${new}" ] && continue + + echo "### Platform: \`${plat}\`" >> "${out}" + + if [ -f "${old}" ]; then + # Added / Removed sets based on names + comm -13 "${old%.txt}.names" "${new%.txt}.names" > added.tmp || true + comm -23 "${old%.txt}.names" "${new%.txt}.names" > removed.tmp || true + + # Updated: same name but different versions + join -j1 -t $'\t' -o 0,1.2,2.2 \ + <(join -t $'\t' -1 1 -2 1 <(cut -f1,2 "${old}" | sort) <(cut -f1,2 "${new}" | sort -k1,1) | sort -k1,1) \ + <(cut -f1,2 "${new}" | sort -k1,1) >/dev/null 2>&1 || true + + # Recompute diffs and extract version changes + updated_tmp=$(mktemp) + awk -F'\t' 'NR==FNR{old[$1]=$2;next}{ if($1 in old && old[$1]!=$2){ print $1"\t"old[$1]" -> "$2 } }' \ + "${old}" "${new}" > "${updated_tmp}" + + addc=$(wc -l < added.tmp | tr -d ' ') + updc=$(wc -l < "${updated_tmp}" | tr -d ' ') + delc=$(wc -l < removed.tmp | tr -d ' ') + + echo "" >> "${out}" + echo "**Added (${addc})**" >> "${out}" + echo "" >> "${out}" + [ "$addc" -gt 0 ] && awk '{print "- " $0}' added.tmp >> "${out}" || echo "- (none)" >> "${out}" + + echo "" >> "${out}" + echo "**Updated (${updc})**" >> "${out}" + echo "" >> "${out}" + if [ "$updc" -gt 0 ]; then + awk -F'\t' '{printf "- %s: %s\n",$1,$2}' "${updated_tmp}" >> "${out}" + else + echo "- (none)" >> "${out}" + fi + + echo "" >> "${out}" + echo "**Removed (${delc})**" >> "${out}" + echo "" >> "${out}" + [ "$delc" -gt 0 ] && awk '{print "- " $0}' removed.tmp >> "${out}" || echo "- (none)" >> "${out}" + echo "" >> "${out}" + + any_section=true + rm -f added.tmp removed.tmp "${updated_tmp}" + else + # No previous list: treat all as Added + echo "" >> "${out}" + echo "**Added**" >> "${out}" + echo "" >> "${out}" + cut -f1 "${new}" | awk '{print "- " $0}' >> "${out}" + echo "" >> "${out}" + any_section=true + fi + done + + $any_section || echo "_No RPM changes detected (no comparable platforms found)._" >> "${out}" + + # Move artifacts into a readable path + mkdir -p rpm_diff_artifacts/${VMJ} + mv "${out}" "rpm_diff_artifacts/${VMJ}/rpm_diff_${VMJ}_${IMT}.md" + + - + name: Upload RPM diff artifacts + if: env.check_update != 0 + uses: actions/upload-artifact@v4 + with: + name: rpm-diff-${{ matrix.version_major }}-${{ matrix.image_types }} + path: rpm_diff_artifacts/${{ matrix.version_major }}/ + - name: Push images to Client Library if: env.check_update != 0 @@ -524,6 +698,119 @@ jobs: message: "AlmaLinux ${{ matrix.version_major }} ${{ matrix.image_types }} - ${{ env.date_stamp }} ${{ env.time_stamp }} (generated on ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})." push: true + publish-rpm-diff: + name: Publish RPM diff to GitHub Release + runs-on: ubuntu-24.04 + needs: [init-data, build-test-push] + steps: + - + name: Download all RPM diff artifacts (if any) + uses: actions/download-artifact@v4 + with: + pattern: rpm-diff-* + merge-multiple: true + path: _rpm_diff_all + continue-on-error: true + + - + name: Check artifacts existence + id: chk + run: | + set -euo pipefail + # Recursively search to find any rpm_diff_*.md anywhere + if ! find _rpm_diff_all -type f -name 'rpm_diff_*.md' | grep -q .; then + echo "found=false" >> "$GITHUB_OUTPUT" + echo "No RPM diff artifacts found. Skipping release." + exit 0 + fi + echo "found=true" >> "$GITHUB_OUTPUT" + + - + name: Compose release notes (group by version & type) + if: steps.chk.outputs.found == 'true' + id: notes + run: | + set -euo pipefail + + DATE_STAMP='${{ needs.init-data.outputs.date_stamp }}' + TIME_STAMP='${{ needs.init-data.outputs.time_stamp }}' + PROD='${{ needs.init-data.outputs.production }}' + + header="# AlmaLinux Container Images – RPM diffs (${DATE_STAMP} ${TIME_STAMP} UTC)\n" + [ "${PROD}" = "true" ] && header="${header}\n_Release type: **Production**_\n" || header="${header}\n_Release type: **Testing / Pre-release**_\n" + printf "%b\n" "${header}" > RELEASE_NOTES.md + + # Collect all md files regardless of where they were unpacked + mapfile -t FILES < <(find _rpm_diff_all -type f -name 'rpm_diff_*.md' | sort) + + # Build a map: vmj::type -> file (robustly extracted from file name) + declare -A MAP + declare -A TYPES_OF + for f in "${FILES[@]}"; do + base=$(basename "$f") + if [[ "$base" =~ ^rpm_diff_([0-9]+|10-kitten)_([a-z0-9_-]+)\.md$ ]]; then + vmj="${BASH_REMATCH[1]}" + itype="${BASH_REMATCH[2]}" + key="${vmj}::${itype}" + MAP["$key"]="$f" + TYPES_OF["$vmj"]+="$itype " + fi + done + + # Sort versions ascending; output types in a fixed order for readability + mapfile -t VMJS < <(printf "%s\n" "${!TYPES_OF[@]}" | sort -V) + ORDER=(default minimal micro base init toolbox) + + for vmj in "${VMJS[@]}"; do + echo -e "\n## AlmaLinux ${vmj}\n" >> RELEASE_NOTES.md + + read -r -a tlist <<< "${TYPES_OF[$vmj]}" + + # Output known types in preferred order + for want in "${ORDER[@]}"; do + for itype in "${tlist[@]}"; do + [ "$itype" != "$want" ] && continue + key="${vmj}::${itype}" + f="${MAP[$key]}" + [ -z "$f" ] && continue + echo -e "\n### Image type: \`${itype}\`\n" >> RELEASE_NOTES.md + # Inline the full changelog (Added/Updated/Removed) for each flavor + cat "$f" >> RELEASE_NOTES.md + done + done + # Any unknown types go last + for itype in "${tlist[@]}"; do + if [[ ! " ${ORDER[*]} " =~ " ${itype} " ]]; then + key="${vmj}::${itype}" + f="${MAP[$key]}" + [ -z "$f" ] && continue + echo -e "\n### Image type: \`${itype}\`\n" >> RELEASE_NOTES.md + cat "$f" >> RELEASE_NOTES.md + fi + done + done + + # Also archive the original markdown files for attachment + tar -czf rpm-diff-artifacts-${DATE_STAMP}.tar.gz -C _rpm_diff_all . + + echo "tag=v${DATE_STAMP}" >> "$GITHUB_OUTPUT" + echo "name=AlmaLinux Container RPM diffs (${DATE_STAMP})" >> "$GITHUB_OUTPUT" + + - + name: Create/Update GitHub Release + if: steps.chk.outputs.found == 'true' + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.notes.outputs.tag }} + name: ${{ steps.notes.outputs.name }} + body_path: RELEASE_NOTES.md + draft: false + prerelease: ${{ needs.init-data.outputs.production != 'true' }} + files: | + rpm-diff-artifacts-${{ needs.init-data.outputs.date_stamp }}.tar.gz + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + notify-mattermost: name: Mattermost notification runs-on: ubuntu-24.04 From b63a7742d0070cc5d40f208ab281f0b7dd49f8f4 Mon Sep 17 00:00:00 2001 From: Ryosuke Nakayama <78244973+unixtech-06@users.noreply.github.com> Date: Mon, 6 Oct 2025 16:17:14 +0900 Subject: [PATCH 02/25] =?UTF-8?q?fix(diff):=20change=20arrow=20in=20awk=20?= =?UTF-8?q?output=20from=20'->'=20to=20'=E2=86=92'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alex Iribarren --- .github/workflows/build-test-push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index e2403b4..d523997 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -468,7 +468,7 @@ jobs: # Recompute diffs and extract version changes updated_tmp=$(mktemp) - awk -F'\t' 'NR==FNR{old[$1]=$2;next}{ if($1 in old && old[$1]!=$2){ print $1"\t"old[$1]" -> "$2 } }' \ + awk -F'\t' 'NR==FNR{old[$1]=$2;next}{ if($1 in old && old[$1]!=$2){ print $1"\t"old[$1]" → "$2 } }' \ "${old}" "${new}" > "${updated_tmp}" addc=$(wc -l < added.tmp | tr -d ' ') From b9bca8d40a9c0978b471a105b804d7f330d31a93 Mon Sep 17 00:00:00 2001 From: ryosuke-nakayama Date: Tue, 7 Oct 2025 09:45:06 +0900 Subject: [PATCH 03/25] =?UTF-8?q?ci:=20simplify=20previous=20image=20detec?= =?UTF-8?q?tion=20=E2=80=94=20use=20=20tag=20directly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-test-push.yml | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index d523997..6386adc 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -357,25 +357,13 @@ jobs: VMIN: ${{ env.version_minor }} DATE: ${{ env.date_stamp }} run: | + set -euo pipefail IMAGE_REPO="${IMAGE_NAMES%%,*}" - - # Fetch tag list and pick latest dated tag (-YYYYMMDD - TAG_JSON=$(skopeo list-tags docker://"${IMAGE_REPO}" || echo '{}') - CAND=$(echo "${TAG_JSON}" | jq -r '.Tags // [] | .[]' | grep -E "^${VMJ}${VMIN}-[0-9]{8}$" | sort -r | while read -r T; do - D="${T##*-}" - if [ "$D" -lt "${DATE}" ]; then - echo "$T" - break - fi - done) - - # If none found, treat as first release (no comparison target) - if [ -z "${CAND}" ]; then - echo "prev_image=" >> $GITHUB_ENV - echo "prev_tag=" >> $GITHUB_ENV - echo "prev_found=false" >> $GITHUB_OUTPUT - echo "No previous dated tag found for ${IMAGE_REPO}" >&2 - exit 0 + PREV_TAG="${VMJ}" + echo "Checking ${IMAGE_REPO}:${PREV_TAG}" + if ! skopeo inspect docker://"${IMAGE_REPO}:${PREV_TAG}" >/dev/null 2>&1; then + echo "::error ::Tag not found: ${IMAGE_REPO}:${PREV_TAG}" + exit 1 fi echo "prev_image=${IMAGE_REPO}" >> $GITHUB_ENV From c59a1bc7792d6486f21ec471145dc67a3c79085a Mon Sep 17 00:00:00 2001 From: ryosuke-nakayama Date: Tue, 7 Oct 2025 11:46:58 +0900 Subject: [PATCH 04/25] ci: skip empty RPM diff sections in changelog --- .github/workflows/build-test-push.yml | 51 ++++++++++++++++++--------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index 6386adc..8e187b4 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -463,27 +463,46 @@ jobs: updc=$(wc -l < "${updated_tmp}" | tr -d ' ') delc=$(wc -l < removed.tmp | tr -d ' ') - echo "" >> "${out}" - echo "**Added (${addc})**" >> "${out}" - echo "" >> "${out}" - [ "$addc" -gt 0 ] && awk '{print "- " $0}' added.tmp >> "${out}" || echo "- (none)" >> "${out}" + printed=false + + # Added + if [ "$addc" -gt 0 ]; then + { + echo "" + echo "**Added (${addc})**" + echo "" + awk '{print "- " $0}' added.tmp + echo "" + } >> "${out}" + printed=true + fi - echo "" >> "${out}" - echo "**Updated (${updc})**" >> "${out}" - echo "" >> "${out}" + # Updated if [ "$updc" -gt 0 ]; then - awk -F'\t' '{printf "- %s: %s\n",$1,$2}' "${updated_tmp}" >> "${out}" - else - echo "- (none)" >> "${out}" + { + echo "" + echo "**Updated (${updc})**" + echo "" + awk -F'\t' '{printf "- %s: %s\n",$1,$2}' "${updated_tmp}" + echo "" + } >> "${out}" + printed=true fi - echo "" >> "${out}" - echo "**Removed (${delc})**" >> "${out}" - echo "" >> "${out}" - [ "$delc" -gt 0 ] && awk '{print "- " $0}' removed.tmp >> "${out}" || echo "- (none)" >> "${out}" - echo "" >> "${out}" + # Removed + if [ "$delc" -gt 0 ]; then + { + echo "" + echo "**Removed (${delc})**" + echo "" + awk '{print "- " $0}' removed.tmp + echo "" + } >> "${out}" + printed=true + fi + + [ "$printed" = true ] && any_section=true - any_section=true rm -f added.tmp removed.tmp "${updated_tmp}" else # No previous list: treat all as Added From 5ebb806c16d0c6337ae894c3089775f8233c53bd Mon Sep 17 00:00:00 2001 From: ryosuke-nakayama Date: Tue, 7 Oct 2025 11:51:44 +0900 Subject: [PATCH 05/25] =?UTF-8?q?ci:=20simplify=20previous=20image=20detec?= =?UTF-8?q?tion=20=E2=80=94=20use=20tag=20directly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-test-push.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index 8e187b4..b98d3db 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -366,10 +366,10 @@ jobs: exit 1 fi - echo "prev_image=${IMAGE_REPO}" >> $GITHUB_ENV - echo "prev_tag=${CAND}" >> $GITHUB_ENV - echo "prev_found=true" >> $GITHUB_OUTPUT - echo "Previous: ${IMAGE_REPO}:${CAND}" + echo "prev_image=${IMAGE_REPO}" >> "$GITHUB_ENV" + echo "prev_tag=${PREV_TAG}" >> "$GITHUB_ENV" + echo "prev_found=true" >> "$GITHUB_OUTPUT" + echo "Previous: ${IMAGE_REPO}:${PREV_TAG}" - name: Generate RPM lists (new vs previous) per platform From 8c0ac51ac5f24443a5f6f2985951ca370190a0c7 Mon Sep 17 00:00:00 2001 From: ryosuke-nakayama Date: Tue, 7 Oct 2025 15:39:48 +0900 Subject: [PATCH 06/25] ci: wrap per-platform RPM diff in collapsible
sections --- .github/workflows/build-test-push.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index b98d3db..183d41e 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -442,7 +442,14 @@ jobs: [ ! -f "${new}" ] && continue - echo "### Platform: \`${plat}\`" >> "${out}" + + # Collapse each platform's RPM diff into a
section + { + echo "" + echo "
" + echo "Platform: \`${plat}\`" + echo "" + } >> "${out}" if [ -f "${old}" ]; then # Added / Removed sets based on names @@ -512,6 +519,12 @@ jobs: cut -f1 "${new}" | awk '{print "- " $0}' >> "${out}" echo "" >> "${out}" any_section=true + + { + echo "" + echo "
" + echo "" + } >> "${out}" fi done From d299edb244653119dbc361a6492327e27e182a38 Mon Sep 17 00:00:00 2001 From: ryosuke-nakayama Date: Wed, 8 Oct 2025 09:44:42 +0900 Subject: [PATCH 07/25] ci: allow initial run when no previous tag exists --- .github/workflows/build-test-push.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index 183d41e..bf1aa36 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -353,24 +353,24 @@ jobs: id: detect-prev env: VMJ: ${{ matrix.version_major }} - IMT: ${{ matrix.image_types }} - VMIN: ${{ env.version_minor }} - DATE: ${{ env.date_stamp }} run: | set -euo pipefail IMAGE_REPO="${IMAGE_NAMES%%,*}" PREV_TAG="${VMJ}" + echo "Checking ${IMAGE_REPO}:${PREV_TAG}" - if ! skopeo inspect docker://"${IMAGE_REPO}:${PREV_TAG}" >/dev/null 2>&1; then - echo "::error ::Tag not found: ${IMAGE_REPO}:${PREV_TAG}" - exit 1 + if skopeo inspect docker://"${IMAGE_REPO}:${PREV_TAG}" >/dev/null 2>&1; then + echo "prev_image=${IMAGE_REPO}" >> "$GITHUB_ENV" + echo "prev_tag=${PREV_TAG}" >> "$GITHUB_ENV" + echo "prev_found=true" >> "$GITHUB_OUTPUT" + echo "Previous: ${IMAGE_REPO}:${PREV_TAG}" + else + echo "No previous tag found for ${IMAGE_REPO}:${PREV_TAG} — treating as first release." + echo "prev_image=" >> "$GITHUB_ENV" + echo "prev_tag=" >> "$GITHUB_ENV" + echo "prev_found=false" >> "$GITHUB_OUTPUT" fi - echo "prev_image=${IMAGE_REPO}" >> "$GITHUB_ENV" - echo "prev_tag=${PREV_TAG}" >> "$GITHUB_ENV" - echo "prev_found=true" >> "$GITHUB_OUTPUT" - echo "Previous: ${IMAGE_REPO}:${PREV_TAG}" - - name: Generate RPM lists (new vs previous) per platform if: env.check_update != 0 From 00742102c1a3bbde85eae79d61cd68ea844e7259 Mon Sep 17 00:00:00 2001 From: ryosuke-nakayama Date: Wed, 8 Oct 2025 10:57:44 +0900 Subject: [PATCH 08/25] ci: switch
blocks to printf and always close after platform loop ci: switch
blocks to printf and always close after platform loop --- .github/workflows/build-test-push.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index bf1aa36..c0a8791 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -444,12 +444,7 @@ jobs: # Collapse each platform's RPM diff into a
section - { - echo "" - echo "
" - echo "Platform: \`${plat}\`" - echo "" - } >> "${out}" + printf "\n
\nPlatform: \`%s\`\n\n" "$plat" >> "$ if [ -f "${old}" ]; then # Added / Removed sets based on names @@ -520,12 +515,9 @@ jobs: echo "" >> "${out}" any_section=true - { - echo "" - echo "
" - echo "" - } >> "${out}" fi + # Close the
section for this platform + printf "\n
\n" >> "${out}" done $any_section || echo "_No RPM changes detected (no comparable platforms found)._" >> "${out}" From 4872506c8b455bca088de7d029e930c645561c99 Mon Sep 17 00:00:00 2001 From: ryosuke-nakayama Date: Thu, 9 Oct 2025 11:42:17 +0900 Subject: [PATCH 09/25] feat: embed rechunker-style RPM metadata as compressed label in container images feat: embed rechunker-style RPM metadata as compressed label in container images --- .github/workflows/build-test-push.yml | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index c0a8791..4d50a6d 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -526,6 +526,67 @@ jobs: mkdir -p rpm_diff_artifacts/${VMJ} mv "${out}" "rpm_diff_artifacts/${VMJ}/rpm_diff_${VMJ}_${IMT}.md" + - + name: Build rechunk-style label payload (gzip+base64) + if: env.check_update != 0 + id: build-rechunk-label + env: + PLATFORMS: ${{ env.platforms }} + VMJ: ${{ matrix.version_major }} + IMT: ${{ matrix.image_types }} + PREV_TAG: ${{ env.prev_tag }} + DTS: ${{ needs.init-data.outputs.date_time_stamp }} + LABEL_KEY: dev.hhd.rechunk.info + run: | + set -euo pipefail + base="rpm_lists/${VMJ}/${IMT}" + + # Create a base JSON + jq -n \ + --arg vmj "${VMJ}" \ + --arg imt "${IMT}" \ + --arg dts "${DTS}" \ + --arg prev "${PREV_TAG:-}" \ + '{ + version_major: $vmj, + image_type: $imt, + built_at: $dts, + prev_tag: ( $prev | select(length>0) // null ), + platforms: {} + }' > rechunk_${VMJ}_${IMT}.json + + # Add per-platform package lists and SHA256 + for p in ${PLATFORMS//,/ }; do + plat=$(echo "$p" | xargs) + key="${plat//\//_}" + new="${base}/new_${key}.txt" + [ -f "${new}" ] || continue + + sha=$(sha256sum "${new}" | awk '{print $1}') + # Convert TSV "nameversion-release" into JSON array + jq -Rs ' + split("\n") + | map(select(length>0) | split("\t") | {n: .[0], v: .[1]}) + ' "${new}" > pkgs_${key}.json + + tmp=$(mktemp) + jq --arg plat "${plat}" --arg sha "${sha}" --slurpfile pkgs pkgs_${key}.json \ + '.platforms[$plat] = { pkgs_sha256: $sha, pkgs: $pkgs[0] }' \ + "rechunk_${VMJ}_${IMT}.json" > "${tmp}" + mv "${tmp}" "rechunk_${VMJ}_${IMT}.json" + rm -f pkgs_${key}.json + done + + # Compress and encode JSON into a single-line lable payload + RCHUNK_INFO_LABEL=$(gzip -c "rechunk_${VMJ}_${IMT}.json" | base64 -w0) + echo "RCHUNK_INFO_LABEL=${RCHUNK_INFO_LABEL}" >> "$GITHUB_ENV" + echo "LABEL_KEY=${LABEL_KEY}" >> "$GITHUB_ENV" + + # Debug: show summary of the generated JSON without full package lists + echo "[Debug] schema/preview:" + jq 'del(.platforms[]?.pkgs) | .platforms |= with_entries(.value |= {pkgs_sha256:.pkgs_sha256, pkgs_count:0})' "rechunk_${VMJ}_${IMT}.json" || true + + - name: Upload RPM diff artifacts if: env.check_update != 0 @@ -546,6 +607,8 @@ jobs: platforms: ${{ env.platforms }} push: true tags: ${{ steps.meta.outputs.tags }} + labels: | + ${{ env.LABLE_KEY }}=${{ env.RCHUNK_INFO_LABEL }} - name: Prepare Mattermost message From 6ae0febc4baeb92c6a94d3075f19be39ecbf54b2 Mon Sep 17 00:00:00 2001 From: ryosuke-nakayama Date: Thu, 9 Oct 2025 20:03:29 +0900 Subject: [PATCH 10/25] ci: change LABEL_KEY to org.almalinux.sbom (replace dev.hhd.rechunk.info) --- .github/workflows/build-test-push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index 4d50a6d..317ceaf 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -536,7 +536,7 @@ jobs: IMT: ${{ matrix.image_types }} PREV_TAG: ${{ env.prev_tag }} DTS: ${{ needs.init-data.outputs.date_time_stamp }} - LABEL_KEY: dev.hhd.rechunk.info + LABEL_KEY: org.almalinux.sbom run: | set -euo pipefail base="rpm_lists/${VMJ}/${IMT}" From e9967c8dbfb91e82361205490363c9ab4006e53b Mon Sep 17 00:00:00 2001 From: ryosuke-nakayama Date: Thu, 9 Oct 2025 20:05:11 +0900 Subject: [PATCH 11/25] fix: correct label-related typos --- .github/workflows/build-test-push.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index 317ceaf..d90e7e0 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -577,7 +577,7 @@ jobs: rm -f pkgs_${key}.json done - # Compress and encode JSON into a single-line lable payload + # Compress and encode JSON into a single-line label payload RCHUNK_INFO_LABEL=$(gzip -c "rechunk_${VMJ}_${IMT}.json" | base64 -w0) echo "RCHUNK_INFO_LABEL=${RCHUNK_INFO_LABEL}" >> "$GITHUB_ENV" echo "LABEL_KEY=${LABEL_KEY}" >> "$GITHUB_ENV" @@ -608,7 +608,7 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: | - ${{ env.LABLE_KEY }}=${{ env.RCHUNK_INFO_LABEL }} + ${{ env.LABEL_KEY }}=${{ env.RCHUNK_INFO_LABEL }} - name: Prepare Mattermost message From ed58b53a4d78fc157d90a53c22b9eeb40ffa073a Mon Sep 17 00:00:00 2001 From: ryosuke-nakayama Date: Thu, 9 Oct 2025 21:00:42 +0900 Subject: [PATCH 12/25] ci: read previous RPM list from image label via crane instead of running old image ci: read previous RPM list from image label via crane instead of running old image ci: read previous RPM list from image label via crane instead of running old image ci: read previous RPM list from image label via crane instead of running old image ci: read previous RPM list from image label via crane instead of running old image --- .github/workflows/build-test-push.yml | 29 +++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index d90e7e0..483d7bd 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -371,6 +371,17 @@ jobs: echo "prev_found=false" >> "$GITHUB_OUTPUT" fi + - + name: Install crane + if: env.check_update != 0 + run: | + set -euo pipefail + ver=v0.20.2 + curl -sSL -o /tmp/crane.tgz \ + https://github.com/google/go-containerregistry/releases/download/${ver}/crane_Linux_x86_64.tar.gz + sudo tar -C /usr/local/bin -xzf /tmp/crane.tgz crane + crane version + - name: Generate RPM lists (new vs previous) per platform if: env.check_update != 0 @@ -402,11 +413,21 @@ jobs: # New image: refer by digest list_rpms "${IMG_NEW_DIGEST}" "${plat}" "rpm_lists/${VMJ}/${IMT}/new_${plat//\//_}.txt" - # Old image (if present): : + # Old image (if present): extract package list from label instead of running it if [ -n "${IMG_PREV:-}" ] && [ -n "${TAG_PREV:-}" ]; then - docker pull --platform "${plat}" "${IMG_PREV}:${TAG_PREV}" >/dev/null 2>&1 || true - list_rpms "${IMG_PREV}:${TAG_PREV}" "${plat}" "rpm_lists/${VMJ}/${IMT}/old_${plat//\//_}.txt" || \ - : # Continue even if platform not provided previously + echo "Fetching previous RPM list label..." + label_json=$(crane config "${IMG_PREV}:${TAG_PREV}" \ + | jq -r --arg key "${LABEL_KEY}" '.config.Labels[$key] // empty') + if [ -n "${label_json}" ]; then + val=$(jq -r --arg plat "${plat}" --argjson data "${label_json}" '($data[$plat]) // empty' "rpm_lists/${VMJ}/${IMT}/old_${plat//\//_}.txt" + else + echo "No previous entry for ${plat} in label ${LABEL_KEY}" + fi + else + echo "No previous label found for ${plat}" + fi fi done From 39a73ba85f1070cbff74b1574565bb11ab69b9c9 Mon Sep 17 00:00:00 2001 From: ryosuke-nakayama Date: Thu, 9 Oct 2025 21:30:27 +0900 Subject: [PATCH 13/25] refactor: store JSON label directly instead of gzip+base64 --- .github/workflows/build-test-push.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index 483d7bd..926f970 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -598,8 +598,8 @@ jobs: rm -f pkgs_${key}.json done - # Compress and encode JSON into a single-line label payload - RCHUNK_INFO_LABEL=$(gzip -c "rechunk_${VMJ}_${IMT}.json" | base64 -w0) + # Store the JSON directly in the label payload (no compression) + RCHUNK_INFO_LABEL=$(cat "rechunk_${VMJ}_${IMT}.json" | jq -c .) echo "RCHUNK_INFO_LABEL=${RCHUNK_INFO_LABEL}" >> "$GITHUB_ENV" echo "LABEL_KEY=${LABEL_KEY}" >> "$GITHUB_ENV" From 39bb7496668e22dc6976ee5136c7075cb9e1f4af Mon Sep 17 00:00:00 2001 From: ryosuke-nakayama Date: Fri, 10 Oct 2025 15:55:00 +0900 Subject: [PATCH 14/25] ci(registry,label,changelog): tweak testing registry, label fetch, and RPM diff output --- .github/workflows/build-test-push.yml | 56 +++++++++++---------------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index 926f970..9a287e6 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -376,9 +376,9 @@ jobs: if: env.check_update != 0 run: | set -euo pipefail - ver=v0.20.2 + ver=v0.20.6 curl -sSL -o /tmp/crane.tgz \ - https://github.com/google/go-containerregistry/releases/download/${ver}/crane_Linux_x86_64.tar.gz + https://github.com/google/go-containerregistry/releases/download/${ver}/go-containerregistry_Linux_x86_64.tar.gz sudo tar -C /usr/local/bin -xzf /tmp/crane.tgz crane crane version @@ -393,6 +393,7 @@ jobs: PLATFORMS: ${{ env.platforms }} VMJ: ${{ matrix.version_major }} IMT: ${{ matrix.image_types }} + LABEL_KEY: org.almalinux.sbom run: | set -euo pipefail mkdir -p rpm_lists/${VMJ}/${IMT} @@ -417,7 +418,7 @@ jobs: if [ -n "${IMG_PREV:-}" ] && [ -n "${TAG_PREV:-}" ]; then echo "Fetching previous RPM list label..." label_json=$(crane config "${IMG_PREV}:${TAG_PREV}" \ - | jq -r --arg key "${LABEL_KEY}" '.config.Labels[$key] // empty') + | jq -r --arg key "${LABEL_KEY:-org.almalinux.sbom}" '.config.Labels[$key] // empty') if [ -n "${label_json}" ]; then val=$(jq -r --arg plat "${plat}" --argjson data "${label_json}" '($data[$plat]) // empty' "${out}" if [ -n "${PREV_TAG}" ]; then - echo "" >> "${out}" - echo "- Compared to previous: \`${PREV_TAG}\`" >> "${out}" + echo "Changes since last version (${PREV_TAG}):" > "${out}" else - echo "" >> "${out}" - echo "- No previous dated tag found. Treating all as **Added**." >> "${out}" + echo "Changes since last version: (first release)" > "${out}" fi echo "" >> "${out}" @@ -463,20 +461,16 @@ jobs: [ ! -f "${new}" ] && continue - - # Collapse each platform's RPM diff into a
section - printf "\n
\nPlatform: \`%s\`\n\n" "$plat" >> "$ + { + echo "
" + printf "Platform: \`%s\`\n\n" "$plat" + } >> "${out}" if [ -f "${old}" ]; then # Added / Removed sets based on names comm -13 "${old%.txt}.names" "${new%.txt}.names" > added.tmp || true comm -23 "${old%.txt}.names" "${new%.txt}.names" > removed.tmp || true - # Updated: same name but different versions - join -j1 -t $'\t' -o 0,1.2,2.2 \ - <(join -t $'\t' -1 1 -2 1 <(cut -f1,2 "${old}" | sort) <(cut -f1,2 "${new}" | sort -k1,1) | sort -k1,1) \ - <(cut -f1,2 "${new}" | sort -k1,1) >/dev/null 2>&1 || true - # Recompute diffs and extract version changes updated_tmp=$(mktemp) awk -F'\t' 'NR==FNR{old[$1]=$2;next}{ if($1 in old && old[$1]!=$2){ print $1"\t"old[$1]" → "$2 } }' \ @@ -486,36 +480,32 @@ jobs: updc=$(wc -l < "${updated_tmp}" | tr -d ' ') delc=$(wc -l < removed.tmp | tr -d ' ') - printed=false + printed=false + echo "**Package Changes**" >> "${out}" + echo "" >> "${out}" - # Added - if [ "$addc" -gt 0 ]; then + if [ "$updc" -gt 0 ]; then { + echo "**Updated (${updc})**" echo "" - echo "**Added (${addc})**" - echo "" - awk '{print "- " $0}' added.tmp + awk -F'\t' '{printf "- %s: %s\n",$1,$2}' "${updated_tmp}" echo "" } >> "${out}" printed=true fi - # Updated - if [ "$updc" -gt 0 ]; then + if [ "$addc" -gt 0 ]; then { + echo "**Added (${addc})**" echo "" - echo "**Updated (${updc})**" - echo "" - awk -F'\t' '{printf "- %s: %s\n",$1,$2}' "${updated_tmp}" + awk -v OFS='\t' 'NR==FNR{ver[$1]=$2;next}{n=$0; if(n!=""){ printf "- %s: %s\n", n, ver[n] }}' "${new}" added.tmp echo "" } >> "${out}" printed=true fi - # Removed if [ "$delc" -gt 0 ]; then { - echo "" echo "**Removed (${delc})**" echo "" awk '{print "- " $0}' removed.tmp @@ -528,11 +518,11 @@ jobs: rm -f added.tmp removed.tmp "${updated_tmp}" else - # No previous list: treat all as Added + echo "**Package Changes**" >> "${out}" echo "" >> "${out}" - echo "**Added**" >> "${out}" + echo "_No previous version — initial publication (showing all as Added)_" >> "${out}" echo "" >> "${out}" - cut -f1 "${new}" | awk '{print "- " $0}' >> "${out}" + awk -F'\t' '{printf "- %s: %s\n",$1,$2}' "${new}" >> "${out}" echo "" >> "${out}" any_section=true @@ -541,7 +531,7 @@ jobs: printf "\n
\n" >> "${out}" done - $any_section || echo "_No RPM changes detected (no comparable platforms found)._" >> "${out}" + $any_section || echo "_No package changes detected._" >> "${out}" # Move artifacts into a readable path mkdir -p rpm_diff_artifacts/${VMJ} From 842ee343c27795b3f6db6be0c55cb22eb43dc361 Mon Sep 17 00:00:00 2001 From: unixtech Date: Thu, 12 Feb 2026 01:26:01 +0900 Subject: [PATCH 15/25] feat: replace RPM-based changelog with SBOM-based generation --- .github/workflows/build-test-push.yml | 536 +++++++++++++++----------- 1 file changed, 311 insertions(+), 225 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index 313857b..367bd99 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -2,71 +2,71 @@ name: Build, test and push to the Client Library on: workflow_dispatch: - inputs: - production: - description: | - 'Push to production registries' - 'not checked - to testing' - required: true - type: boolean - default: false - - notify_mattermost: - description: 'Send notification to Mattermost' - required: true - type: boolean - default: false - - version_major: - description: 'AlmaLinux major version' - required: true - default: '10' - type: choice - options: - - 10-kitten - - 10 - - 9 - - 8 - - type_default: - description: 'default' - required: true - type: boolean - default: true - - type_minimal: - description: 'minimal' - required: true - type: boolean - default: true - - type_micro: - description: 'micro' - required: true - type: boolean - default: true - - type_base: - description: 'base' - required: true - type: boolean - default: true - - type_init: - description: 'init' - required: true - type: boolean - default: true - - type_toolbox: - description: 'toolbox' - required: true - type: boolean - default: true + inputs: + production: + description: | + 'Push to production registries' + 'not checked - to testing' + required: true + type: boolean + default: false + + notify_mattermost: + description: "Send notification to Mattermost" + required: true + type: boolean + default: false + + version_major: + description: "AlmaLinux major version" + required: true + default: "10" + type: choice + options: + - 10-kitten + - 10 + - 9 + - 8 + + type_default: + description: "default" + required: true + type: boolean + default: true + + type_minimal: + description: "minimal" + required: true + type: boolean + default: true + + type_micro: + description: "micro" + required: true + type: boolean + default: true + + type_base: + description: "base" + required: true + type: boolean + default: true + + type_init: + description: "init" + required: true + type: boolean + default: true + + type_toolbox: + description: "toolbox" + required: true + type: boolean + default: true schedule: # run every day at 04:00 UTC - - cron: '00 04 * * *' + - cron: "00 04 * * *" env: # List of versions to build on schedule @@ -81,11 +81,14 @@ env: version_latest: 10 # Platforms list - platforms: 'linux/amd64, linux/ppc64le, linux/s390x, linux/arm64' + platforms: "linux/amd64, linux/ppc64le, linux/s390x, linux/arm64" # Registries lists - registries_production: 'docker.io/almalinux, quay.io/almalinuxorg, ghcr.io/almalinux' - registries_testing: 'quay.io/almalinuxautobot' + registries_production: "docker.io/almalinux, quay.io/almalinuxorg, ghcr.io/almalinux" + registries_testing: "quay.io/ryosuke_666" + + # SBOM tools repository + sbom_tools_repo: "https://github.com/AlmaLinux/cloud-images-sbom-tools.git" jobs: init-data: @@ -142,7 +145,7 @@ jobs: version_major: ${{ fromJSON(needs.init-data.outputs.version_major_matrix) }} image_types: ${{ fromJSON(needs.init-data.outputs.image_types_matrix) }} exclude: - - image_types: 'false' + - image_types: "false" env: date_time_stamp: ${{ needs.init-data.outputs.date_time_stamp }} date_stamp: ${{ needs.init-data.outputs.date_stamp }} @@ -151,8 +154,7 @@ jobs: production: ${{ needs.init-data.outputs.production }} steps: - - - name: Prepare AlmaLinux Minor version number + - name: Prepare AlmaLinux Minor version number run: | case ${{ matrix.version_major }} in 8) @@ -168,8 +170,7 @@ jobs: esac echo "version_minor=${version_minor}" >> $GITHUB_ENV - - - name: Check update + - name: Check update id: check-update run: | # dnf check-update --secseverity=Important @@ -189,28 +190,26 @@ jobs: echo "check_update=${check_update}" >> "$GITHUB_ENV" echo "Exit code: '$check_update'" - - - name: Set platforms and registries + - name: Set platforms and registries if: env.check_update != 0 run: | - # Platforms - platforms="${{ env.platforms }}" - case ${{ matrix.version_major }} in - 8|9) - platforms="linux/386, ${platforms}" ;; - 10*) - platforms="linux/amd64/v2, ${platforms}" ;; - esac - echo "platforms=${platforms}" >> $GITHUB_ENV + # Platforms + platforms="${{ env.platforms }}" + case ${{ matrix.version_major }} in + 8|9) + platforms="linux/386, ${platforms}" ;; + 10*) + platforms="linux/amd64/v2, ${platforms}" ;; + esac + echo "platforms=${platforms}" >> $GITHUB_ENV - # Registries - registries="${{ env.registries_testing }}" - [[ "${{ needs.init-data.outputs.production }}" == "true" ]] && \ - registries="${{ env.registries_production }}" - echo "registries=${registries}" >> $GITHUB_ENV + # Registries + registries="${{ env.registries_testing }}" + [[ "${{ needs.init-data.outputs.production }}" == "true" ]] && \ + registries="${{ env.registries_production }}" + echo "registries=${registries}" >> $GITHUB_ENV - - - name: Generate list of images to use as base name for tags + - name: Generate list of images to use as base name for tags if: env.check_update != 0 run: | # list of registries to push to @@ -239,8 +238,7 @@ jobs: # [Debug] echo $IMAGE_NAMES - - - name: Enable containerd image store, set Docker data-root + - name: Enable containerd image store, set Docker data-root if: env.check_update != 0 run: | # JQ file to switch into containerd image store and set Docker data-root @@ -254,30 +252,31 @@ jobs: echo "[Debug] Docker data-root:" docker info -f '{{ .DockerRootDir }}' - - - name: Checkout ${{ github.repository }}, branch 'main' + - name: Checkout ${{ github.repository }}, branch 'main' if: env.check_update != 0 uses: actions/checkout@v4 - - - name: Checkout ${{ github.repository }}, branch '${{ matrix.version_major }}', path '${{ matrix.version_major }}' + - name: Checkout ${{ github.repository }}, branch '${{ matrix.version_major }}', path '${{ matrix.version_major }}' if: env.check_update != 0 uses: actions/checkout@v4 with: ref: ${{ matrix.version_major }} path: ${{ matrix.version_major }} - - - name: Set up QEMU + - name: Clone SBOM tools + if: env.check_update != 0 + run: | + git clone ${{ env.sbom_tools_repo }} /tmp/sbom-tools + pip install --break-system-packages -r /tmp/sbom-tools/requirements.txt + + - name: Set up QEMU if: env.check_update != 0 uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx + - name: Set up Docker Buildx if: env.check_update != 0 uses: docker/setup-buildx-action@v3 - - - name: Login to Docker.io + - name: Login to Docker.io if: contains(env.registries, 'docker.io') && env.check_update != 0 uses: docker/login-action@v3 with: @@ -285,8 +284,7 @@ jobs: username: ${{ env.production == 'true' && secrets.DOCKERHUB_USERNAME || secrets.TEST_DOCKERHUB_USERNAME }} password: ${{ env.production == 'true' && secrets.DOCKERHUB_TOKEN || secrets.TEST_DOCKERHUB_TOKEN }} - - - name: Login to Quay.io + - name: Login to Quay.io if: contains(env.registries, 'quay.io') && env.check_update != 0 uses: docker/login-action@v3 with: @@ -294,8 +292,7 @@ jobs: username: ${{ env.production == 'true' && secrets.QUAY_IO_USERNAME || secrets.TEST_QUAY_IO_USERNAME }} password: ${{ env.production == 'true' && secrets.QUAY_IO_CLI_PASSWORD || secrets.TEST_QUAY_IO_CLI_PASSWORD }} - - - name: Login to Ghcr.io + - name: Login to Ghcr.io if: contains(env.registries, 'ghcr.io') && env.check_update != 0 uses: docker/login-action@v3 with: @@ -303,8 +300,7 @@ jobs: username: ${{ env.production == 'true' && secrets.GIT_HUB_USERNAME || secrets.TEST_GITHUB_USERNAME }} password: ${{ env.production == 'true' && secrets.GIT_HUB_TOKEN || secrets.TEST_GITHUB_TOKEN }} - - - name: Generate tags and prepare metadata to build and push + - name: Generate tags and prepare metadata to build and push if: env.check_update != 0 id: meta uses: docker/metadata-action@v5 @@ -319,8 +315,7 @@ jobs: type=raw,value=${{ matrix.version_major }}${{ env.version_minor }},enable=true type=raw,value=${{ matrix.version_major }}${{ env.version_minor }}-${{ env.date_stamp }},enable=true - - - name: Build images + - name: Build images if: env.check_update != 0 id: build-images uses: docker/build-push-action@v5 @@ -333,8 +328,7 @@ jobs: load: true tags: ${{ steps.meta.outputs.tags }} - - - name: Test images + - name: Test images if: env.check_update != 0 id: test-images run: | @@ -350,8 +344,7 @@ jobs: && ( test "${{ matrix.image_types }}" != "micro" && rpm -q gpg-pubkey) || true " done - - - name: Detect previous released tag for this image type + - name: Detect previous released tag for this image type if: env.check_update != 0 id: detect-prev env: @@ -374,8 +367,7 @@ jobs: echo "prev_found=false" >> "$GITHUB_OUTPUT" fi - - - name: Install crane + - name: Install crane if: env.check_update != 0 run: | set -euo pipefail @@ -385,12 +377,79 @@ jobs: sudo tar -C /usr/local/bin -xzf /tmp/crane.tgz crane crane version - - - name: Generate RPM lists (new vs previous) per platform + # + # ── SBOM-based changelog generation ─────────────────────────────────── + # + # Instead of raw `rpm -qa` diffing, we now: + # 1. Generate SBOM (SPDX 2.3 JSON) per platform using cloud-images-sbom-tools + # 2. Extract the previous SBOM from the image label (if exists) + # 3. Diff the two SBOMs to produce a Markdown changelog + # + + - name: Generate SBOM per platform (new image) if: env.check_update != 0 - id: rpm-lists + id: sbom-generate env: IMG_NEW_DIGEST: ${{ steps.build-images.outputs.digest }} + PLATFORMS: ${{ env.platforms }} + VMJ: ${{ matrix.version_major }} + IMT: ${{ matrix.image_types }} + DTS: ${{ needs.init-data.outputs.date_time_stamp }} + run: | + set -euo pipefail + mkdir -p sbom_output/${VMJ}/${IMT} + + for p in ${PLATFORMS//,/ }; do + plat=$(echo "$p" | xargs) + key="${plat//\//_}" + + echo "=== Generating SBOM for ${plat} ===" + + # Create a temporary rootfs mount point for sbom_data_collector + rootfs_dir=$(mktemp -d) + + # Export the container filesystem for this platform + container_id=$(docker create --platform="${plat}" "${IMG_NEW_DIGEST}") + docker export "${container_id}" | tar -xf - -C "${rootfs_dir}" 2>/dev/null || true + docker rm "${container_id}" > /dev/null + + # Step 1: Collect metadata using sbom_data_collector.py + metadata_file="sbom_output/${VMJ}/${IMT}/metadata_${key}.json" + python3 /tmp/sbom-tools/sbom_data_collector.py \ + --root "${rootfs_dir}" \ + -o "${metadata_file}" \ + --with-checksums \ + --verbose || { + echo "Warning: sbom_data_collector failed for ${plat}, falling back without checksums" + python3 /tmp/sbom-tools/sbom_data_collector.py \ + --root "${rootfs_dir}" \ + -o "${metadata_file}" \ + --verbose + } + + # Step 2: Generate SPDX SBOM using sbom_generator.py + sbom_file="sbom_output/${VMJ}/${IMT}/sbom_${key}.spdx.json" + image_name="AlmaLinux-${VMJ}-${IMT}-${plat//\//-}" + python3 /tmp/sbom-tools/sbom_generator.py \ + "${image_name}" \ + "${metadata_file}" \ + "${sbom_file}" || { + echo "Error: sbom_generator failed for ${plat}" + # Clean up and continue to next platform + rm -rf "${rootfs_dir}" + continue + } + + echo "[Debug] SBOM generated: ${sbom_file} ($(wc -c < "${sbom_file}") bytes)" + + # Clean up rootfs + rm -rf "${rootfs_dir}" + done + + - name: Fetch previous SBOM from image label (if exists) + if: env.check_update != 0 + id: sbom-fetch-prev + env: IMG_PREV: ${{ env.prev_image }} TAG_PREV: ${{ env.prev_tag }} PLATFORMS: ${{ env.platforms }} @@ -399,46 +458,44 @@ jobs: LABEL_KEY: org.almalinux.sbom run: | set -euo pipefail - mkdir -p rpm_lists/${VMJ}/${IMT} - - list_rpms() { - local ref="$1" plat="$2" outfile="$3" - # Save both package name and full version - docker run --platform="${plat}" --rm "${ref}" /bin/bash -c \ - "rpm -qa --queryformat '%{NAME}\t%{VERSION}-%{RELEASE}\n' | sort" \ - > "${outfile}" - # Also create a names-only list - cut -f1 "${outfile}" | sort -u > "${outfile%.txt}.names" - } + mkdir -p sbom_prev/${VMJ}/${IMT} + + if [ -z "${IMG_PREV:-}" ] || [ -z "${TAG_PREV:-}" ]; then + echo "No previous image found — skipping previous SBOM fetch." + echo "prev_sbom_found=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "Fetching previous SBOM label from ${IMG_PREV}:${TAG_PREV}..." + label_json=$(crane config "${IMG_PREV}:${TAG_PREV}" \ + | jq -r --arg key "${LABEL_KEY}" '.config.Labels[$key] // empty') || true + if [ -z "${label_json}" ]; then + echo "No previous SBOM label found." + echo "prev_sbom_found=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # The label contains per-platform SBOM data; extract each platform's SBOM for p in ${PLATFORMS//,/ }; do plat=$(echo "$p" | xargs) + key="${plat//\//_}" - # New image: refer by digest - list_rpms "${IMG_NEW_DIGEST}" "${plat}" "rpm_lists/${VMJ}/${IMT}/new_${plat//\//_}.txt" - - # Old image (if present): extract package list from label instead of running it - if [ -n "${IMG_PREV:-}" ] && [ -n "${TAG_PREV:-}" ]; then - echo "Fetching previous RPM list label..." - label_json=$(crane config "${IMG_PREV}:${TAG_PREV}" \ - | jq -r --arg key "${LABEL_KEY:-org.almalinux.sbom}" '.config.Labels[$key] // empty') - if [ -n "${label_json}" ]; then - val=$(jq -r --arg plat "${plat}" --argjson data "${label_json}" '($data[$plat]) // empty' "rpm_lists/${VMJ}/${IMT}/old_${plat//\//_}.txt" - else - echo "No previous entry for ${plat} in label ${LABEL_KEY}" - fi - else - echo "No previous label found for ${plat}" - fi + prev_sbom=$(echo "${label_json}" | jq -r --arg plat "${plat}" '.platforms[$plat].sbom // empty' 2>/dev/null || true) + if [ -n "${prev_sbom}" ]; then + echo "${prev_sbom}" | jq . > "sbom_prev/${VMJ}/${IMT}/sbom_${key}.spdx.json" 2>/dev/null || \ + echo "${prev_sbom}" > "sbom_prev/${VMJ}/${IMT}/sbom_${key}.spdx.json" + echo "Restored previous SBOM for ${plat}" + else + echo "No previous SBOM entry for ${plat}" fi done - - - name: Diff RPM sets and build Markdown changelog + echo "prev_sbom_found=true" >> "$GITHUB_OUTPUT" + + - name: Diff SBOMs and build Markdown changelog if: env.check_update != 0 - id: rpm-diff + id: sbom-diff env: PLATFORMS: ${{ env.platforms }} VMJ: ${{ matrix.version_major }} @@ -448,6 +505,7 @@ jobs: run: | set -euo pipefail out="rpm_diff_${VMJ}_${IMT}.md" + if [ -n "${PREV_TAG}" ]; then echo "Changes since last version (${PREV_TAG}):" > "${out}" else @@ -459,32 +517,44 @@ jobs: for p in ${PLATFORMS//,/ }; do plat=$(echo "$p" | xargs) key="${plat//\//_}" - new="rpm_lists/${VMJ}/${IMT}/new_${key}.txt" - old="rpm_lists/${VMJ}/${IMT}/old_${key}.txt" + new_sbom="sbom_output/${VMJ}/${IMT}/sbom_${key}.spdx.json" + old_sbom="sbom_prev/${VMJ}/${IMT}/sbom_${key}.spdx.json" - [ ! -f "${new}" ] && continue + [ ! -f "${new_sbom}" ] && continue { echo "
" printf "Platform: \`%s\`\n\n" "$plat" } >> "${out}" - if [ -f "${old}" ]; then + # Extract package lists from SPDX SBOM JSON + # Each package has: name, versionInfo, SPDXID + jq -r '.packages[]? | select(.SPDXID != "SPDXRef-DOCUMENT") | [.name, .versionInfo // "unknown"] | @tsv' \ + "${new_sbom}" | sort > /tmp/new_pkgs_${key}.txt + + if [ -f "${old_sbom}" ]; then + jq -r '.packages[]? | select(.SPDXID != "SPDXRef-DOCUMENT") | [.name, .versionInfo // "unknown"] | @tsv' \ + "${old_sbom}" | sort > /tmp/old_pkgs_${key}.txt + + # Names only for added/removed detection + cut -f1 /tmp/new_pkgs_${key}.txt | sort -u > /tmp/new_names_${key}.txt + cut -f1 /tmp/old_pkgs_${key}.txt | sort -u > /tmp/old_names_${key}.txt + # Added / Removed sets based on names - comm -13 "${old%.txt}.names" "${new%.txt}.names" > added.tmp || true - comm -23 "${old%.txt}.names" "${new%.txt}.names" > removed.tmp || true + comm -13 /tmp/old_names_${key}.txt /tmp/new_names_${key}.txt > /tmp/added.tmp || true + comm -23 /tmp/old_names_${key}.txt /tmp/new_names_${key}.txt > /tmp/removed.tmp || true - # Recompute diffs and extract version changes + # Updated: same name, different version updated_tmp=$(mktemp) awk -F'\t' 'NR==FNR{old[$1]=$2;next}{ if($1 in old && old[$1]!=$2){ print $1"\t"old[$1]" → "$2 } }' \ - "${old}" "${new}" > "${updated_tmp}" + /tmp/old_pkgs_${key}.txt /tmp/new_pkgs_${key}.txt > "${updated_tmp}" - addc=$(wc -l < added.tmp | tr -d ' ') + addc=$(wc -l < /tmp/added.tmp | tr -d ' ') updc=$(wc -l < "${updated_tmp}" | tr -d ' ') - delc=$(wc -l < removed.tmp | tr -d ' ') + delc=$(wc -l < /tmp/removed.tmp | tr -d ' ') printed=false - echo "**Package Changes**" >> "${out}" + echo "**Package Changes** _(based on SBOM diff)_" >> "${out}" echo "" >> "${out}" if [ "$updc" -gt 0 ]; then @@ -501,7 +571,8 @@ jobs: { echo "**Added (${addc})**" echo "" - awk -v OFS='\t' 'NR==FNR{ver[$1]=$2;next}{n=$0; if(n!=""){ printf "- %s: %s\n", n, ver[n] }}' "${new}" added.tmp + awk -v OFS='\t' 'NR==FNR{ver[$1]=$2;next}{n=$0; if(n!=""){ printf "- %s: %s\n", n, ver[n] }}' \ + /tmp/new_pkgs_${key}.txt /tmp/added.tmp echo "" } >> "${out}" printed=true @@ -511,7 +582,7 @@ jobs: { echo "**Removed (${delc})**" echo "" - awk '{print "- " $0}' removed.tmp + awk '{print "- " $0}' /tmp/removed.tmp echo "" } >> "${out}" printed=true @@ -519,17 +590,20 @@ jobs: [ "$printed" = true ] && any_section=true - rm -f added.tmp removed.tmp "${updated_tmp}" + rm -f /tmp/added.tmp /tmp/removed.tmp "${updated_tmp}" + rm -f /tmp/old_pkgs_${key}.txt /tmp/old_names_${key}.txt else - echo "**Package Changes**" >> "${out}" + echo "**Package Changes** _(based on SBOM diff)_" >> "${out}" echo "" >> "${out}" echo "_No previous version — initial publication (showing all as Added)_" >> "${out}" echo "" >> "${out}" - awk -F'\t' '{printf "- %s: %s\n",$1,$2}' "${new}" >> "${out}" + awk -F'\t' '{printf "- %s: %s\n",$1,$2}' /tmp/new_pkgs_${key}.txt >> "${out}" echo "" >> "${out}" any_section=true - fi + + rm -f /tmp/new_pkgs_${key}.txt /tmp/new_names_${key}.txt + # Close the
section for this platform printf "\n
\n" >> "${out}" done @@ -540,10 +614,9 @@ jobs: mkdir -p rpm_diff_artifacts/${VMJ} mv "${out}" "rpm_diff_artifacts/${VMJ}/rpm_diff_${VMJ}_${IMT}.md" - - - name: Build rechunk-style label payload (gzip+base64) + - name: Build SBOM label payload for image if: env.check_update != 0 - id: build-rechunk-label + id: build-sbom-label env: PLATFORMS: ${{ env.platforms }} VMJ: ${{ matrix.version_major }} @@ -553,9 +626,8 @@ jobs: LABEL_KEY: org.almalinux.sbom run: | set -euo pipefail - base="rpm_lists/${VMJ}/${IMT}" - # Create a base JSON + # Create the top-level label JSON with metadata and per-platform SBOMs jq -n \ --arg vmj "${VMJ}" \ --arg imt "${IMT}" \ @@ -567,50 +639,61 @@ jobs: built_at: $dts, prev_tag: ( $prev | select(length>0) // null ), platforms: {} - }' > rechunk_${VMJ}_${IMT}.json + }' > sbom_label_${VMJ}_${IMT}.json - # Add per-platform package lists and SHA256 for p in ${PLATFORMS//,/ }; do plat=$(echo "$p" | xargs) key="${plat//\//_}" - new="${base}/new_${key}.txt" - [ -f "${new}" ] || continue + sbom_file="sbom_output/${VMJ}/${IMT}/sbom_${key}.spdx.json" + metadata_file="sbom_output/${VMJ}/${IMT}/metadata_${key}.json" + [ -f "${sbom_file}" ] || continue - sha=$(sha256sum "${new}" | awk '{print $1}') - # Convert TSV "nameversion-release" into JSON array - jq -Rs ' - split("\n") - | map(select(length>0) | split("\t") | {n: .[0], v: .[1]}) - ' "${new}" > pkgs_${key}.json + # Compute SHA256 of the SBOM file + sha=$(sha256sum "${sbom_file}" | awk '{print $1}') + # Extract package count from SBOM + pkg_count=$(jq '[.packages[]? | select(.SPDXID != "SPDXRef-DOCUMENT")] | length' "${sbom_file}") + + # Include the full SBOM as a nested JSON object in the label tmp=$(mktemp) - jq --arg plat "${plat}" --arg sha "${sha}" --slurpfile pkgs pkgs_${key}.json \ - '.platforms[$plat] = { pkgs_sha256: $sha, pkgs: $pkgs[0] }' \ - "rechunk_${VMJ}_${IMT}.json" > "${tmp}" - mv "${tmp}" "rechunk_${VMJ}_${IMT}.json" - rm -f pkgs_${key}.json + jq --arg plat "${plat}" \ + --arg sha "${sha}" \ + --argjson count "${pkg_count}" \ + --slurpfile sbom "${sbom_file}" \ + '.platforms[$plat] = { + sbom_sha256: $sha, + package_count: $count, + sbom: $sbom[0] + }' \ + "sbom_label_${VMJ}_${IMT}.json" > "${tmp}" + mv "${tmp}" "sbom_label_${VMJ}_${IMT}.json" done - # Store the JSON directly in the label payload (no compression) - RCHUNK_INFO_LABEL=$(cat "rechunk_${VMJ}_${IMT}.json" | jq -c .) - echo "RCHUNK_INFO_LABEL=${RCHUNK_INFO_LABEL}" >> "$GITHUB_ENV" + # Store the JSON directly in the label payload + SBOM_LABEL=$(cat "sbom_label_${VMJ}_${IMT}.json" | jq -c .) + echo "SBOM_LABEL=${SBOM_LABEL}" >> "$GITHUB_ENV" echo "LABEL_KEY=${LABEL_KEY}" >> "$GITHUB_ENV" - # Debug: show summary of the generated JSON without full package lists - echo "[Debug] schema/preview:" - jq 'del(.platforms[]?.pkgs) | .platforms |= with_entries(.value |= {pkgs_sha256:.pkgs_sha256, pkgs_count:0})' "rechunk_${VMJ}_${IMT}.json" || true - + # Debug: show summary without full SBOM contents + echo "[Debug] SBOM label schema/preview:" + jq 'del(.platforms[]?.sbom) | .platforms |= with_entries(.value |= {sbom_sha256:.sbom_sha256, package_count:.package_count})' \ + "sbom_label_${VMJ}_${IMT}.json" || true - - - name: Upload RPM diff artifacts + - name: Upload SBOM artifacts + if: env.check_update != 0 + uses: actions/upload-artifact@v4 + with: + name: sbom-${{ matrix.version_major }}-${{ matrix.image_types }} + path: sbom_output/${{ matrix.version_major }}/${{ matrix.image_types }}/ + + - name: Upload RPM diff artifacts if: env.check_update != 0 uses: actions/upload-artifact@v4 with: name: rpm-diff-${{ matrix.version_major }}-${{ matrix.image_types }} path: rpm_diff_artifacts/${{ matrix.version_major }}/ - - - name: Push images to Client Library + - name: Push images to Client Library if: env.check_update != 0 id: push-images uses: docker/build-push-action@v5 @@ -622,10 +705,9 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: | - ${{ env.LABEL_KEY }}=${{ env.RCHUNK_INFO_LABEL }} + ${{ env.LABEL_KEY }}=${{ env.SBOM_LABEL }} - - - name: Prepare Mattermost message + - name: Prepare Mattermost message if: env.check_update != 0 run: | # Mattermost message heading @@ -657,8 +739,7 @@ jobs: name: mattermost-message-${{ matrix.version_major }}-${{ matrix.image_types }} path: ${{ matrix.version_major }}*_mattermost.md - - - name: Extract RootFS (default and minimal only) + - name: Extract RootFS (default and minimal only) id: extract-rootfs # 'default' or 'minimal' images only go to Docker Official Library if: ( matrix.image_types == 'default' || matrix.image_types == 'minimal' ) && env.check_update != 0 @@ -741,8 +822,7 @@ jobs: ls -1 ${pwd}/${{ matrix.version_major }}/${{ matrix.image_types }}/*/*.tar.xz # Change date stamp in '${version_major}/${image_types}/${arch}/Dockerfile' - - - name: Change date stamp in Dockerfile (default and minimal only) + - name: Change date stamp in Dockerfile (default and minimal only) # 'default' or 'minimal' images only go to Docker Official Library if: ( matrix.image_types == 'default' || matrix.image_types == 'minimal' ) && env.check_update != 0 run: | @@ -774,8 +854,7 @@ jobs: done # Commit '${version_major}/${image_types}/${arch}/*' - - - name: "Commit and push ${{ matrix.image_types }}/*/* Dockerfile and RootFS (branch ${{ matrix.version_major }})" + - name: "Commit and push ${{ matrix.image_types }}/*/* Dockerfile and RootFS (branch ${{ matrix.version_major }})" # 'default' or 'minimal' images only and 'Push to production' is checked if: ( matrix.image_types == 'default' || matrix.image_types == 'minimal' ) && env.production == 'true' && env.check_update != 0 uses: EndBug/add-and-commit@v9 @@ -783,7 +862,7 @@ jobs: default_author: user_info new_branch: ${{ matrix.version_major }} cwd: ${{ matrix.version_major }} - pull: '--rebase --autostash' + pull: "--rebase --autostash" message: "AlmaLinux ${{ matrix.version_major }} ${{ matrix.image_types }} - ${{ env.date_stamp }} ${{ env.time_stamp }} (generated on ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})." push: true @@ -792,8 +871,7 @@ jobs: runs-on: ubuntu-24.04 needs: [init-data, build-test-push] steps: - - - name: Download all RPM diff artifacts (if any) + - name: Download all RPM diff artifacts (if any) uses: actions/download-artifact@v4 with: pattern: rpm-diff-* @@ -801,8 +879,15 @@ jobs: path: _rpm_diff_all continue-on-error: true - - - name: Check artifacts existence + - name: Download all SBOM artifacts (if any) + uses: actions/download-artifact@v4 + with: + pattern: sbom-* + merge-multiple: true + path: _sbom_all + continue-on-error: true + + - name: Check artifacts existence id: chk run: | set -euo pipefail @@ -814,8 +899,7 @@ jobs: fi echo "found=true" >> "$GITHUB_OUTPUT" - - - name: Compose release notes (group by version & type) + - name: Compose release notes (group by version & type) if: steps.chk.outputs.found == 'true' id: notes run: | @@ -825,8 +909,9 @@ jobs: TIME_STAMP='${{ needs.init-data.outputs.time_stamp }}' PROD='${{ needs.init-data.outputs.production }}' - header="# AlmaLinux Container Images – RPM diffs (${DATE_STAMP} ${TIME_STAMP} UTC)\n" + header="# AlmaLinux Container Images – SBOM-based RPM diffs (${DATE_STAMP} ${TIME_STAMP} UTC)\n" [ "${PROD}" = "true" ] && header="${header}\n_Release type: **Production**_\n" || header="${header}\n_Release type: **Testing / Pre-release**_\n" + header="${header}\n_Changelogs generated from SPDX 2.3 SBOM comparison using [cloud-images-sbom-tools](https://github.com/AlmaLinux/cloud-images-sbom-tools)_\n" printf "%b\n" "${header}" > RELEASE_NOTES.md # Collect all md files regardless of where they were unpacked @@ -879,14 +964,18 @@ jobs: done done - # Also archive the original markdown files for attachment + # Archive the original markdown files + SBOM files for attachment tar -czf rpm-diff-artifacts-${DATE_STAMP}.tar.gz -C _rpm_diff_all . + # Also archive SBOM artifacts if they exist + if [ -d _sbom_all ] && find _sbom_all -type f -name '*.spdx.json' | grep -q .; then + tar -czf sbom-artifacts-${DATE_STAMP}.tar.gz -C _sbom_all . + fi + echo "tag=v${DATE_STAMP}" >> "$GITHUB_OUTPUT" echo "name=AlmaLinux Container RPM diffs (${DATE_STAMP})" >> "$GITHUB_OUTPUT" - - - name: Create/Update GitHub Release + - name: Create/Update GitHub Release if: steps.chk.outputs.found == 'true' uses: softprops/action-gh-release@v2 with: @@ -897,6 +986,7 @@ jobs: prerelease: ${{ needs.init-data.outputs.production != 'true' }} files: | rpm-diff-artifacts-${{ needs.init-data.outputs.date_stamp }}.tar.gz + sbom-artifacts-${{ needs.init-data.outputs.date_stamp }}.tar.gz env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -961,16 +1051,13 @@ jobs: version_major: ${{ fromJSON(needs.init-data.outputs.version_major_matrix) }} steps: - - - - name: Checkout ${{ github.repository }}, branch '${{ matrix.version_major }}', path '${{ matrix.version_major }}' + - name: Checkout ${{ github.repository }}, branch '${{ matrix.version_major }}', path '${{ matrix.version_major }}' uses: actions/checkout@v4 with: ref: ${{ matrix.version_major }} path: ${{ matrix.version_major }} - - - name: Optimize size of branch the '${{ matrix.version_major }}' + - name: Optimize size of branch the '${{ matrix.version_major }}' run: | date_stamp=${{ needs.init-data.outputs.date_stamp }} cd ${{ matrix.version_major }} @@ -1002,11 +1089,10 @@ jobs: echo "[Debug]" git status - - - name: Commit and push ${{ github.repository }}, branch '${{ matrix.version_major }}' + - name: Commit and push ${{ github.repository }}, branch '${{ matrix.version_major }}' uses: EndBug/add-and-commit@v9 with: default_author: user_info message: "Update AlmaLinux ${{ matrix.version_major }} - ${{ needs.init-data.outputs.date_stamp }} ${{ needs.init-data.outputs.time_stamp }} (generated on ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})." - push: '--force --set-upstream origin ${{ matrix.version_major }}' + push: "--force --set-upstream origin ${{ matrix.version_major }}" cwd: ${{ matrix.version_major }} From ac9e8aff5754517078c2c5d96c60595aad580e1c Mon Sep 17 00:00:00 2001 From: unixtech Date: Thu, 12 Feb 2026 11:59:15 +0900 Subject: [PATCH 16/25] fix: run SBOM tools on host with rpm/dnf bindings installed on Ubuntu --- .github/workflows/build-test-push.yml | 267 +++++++++++++++++++------- 1 file changed, 199 insertions(+), 68 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index 367bd99..777c316 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -90,6 +90,99 @@ env: # SBOM tools repository sbom_tools_repo: "https://github.com/AlmaLinux/cloud-images-sbom-tools.git" +jobs: +name: Build, test and push to the Client Library + +on: + workflow_dispatch: + inputs: + production: + description: | + 'Push to production registries' + 'not checked - to testing' + required: true + type: boolean + default: false + + notify_mattermost: + description: 'Send notification to Mattermost' + required: true + type: boolean + default: false + + version_major: + description: 'AlmaLinux major version' + required: true + default: '10' + type: choice + options: + - 10-kitten + - 10 + - 9 + - 8 + + type_default: + description: 'default' + required: true + type: boolean + default: true + + type_minimal: + description: 'minimal' + required: true + type: boolean + default: true + + type_micro: + description: 'micro' + required: true + type: boolean + default: true + + type_base: + description: 'base' + required: true + type: boolean + default: true + + type_init: + description: 'init' + required: true + type: boolean + default: true + + type_toolbox: + description: 'toolbox' + required: true + type: boolean + default: true + + schedule: + # run every day at 04:00 UTC + - cron: '00 04 * * *' + +env: + # List of versions to build on schedule + # TODO: add 10 when it is released + # Kitten should not be built on schedule + versions_list: '"8", "9", "10"' + + # List image types to build on schedule + image_types: '"default", "minimal", "micro", "base", "init", "toolbox"' + + # Latest version + version_latest: 10 + + # Platforms list + platforms: 'linux/amd64, linux/ppc64le, linux/s390x, linux/arm64' + + # Registries lists + registries_production: 'docker.io/almalinux, quay.io/almalinuxorg, ghcr.io/almalinux' + registries_testing: 'quay.io/almalinuxautobot' + + # SBOM tools repository + sbom_tools_repo: 'https://github.com/AlmaLinux/cloud-images-sbom-tools.git' + jobs: init-data: name: Set matrix, prepare variables @@ -145,7 +238,7 @@ jobs: version_major: ${{ fromJSON(needs.init-data.outputs.version_major_matrix) }} image_types: ${{ fromJSON(needs.init-data.outputs.image_types_matrix) }} exclude: - - image_types: "false" + - image_types: 'false' env: date_time_stamp: ${{ needs.init-data.outputs.date_time_stamp }} date_stamp: ${{ needs.init-data.outputs.date_stamp }} @@ -154,7 +247,8 @@ jobs: production: ${{ needs.init-data.outputs.production }} steps: - - name: Prepare AlmaLinux Minor version number + - + name: Prepare AlmaLinux Minor version number run: | case ${{ matrix.version_major }} in 8) @@ -170,7 +264,8 @@ jobs: esac echo "version_minor=${version_minor}" >> $GITHUB_ENV - - name: Check update + - + name: Check update id: check-update run: | # dnf check-update --secseverity=Important @@ -190,26 +285,28 @@ jobs: echo "check_update=${check_update}" >> "$GITHUB_ENV" echo "Exit code: '$check_update'" - - name: Set platforms and registries + - + name: Set platforms and registries if: env.check_update != 0 run: | - # Platforms - platforms="${{ env.platforms }}" - case ${{ matrix.version_major }} in - 8|9) - platforms="linux/386, ${platforms}" ;; - 10*) - platforms="linux/amd64/v2, ${platforms}" ;; - esac - echo "platforms=${platforms}" >> $GITHUB_ENV + # Platforms + platforms="${{ env.platforms }}" + case ${{ matrix.version_major }} in + 8|9) + platforms="linux/386, ${platforms}" ;; + 10*) + platforms="linux/amd64/v2, ${platforms}" ;; + esac + echo "platforms=${platforms}" >> $GITHUB_ENV - # Registries - registries="${{ env.registries_testing }}" - [[ "${{ needs.init-data.outputs.production }}" == "true" ]] && \ - registries="${{ env.registries_production }}" - echo "registries=${registries}" >> $GITHUB_ENV + # Registries + registries="${{ env.registries_testing }}" + [[ "${{ needs.init-data.outputs.production }}" == "true" ]] && \ + registries="${{ env.registries_production }}" + echo "registries=${registries}" >> $GITHUB_ENV - - name: Generate list of images to use as base name for tags + - + name: Generate list of images to use as base name for tags if: env.check_update != 0 run: | # list of registries to push to @@ -238,7 +335,8 @@ jobs: # [Debug] echo $IMAGE_NAMES - - name: Enable containerd image store, set Docker data-root + - + name: Enable containerd image store, set Docker data-root if: env.check_update != 0 run: | # JQ file to switch into containerd image store and set Docker data-root @@ -252,31 +350,43 @@ jobs: echo "[Debug] Docker data-root:" docker info -f '{{ .DockerRootDir }}' - - name: Checkout ${{ github.repository }}, branch 'main' + - + name: Checkout ${{ github.repository }}, branch 'main' if: env.check_update != 0 uses: actions/checkout@v4 - - name: Checkout ${{ github.repository }}, branch '${{ matrix.version_major }}', path '${{ matrix.version_major }}' + - + name: Checkout ${{ github.repository }}, branch '${{ matrix.version_major }}', path '${{ matrix.version_major }}' if: env.check_update != 0 uses: actions/checkout@v4 with: ref: ${{ matrix.version_major }} path: ${{ matrix.version_major }} - - name: Clone SBOM tools + - + name: Clone SBOM tools and install dependencies on host if: env.check_update != 0 run: | git clone ${{ env.sbom_tools_repo }} /tmp/sbom-tools + + # Install RPM/DNF Python bindings on Ubuntu + sudo apt-get update -qq + sudo apt-get install -y -qq python3-rpm python3-dnf rpm + + # Install sbom-tools Python dependencies pip install --break-system-packages -r /tmp/sbom-tools/requirements.txt - - name: Set up QEMU + - + name: Set up QEMU if: env.check_update != 0 uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx + - + name: Set up Docker Buildx if: env.check_update != 0 uses: docker/setup-buildx-action@v3 - - name: Login to Docker.io + - + name: Login to Docker.io if: contains(env.registries, 'docker.io') && env.check_update != 0 uses: docker/login-action@v3 with: @@ -284,7 +394,8 @@ jobs: username: ${{ env.production == 'true' && secrets.DOCKERHUB_USERNAME || secrets.TEST_DOCKERHUB_USERNAME }} password: ${{ env.production == 'true' && secrets.DOCKERHUB_TOKEN || secrets.TEST_DOCKERHUB_TOKEN }} - - name: Login to Quay.io + - + name: Login to Quay.io if: contains(env.registries, 'quay.io') && env.check_update != 0 uses: docker/login-action@v3 with: @@ -292,7 +403,8 @@ jobs: username: ${{ env.production == 'true' && secrets.QUAY_IO_USERNAME || secrets.TEST_QUAY_IO_USERNAME }} password: ${{ env.production == 'true' && secrets.QUAY_IO_CLI_PASSWORD || secrets.TEST_QUAY_IO_CLI_PASSWORD }} - - name: Login to Ghcr.io + - + name: Login to Ghcr.io if: contains(env.registries, 'ghcr.io') && env.check_update != 0 uses: docker/login-action@v3 with: @@ -300,7 +412,8 @@ jobs: username: ${{ env.production == 'true' && secrets.GIT_HUB_USERNAME || secrets.TEST_GITHUB_USERNAME }} password: ${{ env.production == 'true' && secrets.GIT_HUB_TOKEN || secrets.TEST_GITHUB_TOKEN }} - - name: Generate tags and prepare metadata to build and push + - + name: Generate tags and prepare metadata to build and push if: env.check_update != 0 id: meta uses: docker/metadata-action@v5 @@ -315,7 +428,8 @@ jobs: type=raw,value=${{ matrix.version_major }}${{ env.version_minor }},enable=true type=raw,value=${{ matrix.version_major }}${{ env.version_minor }}-${{ env.date_stamp }},enable=true - - name: Build images + - + name: Build images if: env.check_update != 0 id: build-images uses: docker/build-push-action@v5 @@ -328,7 +442,8 @@ jobs: load: true tags: ${{ steps.meta.outputs.tags }} - - name: Test images + - + name: Test images if: env.check_update != 0 id: test-images run: | @@ -344,7 +459,8 @@ jobs: && ( test "${{ matrix.image_types }}" != "micro" && rpm -q gpg-pubkey) || true " done - - name: Detect previous released tag for this image type + - + name: Detect previous released tag for this image type if: env.check_update != 0 id: detect-prev env: @@ -367,7 +483,8 @@ jobs: echo "prev_found=false" >> "$GITHUB_OUTPUT" fi - - name: Install crane + - + name: Install crane if: env.check_update != 0 run: | set -euo pipefail @@ -386,7 +503,8 @@ jobs: # 3. Diff the two SBOMs to produce a Markdown changelog # - - name: Generate SBOM per platform (new image) + - + name: Generate SBOM per platform (new image) if: env.check_update != 0 id: sbom-generate env: @@ -405,37 +523,31 @@ jobs: echo "=== Generating SBOM for ${plat} ===" - # Create a temporary rootfs mount point for sbom_data_collector + # Export the container filesystem and mount it on the host rootfs_dir=$(mktemp -d) - - # Export the container filesystem for this platform container_id=$(docker create --platform="${plat}" "${IMG_NEW_DIGEST}") docker export "${container_id}" | tar -xf - -C "${rootfs_dir}" 2>/dev/null || true docker rm "${container_id}" > /dev/null - # Step 1: Collect metadata using sbom_data_collector.py + # Step 1: Collect metadata using host's rpm/dnf bindings with --root metadata_file="sbom_output/${VMJ}/${IMT}/metadata_${key}.json" python3 /tmp/sbom-tools/sbom_data_collector.py \ --root "${rootfs_dir}" \ -o "${metadata_file}" \ - --with-checksums \ --verbose || { - echo "Warning: sbom_data_collector failed for ${plat}, falling back without checksums" - python3 /tmp/sbom-tools/sbom_data_collector.py \ - --root "${rootfs_dir}" \ - -o "${metadata_file}" \ - --verbose + echo "Warning: sbom_data_collector failed for ${plat}, skipping" + rm -rf "${rootfs_dir}" + continue } - # Step 2: Generate SPDX SBOM using sbom_generator.py + # Step 2: Generate SPDX SBOM from collected metadata sbom_file="sbom_output/${VMJ}/${IMT}/sbom_${key}.spdx.json" image_name="AlmaLinux-${VMJ}-${IMT}-${plat//\//-}" python3 /tmp/sbom-tools/sbom_generator.py \ "${image_name}" \ "${metadata_file}" \ "${sbom_file}" || { - echo "Error: sbom_generator failed for ${plat}" - # Clean up and continue to next platform + echo "Error: sbom_generator failed for ${plat}, skipping" rm -rf "${rootfs_dir}" continue } @@ -446,7 +558,8 @@ jobs: rm -rf "${rootfs_dir}" done - - name: Fetch previous SBOM from image label (if exists) + - + name: Fetch previous SBOM from image label (if exists) if: env.check_update != 0 id: sbom-fetch-prev env: @@ -493,7 +606,8 @@ jobs: echo "prev_sbom_found=true" >> "$GITHUB_OUTPUT" - - name: Diff SBOMs and build Markdown changelog + - + name: Diff SBOMs and build Markdown changelog if: env.check_update != 0 id: sbom-diff env: @@ -614,7 +728,8 @@ jobs: mkdir -p rpm_diff_artifacts/${VMJ} mv "${out}" "rpm_diff_artifacts/${VMJ}/rpm_diff_${VMJ}_${IMT}.md" - - name: Build SBOM label payload for image + - + name: Build SBOM label payload for image if: env.check_update != 0 id: build-sbom-label env: @@ -679,21 +794,24 @@ jobs: jq 'del(.platforms[]?.sbom) | .platforms |= with_entries(.value |= {sbom_sha256:.sbom_sha256, package_count:.package_count})' \ "sbom_label_${VMJ}_${IMT}.json" || true - - name: Upload SBOM artifacts + - + name: Upload SBOM artifacts if: env.check_update != 0 uses: actions/upload-artifact@v4 with: name: sbom-${{ matrix.version_major }}-${{ matrix.image_types }} path: sbom_output/${{ matrix.version_major }}/${{ matrix.image_types }}/ - - name: Upload RPM diff artifacts + - + name: Upload RPM diff artifacts if: env.check_update != 0 uses: actions/upload-artifact@v4 with: name: rpm-diff-${{ matrix.version_major }}-${{ matrix.image_types }} path: rpm_diff_artifacts/${{ matrix.version_major }}/ - - name: Push images to Client Library + - + name: Push images to Client Library if: env.check_update != 0 id: push-images uses: docker/build-push-action@v5 @@ -707,7 +825,8 @@ jobs: labels: | ${{ env.LABEL_KEY }}=${{ env.SBOM_LABEL }} - - name: Prepare Mattermost message + - + name: Prepare Mattermost message if: env.check_update != 0 run: | # Mattermost message heading @@ -739,7 +858,8 @@ jobs: name: mattermost-message-${{ matrix.version_major }}-${{ matrix.image_types }} path: ${{ matrix.version_major }}*_mattermost.md - - name: Extract RootFS (default and minimal only) + - + name: Extract RootFS (default and minimal only) id: extract-rootfs # 'default' or 'minimal' images only go to Docker Official Library if: ( matrix.image_types == 'default' || matrix.image_types == 'minimal' ) && env.check_update != 0 @@ -822,7 +942,8 @@ jobs: ls -1 ${pwd}/${{ matrix.version_major }}/${{ matrix.image_types }}/*/*.tar.xz # Change date stamp in '${version_major}/${image_types}/${arch}/Dockerfile' - - name: Change date stamp in Dockerfile (default and minimal only) + - + name: Change date stamp in Dockerfile (default and minimal only) # 'default' or 'minimal' images only go to Docker Official Library if: ( matrix.image_types == 'default' || matrix.image_types == 'minimal' ) && env.check_update != 0 run: | @@ -854,7 +975,8 @@ jobs: done # Commit '${version_major}/${image_types}/${arch}/*' - - name: "Commit and push ${{ matrix.image_types }}/*/* Dockerfile and RootFS (branch ${{ matrix.version_major }})" + - + name: "Commit and push ${{ matrix.image_types }}/*/* Dockerfile and RootFS (branch ${{ matrix.version_major }})" # 'default' or 'minimal' images only and 'Push to production' is checked if: ( matrix.image_types == 'default' || matrix.image_types == 'minimal' ) && env.production == 'true' && env.check_update != 0 uses: EndBug/add-and-commit@v9 @@ -862,7 +984,7 @@ jobs: default_author: user_info new_branch: ${{ matrix.version_major }} cwd: ${{ matrix.version_major }} - pull: "--rebase --autostash" + pull: '--rebase --autostash' message: "AlmaLinux ${{ matrix.version_major }} ${{ matrix.image_types }} - ${{ env.date_stamp }} ${{ env.time_stamp }} (generated on ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})." push: true @@ -871,7 +993,8 @@ jobs: runs-on: ubuntu-24.04 needs: [init-data, build-test-push] steps: - - name: Download all RPM diff artifacts (if any) + - + name: Download all RPM diff artifacts (if any) uses: actions/download-artifact@v4 with: pattern: rpm-diff-* @@ -879,7 +1002,8 @@ jobs: path: _rpm_diff_all continue-on-error: true - - name: Download all SBOM artifacts (if any) + - + name: Download all SBOM artifacts (if any) uses: actions/download-artifact@v4 with: pattern: sbom-* @@ -887,7 +1011,8 @@ jobs: path: _sbom_all continue-on-error: true - - name: Check artifacts existence + - + name: Check artifacts existence id: chk run: | set -euo pipefail @@ -899,7 +1024,8 @@ jobs: fi echo "found=true" >> "$GITHUB_OUTPUT" - - name: Compose release notes (group by version & type) + - + name: Compose release notes (group by version & type) if: steps.chk.outputs.found == 'true' id: notes run: | @@ -975,7 +1101,8 @@ jobs: echo "tag=v${DATE_STAMP}" >> "$GITHUB_OUTPUT" echo "name=AlmaLinux Container RPM diffs (${DATE_STAMP})" >> "$GITHUB_OUTPUT" - - name: Create/Update GitHub Release + - + name: Create/Update GitHub Release if: steps.chk.outputs.found == 'true' uses: softprops/action-gh-release@v2 with: @@ -1051,13 +1178,16 @@ jobs: version_major: ${{ fromJSON(needs.init-data.outputs.version_major_matrix) }} steps: - - name: Checkout ${{ github.repository }}, branch '${{ matrix.version_major }}', path '${{ matrix.version_major }}' + + - + name: Checkout ${{ github.repository }}, branch '${{ matrix.version_major }}', path '${{ matrix.version_major }}' uses: actions/checkout@v4 with: ref: ${{ matrix.version_major }} path: ${{ matrix.version_major }} - - name: Optimize size of branch the '${{ matrix.version_major }}' + - + name: Optimize size of branch the '${{ matrix.version_major }}' run: | date_stamp=${{ needs.init-data.outputs.date_stamp }} cd ${{ matrix.version_major }} @@ -1089,10 +1219,11 @@ jobs: echo "[Debug]" git status - - name: Commit and push ${{ github.repository }}, branch '${{ matrix.version_major }}' + - + name: Commit and push ${{ github.repository }}, branch '${{ matrix.version_major }}' uses: EndBug/add-and-commit@v9 with: default_author: user_info message: "Update AlmaLinux ${{ matrix.version_major }} - ${{ needs.init-data.outputs.date_stamp }} ${{ needs.init-data.outputs.time_stamp }} (generated on ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})." - push: "--force --set-upstream origin ${{ matrix.version_major }}" - cwd: ${{ matrix.version_major }} + push: '--force --set-upstream origin ${{ matrix.version_major }}' + cwd: ${{ matrix.version_major }} \ No newline at end of file From 9c111a486be0ca980c89dc61dfa19c61a593d8fa Mon Sep 17 00:00:00 2001 From: unixtech Date: Thu, 12 Feb 2026 12:04:05 +0900 Subject: [PATCH 17/25] fix: replace duplicated workflow with clean version --- .github/workflows/build-test-push.yml | 243 ++++++-------------------- 1 file changed, 56 insertions(+), 187 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index 777c316..d150a50 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -85,104 +85,11 @@ env: # Registries lists registries_production: "docker.io/almalinux, quay.io/almalinuxorg, ghcr.io/almalinux" - registries_testing: "quay.io/ryosuke_666" + registries_testing: "quay.io/almalinuxautobot" # SBOM tools repository sbom_tools_repo: "https://github.com/AlmaLinux/cloud-images-sbom-tools.git" -jobs: -name: Build, test and push to the Client Library - -on: - workflow_dispatch: - inputs: - production: - description: | - 'Push to production registries' - 'not checked - to testing' - required: true - type: boolean - default: false - - notify_mattermost: - description: 'Send notification to Mattermost' - required: true - type: boolean - default: false - - version_major: - description: 'AlmaLinux major version' - required: true - default: '10' - type: choice - options: - - 10-kitten - - 10 - - 9 - - 8 - - type_default: - description: 'default' - required: true - type: boolean - default: true - - type_minimal: - description: 'minimal' - required: true - type: boolean - default: true - - type_micro: - description: 'micro' - required: true - type: boolean - default: true - - type_base: - description: 'base' - required: true - type: boolean - default: true - - type_init: - description: 'init' - required: true - type: boolean - default: true - - type_toolbox: - description: 'toolbox' - required: true - type: boolean - default: true - - schedule: - # run every day at 04:00 UTC - - cron: '00 04 * * *' - -env: - # List of versions to build on schedule - # TODO: add 10 when it is released - # Kitten should not be built on schedule - versions_list: '"8", "9", "10"' - - # List image types to build on schedule - image_types: '"default", "minimal", "micro", "base", "init", "toolbox"' - - # Latest version - version_latest: 10 - - # Platforms list - platforms: 'linux/amd64, linux/ppc64le, linux/s390x, linux/arm64' - - # Registries lists - registries_production: 'docker.io/almalinux, quay.io/almalinuxorg, ghcr.io/almalinux' - registries_testing: 'quay.io/almalinuxautobot' - - # SBOM tools repository - sbom_tools_repo: 'https://github.com/AlmaLinux/cloud-images-sbom-tools.git' - jobs: init-data: name: Set matrix, prepare variables @@ -238,7 +145,7 @@ jobs: version_major: ${{ fromJSON(needs.init-data.outputs.version_major_matrix) }} image_types: ${{ fromJSON(needs.init-data.outputs.image_types_matrix) }} exclude: - - image_types: 'false' + - image_types: "false" env: date_time_stamp: ${{ needs.init-data.outputs.date_time_stamp }} date_stamp: ${{ needs.init-data.outputs.date_stamp }} @@ -247,8 +154,7 @@ jobs: production: ${{ needs.init-data.outputs.production }} steps: - - - name: Prepare AlmaLinux Minor version number + - name: Prepare AlmaLinux Minor version number run: | case ${{ matrix.version_major }} in 8) @@ -264,8 +170,7 @@ jobs: esac echo "version_minor=${version_minor}" >> $GITHUB_ENV - - - name: Check update + - name: Check update id: check-update run: | # dnf check-update --secseverity=Important @@ -285,28 +190,26 @@ jobs: echo "check_update=${check_update}" >> "$GITHUB_ENV" echo "Exit code: '$check_update'" - - - name: Set platforms and registries + - name: Set platforms and registries if: env.check_update != 0 run: | - # Platforms - platforms="${{ env.platforms }}" - case ${{ matrix.version_major }} in - 8|9) - platforms="linux/386, ${platforms}" ;; - 10*) - platforms="linux/amd64/v2, ${platforms}" ;; - esac - echo "platforms=${platforms}" >> $GITHUB_ENV + # Platforms + platforms="${{ env.platforms }}" + case ${{ matrix.version_major }} in + 8|9) + platforms="linux/386, ${platforms}" ;; + 10*) + platforms="linux/amd64/v2, ${platforms}" ;; + esac + echo "platforms=${platforms}" >> $GITHUB_ENV - # Registries - registries="${{ env.registries_testing }}" - [[ "${{ needs.init-data.outputs.production }}" == "true" ]] && \ - registries="${{ env.registries_production }}" - echo "registries=${registries}" >> $GITHUB_ENV + # Registries + registries="${{ env.registries_testing }}" + [[ "${{ needs.init-data.outputs.production }}" == "true" ]] && \ + registries="${{ env.registries_production }}" + echo "registries=${registries}" >> $GITHUB_ENV - - - name: Generate list of images to use as base name for tags + - name: Generate list of images to use as base name for tags if: env.check_update != 0 run: | # list of registries to push to @@ -335,8 +238,7 @@ jobs: # [Debug] echo $IMAGE_NAMES - - - name: Enable containerd image store, set Docker data-root + - name: Enable containerd image store, set Docker data-root if: env.check_update != 0 run: | # JQ file to switch into containerd image store and set Docker data-root @@ -350,21 +252,18 @@ jobs: echo "[Debug] Docker data-root:" docker info -f '{{ .DockerRootDir }}' - - - name: Checkout ${{ github.repository }}, branch 'main' + - name: Checkout ${{ github.repository }}, branch 'main' if: env.check_update != 0 uses: actions/checkout@v4 - - - name: Checkout ${{ github.repository }}, branch '${{ matrix.version_major }}', path '${{ matrix.version_major }}' + - name: Checkout ${{ github.repository }}, branch '${{ matrix.version_major }}', path '${{ matrix.version_major }}' if: env.check_update != 0 uses: actions/checkout@v4 with: ref: ${{ matrix.version_major }} path: ${{ matrix.version_major }} - - - name: Clone SBOM tools and install dependencies on host + - name: Clone SBOM tools and install dependencies on host if: env.check_update != 0 run: | git clone ${{ env.sbom_tools_repo }} /tmp/sbom-tools @@ -376,17 +275,14 @@ jobs: # Install sbom-tools Python dependencies pip install --break-system-packages -r /tmp/sbom-tools/requirements.txt - - - name: Set up QEMU + - name: Set up QEMU if: env.check_update != 0 uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx + - name: Set up Docker Buildx if: env.check_update != 0 uses: docker/setup-buildx-action@v3 - - - name: Login to Docker.io + - name: Login to Docker.io if: contains(env.registries, 'docker.io') && env.check_update != 0 uses: docker/login-action@v3 with: @@ -394,8 +290,7 @@ jobs: username: ${{ env.production == 'true' && secrets.DOCKERHUB_USERNAME || secrets.TEST_DOCKERHUB_USERNAME }} password: ${{ env.production == 'true' && secrets.DOCKERHUB_TOKEN || secrets.TEST_DOCKERHUB_TOKEN }} - - - name: Login to Quay.io + - name: Login to Quay.io if: contains(env.registries, 'quay.io') && env.check_update != 0 uses: docker/login-action@v3 with: @@ -403,8 +298,7 @@ jobs: username: ${{ env.production == 'true' && secrets.QUAY_IO_USERNAME || secrets.TEST_QUAY_IO_USERNAME }} password: ${{ env.production == 'true' && secrets.QUAY_IO_CLI_PASSWORD || secrets.TEST_QUAY_IO_CLI_PASSWORD }} - - - name: Login to Ghcr.io + - name: Login to Ghcr.io if: contains(env.registries, 'ghcr.io') && env.check_update != 0 uses: docker/login-action@v3 with: @@ -412,8 +306,7 @@ jobs: username: ${{ env.production == 'true' && secrets.GIT_HUB_USERNAME || secrets.TEST_GITHUB_USERNAME }} password: ${{ env.production == 'true' && secrets.GIT_HUB_TOKEN || secrets.TEST_GITHUB_TOKEN }} - - - name: Generate tags and prepare metadata to build and push + - name: Generate tags and prepare metadata to build and push if: env.check_update != 0 id: meta uses: docker/metadata-action@v5 @@ -428,8 +321,7 @@ jobs: type=raw,value=${{ matrix.version_major }}${{ env.version_minor }},enable=true type=raw,value=${{ matrix.version_major }}${{ env.version_minor }}-${{ env.date_stamp }},enable=true - - - name: Build images + - name: Build images if: env.check_update != 0 id: build-images uses: docker/build-push-action@v5 @@ -442,8 +334,7 @@ jobs: load: true tags: ${{ steps.meta.outputs.tags }} - - - name: Test images + - name: Test images if: env.check_update != 0 id: test-images run: | @@ -459,8 +350,7 @@ jobs: && ( test "${{ matrix.image_types }}" != "micro" && rpm -q gpg-pubkey) || true " done - - - name: Detect previous released tag for this image type + - name: Detect previous released tag for this image type if: env.check_update != 0 id: detect-prev env: @@ -483,8 +373,7 @@ jobs: echo "prev_found=false" >> "$GITHUB_OUTPUT" fi - - - name: Install crane + - name: Install crane if: env.check_update != 0 run: | set -euo pipefail @@ -503,8 +392,7 @@ jobs: # 3. Diff the two SBOMs to produce a Markdown changelog # - - - name: Generate SBOM per platform (new image) + - name: Generate SBOM per platform (new image) if: env.check_update != 0 id: sbom-generate env: @@ -558,8 +446,7 @@ jobs: rm -rf "${rootfs_dir}" done - - - name: Fetch previous SBOM from image label (if exists) + - name: Fetch previous SBOM from image label (if exists) if: env.check_update != 0 id: sbom-fetch-prev env: @@ -606,8 +493,7 @@ jobs: echo "prev_sbom_found=true" >> "$GITHUB_OUTPUT" - - - name: Diff SBOMs and build Markdown changelog + - name: Diff SBOMs and build Markdown changelog if: env.check_update != 0 id: sbom-diff env: @@ -728,8 +614,7 @@ jobs: mkdir -p rpm_diff_artifacts/${VMJ} mv "${out}" "rpm_diff_artifacts/${VMJ}/rpm_diff_${VMJ}_${IMT}.md" - - - name: Build SBOM label payload for image + - name: Build SBOM label payload for image if: env.check_update != 0 id: build-sbom-label env: @@ -794,24 +679,21 @@ jobs: jq 'del(.platforms[]?.sbom) | .platforms |= with_entries(.value |= {sbom_sha256:.sbom_sha256, package_count:.package_count})' \ "sbom_label_${VMJ}_${IMT}.json" || true - - - name: Upload SBOM artifacts + - name: Upload SBOM artifacts if: env.check_update != 0 uses: actions/upload-artifact@v4 with: name: sbom-${{ matrix.version_major }}-${{ matrix.image_types }} path: sbom_output/${{ matrix.version_major }}/${{ matrix.image_types }}/ - - - name: Upload RPM diff artifacts + - name: Upload RPM diff artifacts if: env.check_update != 0 uses: actions/upload-artifact@v4 with: name: rpm-diff-${{ matrix.version_major }}-${{ matrix.image_types }} path: rpm_diff_artifacts/${{ matrix.version_major }}/ - - - name: Push images to Client Library + - name: Push images to Client Library if: env.check_update != 0 id: push-images uses: docker/build-push-action@v5 @@ -825,8 +707,7 @@ jobs: labels: | ${{ env.LABEL_KEY }}=${{ env.SBOM_LABEL }} - - - name: Prepare Mattermost message + - name: Prepare Mattermost message if: env.check_update != 0 run: | # Mattermost message heading @@ -858,8 +739,7 @@ jobs: name: mattermost-message-${{ matrix.version_major }}-${{ matrix.image_types }} path: ${{ matrix.version_major }}*_mattermost.md - - - name: Extract RootFS (default and minimal only) + - name: Extract RootFS (default and minimal only) id: extract-rootfs # 'default' or 'minimal' images only go to Docker Official Library if: ( matrix.image_types == 'default' || matrix.image_types == 'minimal' ) && env.check_update != 0 @@ -942,8 +822,7 @@ jobs: ls -1 ${pwd}/${{ matrix.version_major }}/${{ matrix.image_types }}/*/*.tar.xz # Change date stamp in '${version_major}/${image_types}/${arch}/Dockerfile' - - - name: Change date stamp in Dockerfile (default and minimal only) + - name: Change date stamp in Dockerfile (default and minimal only) # 'default' or 'minimal' images only go to Docker Official Library if: ( matrix.image_types == 'default' || matrix.image_types == 'minimal' ) && env.check_update != 0 run: | @@ -975,8 +854,7 @@ jobs: done # Commit '${version_major}/${image_types}/${arch}/*' - - - name: "Commit and push ${{ matrix.image_types }}/*/* Dockerfile and RootFS (branch ${{ matrix.version_major }})" + - name: "Commit and push ${{ matrix.image_types }}/*/* Dockerfile and RootFS (branch ${{ matrix.version_major }})" # 'default' or 'minimal' images only and 'Push to production' is checked if: ( matrix.image_types == 'default' || matrix.image_types == 'minimal' ) && env.production == 'true' && env.check_update != 0 uses: EndBug/add-and-commit@v9 @@ -984,7 +862,7 @@ jobs: default_author: user_info new_branch: ${{ matrix.version_major }} cwd: ${{ matrix.version_major }} - pull: '--rebase --autostash' + pull: "--rebase --autostash" message: "AlmaLinux ${{ matrix.version_major }} ${{ matrix.image_types }} - ${{ env.date_stamp }} ${{ env.time_stamp }} (generated on ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})." push: true @@ -993,8 +871,7 @@ jobs: runs-on: ubuntu-24.04 needs: [init-data, build-test-push] steps: - - - name: Download all RPM diff artifacts (if any) + - name: Download all RPM diff artifacts (if any) uses: actions/download-artifact@v4 with: pattern: rpm-diff-* @@ -1002,8 +879,7 @@ jobs: path: _rpm_diff_all continue-on-error: true - - - name: Download all SBOM artifacts (if any) + - name: Download all SBOM artifacts (if any) uses: actions/download-artifact@v4 with: pattern: sbom-* @@ -1011,8 +887,7 @@ jobs: path: _sbom_all continue-on-error: true - - - name: Check artifacts existence + - name: Check artifacts existence id: chk run: | set -euo pipefail @@ -1024,8 +899,7 @@ jobs: fi echo "found=true" >> "$GITHUB_OUTPUT" - - - name: Compose release notes (group by version & type) + - name: Compose release notes (group by version & type) if: steps.chk.outputs.found == 'true' id: notes run: | @@ -1101,8 +975,7 @@ jobs: echo "tag=v${DATE_STAMP}" >> "$GITHUB_OUTPUT" echo "name=AlmaLinux Container RPM diffs (${DATE_STAMP})" >> "$GITHUB_OUTPUT" - - - name: Create/Update GitHub Release + - name: Create/Update GitHub Release if: steps.chk.outputs.found == 'true' uses: softprops/action-gh-release@v2 with: @@ -1178,16 +1051,13 @@ jobs: version_major: ${{ fromJSON(needs.init-data.outputs.version_major_matrix) }} steps: - - - - name: Checkout ${{ github.repository }}, branch '${{ matrix.version_major }}', path '${{ matrix.version_major }}' + - name: Checkout ${{ github.repository }}, branch '${{ matrix.version_major }}', path '${{ matrix.version_major }}' uses: actions/checkout@v4 with: ref: ${{ matrix.version_major }} path: ${{ matrix.version_major }} - - - name: Optimize size of branch the '${{ matrix.version_major }}' + - name: Optimize size of branch the '${{ matrix.version_major }}' run: | date_stamp=${{ needs.init-data.outputs.date_stamp }} cd ${{ matrix.version_major }} @@ -1219,11 +1089,10 @@ jobs: echo "[Debug]" git status - - - name: Commit and push ${{ github.repository }}, branch '${{ matrix.version_major }}' + - name: Commit and push ${{ github.repository }}, branch '${{ matrix.version_major }}' uses: EndBug/add-and-commit@v9 with: default_author: user_info message: "Update AlmaLinux ${{ matrix.version_major }} - ${{ needs.init-data.outputs.date_stamp }} ${{ needs.init-data.outputs.time_stamp }} (generated on ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})." - push: '--force --set-upstream origin ${{ matrix.version_major }}' - cwd: ${{ matrix.version_major }} \ No newline at end of file + push: "--force --set-upstream origin ${{ matrix.version_major }}" + cwd: ${{ matrix.version_major }} From 1f2b4b2af83cd8bd5308c239b761f7b149232b8f Mon Sep 17 00:00:00 2001 From: unixtech Date: Thu, 12 Feb 2026 12:12:22 +0900 Subject: [PATCH 18/25] fix: use sudo rm -rf for rootfs cleanup --- .github/workflows/build-test-push.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index d150a50..153e4e4 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -424,7 +424,7 @@ jobs: -o "${metadata_file}" \ --verbose || { echo "Warning: sbom_data_collector failed for ${plat}, skipping" - rm -rf "${rootfs_dir}" + sudo rm -rf "${rootfs_dir}" continue } @@ -436,14 +436,14 @@ jobs: "${metadata_file}" \ "${sbom_file}" || { echo "Error: sbom_generator failed for ${plat}, skipping" - rm -rf "${rootfs_dir}" + sudo rm -rf "${rootfs_dir}" continue } echo "[Debug] SBOM generated: ${sbom_file} ($(wc -c < "${sbom_file}") bytes)" # Clean up rootfs - rm -rf "${rootfs_dir}" + sudo rm -rf "${rootfs_dir}" done - name: Fetch previous SBOM from image label (if exists) From ce3ef13b0768ede1e4fa342b6e8e21aa1f0f0047 Mon Sep 17 00:00:00 2001 From: unixtech Date: Thu, 12 Feb 2026 12:22:38 +0900 Subject: [PATCH 19/25] fix: update registries_testing to own quay.io namespace --- .github/workflows/build-test-push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index 153e4e4..ce94b28 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -85,7 +85,7 @@ env: # Registries lists registries_production: "docker.io/almalinux, quay.io/almalinuxorg, ghcr.io/almalinux" - registries_testing: "quay.io/almalinuxautobot" + registries_testing: "quay.io/ryosuke_666" # SBOM tools repository sbom_tools_repo: "https://github.com/AlmaLinux/cloud-images-sbom-tools.git" From 49ecc3b6276e71464f3f95cc49930822d3b99e55 Mon Sep 17 00:00:00 2001 From: unixtech Date: Tue, 17 Feb 2026 21:16:24 +0900 Subject: [PATCH 20/25] fix: use Fedora container for SBOM generation Ubuntu 24.04's rpm 4.18 cannot read AlmaLinux 10's SQLite-based RPM database (rpmdb.sqlite), resulting in empty SBOMs. - Add sbom_fedora_image env var (fedora:43) for easy version pinning - Remove host-side apt-get install of python3-rpm/dnf/rpm - Run sbom_data_collector.py and sbom_generator.py inside Fedora container with rootfs mounted as read-only volume --- .github/workflows/build-test-push.yml | 58 ++++++++++++++------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index ce94b28..2b3d712 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -90,6 +90,9 @@ env: # SBOM tools repository sbom_tools_repo: "https://github.com/AlmaLinux/cloud-images-sbom-tools.git" + # Fedora image for SBOM generation (needs rpm 4.19+ for SQLite RPM DB support) + sbom_fedora_image: "fedora:43" + jobs: init-data: name: Set matrix, prepare variables @@ -263,18 +266,11 @@ jobs: ref: ${{ matrix.version_major }} path: ${{ matrix.version_major }} - - name: Clone SBOM tools and install dependencies on host + - name: Clone SBOM tools if: env.check_update != 0 run: | git clone ${{ env.sbom_tools_repo }} /tmp/sbom-tools - # Install RPM/DNF Python bindings on Ubuntu - sudo apt-get update -qq - sudo apt-get install -y -qq python3-rpm python3-dnf rpm - - # Install sbom-tools Python dependencies - pip install --break-system-packages -r /tmp/sbom-tools/requirements.txt - - name: Set up QEMU if: env.check_update != 0 uses: docker/setup-qemu-action@v3 @@ -401,6 +397,7 @@ jobs: VMJ: ${{ matrix.version_major }} IMT: ${{ matrix.image_types }} DTS: ${{ needs.init-data.outputs.date_time_stamp }} + FEDORA_IMAGE: ${{ env.sbom_fedora_image }} run: | set -euo pipefail mkdir -p sbom_output/${VMJ}/${IMT} @@ -417,30 +414,37 @@ jobs: docker export "${container_id}" | tar -xf - -C "${rootfs_dir}" 2>/dev/null || true docker rm "${container_id}" > /dev/null - # Step 1: Collect metadata using host's rpm/dnf bindings with --root - metadata_file="sbom_output/${VMJ}/${IMT}/metadata_${key}.json" - python3 /tmp/sbom-tools/sbom_data_collector.py \ - --root "${rootfs_dir}" \ - -o "${metadata_file}" \ - --verbose || { - echo "Warning: sbom_data_collector failed for ${plat}, skipping" - sudo rm -rf "${rootfs_dir}" - continue - } - - # Step 2: Generate SPDX SBOM from collected metadata - sbom_file="sbom_output/${VMJ}/${IMT}/sbom_${key}.spdx.json" + # Run SBOM generation inside a Fedora container. + # Ubuntu's rpm 4.18 cannot read AlmaLinux 10's SQLite RPM DB; + # Fedora's rpm 4.19+ has native SQLite support. + sbom_host_dir="$(pwd)/sbom_output/${VMJ}/${IMT}" + metadata_file="metadata_${key}.json" + sbom_file="sbom_${key}.spdx.json" image_name="AlmaLinux-${VMJ}-${IMT}-${plat//\//-}" - python3 /tmp/sbom-tools/sbom_generator.py \ - "${image_name}" \ - "${metadata_file}" \ - "${sbom_file}" || { - echo "Error: sbom_generator failed for ${plat}, skipping" + + docker run --rm \ + -v "${rootfs_dir}:/mnt/rootfs:ro" \ + -v /tmp/sbom-tools:/tmp/sbom-tools:ro \ + -v "${sbom_host_dir}:/output" \ + "${FEDORA_IMAGE}" \ + bash -c ' + dnf install -y python3-rpm python3-dnf python3-pip --quiet && \ + pip install --root-user-action=ignore -r /tmp/sbom-tools/requirements.txt --quiet && \ + python3 /tmp/sbom-tools/sbom_data_collector.py \ + --root /mnt/rootfs \ + -o /output/'"${metadata_file}"' \ + --verbose && \ + python3 /tmp/sbom-tools/sbom_generator.py \ + '"\"${image_name}\""' \ + /output/'"${metadata_file}"' \ + /output/'"${sbom_file}"' + ' || { + echo "Warning: SBOM generation failed for ${plat}, skipping" sudo rm -rf "${rootfs_dir}" continue } - echo "[Debug] SBOM generated: ${sbom_file} ($(wc -c < "${sbom_file}") bytes)" + echo "[Debug] SBOM generated: ${sbom_host_dir}/${sbom_file} ($(wc -c < "${sbom_host_dir}/${sbom_file}") bytes)" # Clean up rootfs sudo rm -rf "${rootfs_dir}" From badf899b9ddb6cc4dffdaf17c6977deed379d0bf Mon Sep 17 00:00:00 2001 From: unixtech Date: Tue, 17 Feb 2026 21:42:38 +0900 Subject: [PATCH 21/25] ci: reduce SBOM label log size --- .github/workflows/build-test-push.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index 2b3d712..5a495b0 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -674,9 +674,10 @@ jobs: done # Store the JSON directly in the label payload - SBOM_LABEL=$(cat "sbom_label_${VMJ}_${IMT}.json" | jq -c .) - echo "SBOM_LABEL=${SBOM_LABEL}" >> "$GITHUB_ENV" - echo "LABEL_KEY=${LABEL_KEY}" >> "$GITHUB_ENV" + SBOM_LABEL=$(jq -c . "sbom_label_${VMJ}_${IMT}.json") + echo "SBOM_LABEL<> "$GITHUB_ENV" + echo "$SBOM_LABEL" >> "$GITHUB_ENV" + echo "EOF" >> "$GITHUB_ENV" # Debug: show summary without full SBOM contents echo "[Debug] SBOM label schema/preview:" From 31d186c3ad04cbb2e82d0f56ae9caec2ce662312 Mon Sep 17 00:00:00 2001 From: unixtech Date: Tue, 17 Feb 2026 22:17:31 +0900 Subject: [PATCH 22/25] Fix SBOM upload failure by compressing output before upload --- .github/workflows/build-test-push.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index 5a495b0..a5d9c44 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -684,12 +684,16 @@ jobs: jq 'del(.platforms[]?.sbom) | .platforms |= with_entries(.value |= {sbom_sha256:.sbom_sha256, package_count:.package_count})' \ "sbom_label_${VMJ}_${IMT}.json" || true + - name: Compress SBOM output + run: | + tar -czf sbom_output_${{ matrix.version_major }}_${{ matrix.image_types }}.tar.gz \ + sbom_output/${{ matrix.version_major }}/${{ matrix.image_types }}/ + - name: Upload SBOM artifacts - if: env.check_update != 0 uses: actions/upload-artifact@v4 with: name: sbom-${{ matrix.version_major }}-${{ matrix.image_types }} - path: sbom_output/${{ matrix.version_major }}/${{ matrix.image_types }}/ + path: sbom_output_${{ matrix.version_major }}_${{ matrix.image_types }}.tar.gz - name: Upload RPM diff artifacts if: env.check_update != 0 From 54652dffb8fd90b8b7e931786747c23e84a53759 Mon Sep 17 00:00:00 2001 From: unixtech Date: Tue, 17 Feb 2026 22:33:07 +0900 Subject: [PATCH 23/25] Fix ARG_MAX error: remove full SBOM from image label, keep metadata only --- .github/workflows/build-test-push.yml | 33 ++++++--------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index a5d9c44..a99600c 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -630,8 +630,6 @@ jobs: LABEL_KEY: org.almalinux.sbom run: | set -euo pipefail - - # Create the top-level label JSON with metadata and per-platform SBOMs jq -n \ --arg vmj "${VMJ}" \ --arg imt "${IMT}" \ @@ -644,56 +642,39 @@ jobs: prev_tag: ( $prev | select(length>0) // null ), platforms: {} }' > sbom_label_${VMJ}_${IMT}.json - for p in ${PLATFORMS//,/ }; do plat=$(echo "$p" | xargs) key="${plat//\//_}" sbom_file="sbom_output/${VMJ}/${IMT}/sbom_${key}.spdx.json" - metadata_file="sbom_output/${VMJ}/${IMT}/metadata_${key}.json" [ -f "${sbom_file}" ] || continue - - # Compute SHA256 of the SBOM file sha=$(sha256sum "${sbom_file}" | awk '{print $1}') - - # Extract package count from SBOM pkg_count=$(jq '[.packages[]? | select(.SPDXID != "SPDXRef-DOCUMENT")] | length' "${sbom_file}") - - # Include the full SBOM as a nested JSON object in the label + # Metadata only; do not embed the full SBOM body tmp=$(mktemp) jq --arg plat "${plat}" \ --arg sha "${sha}" \ --argjson count "${pkg_count}" \ - --slurpfile sbom "${sbom_file}" \ '.platforms[$plat] = { sbom_sha256: $sha, - package_count: $count, - sbom: $sbom[0] + package_count: $count }' \ "sbom_label_${VMJ}_${IMT}.json" > "${tmp}" mv "${tmp}" "sbom_label_${VMJ}_${IMT}.json" done - - # Store the JSON directly in the label payload + # Lightweight label JSON into environment variable SBOM_LABEL=$(jq -c . "sbom_label_${VMJ}_${IMT}.json") echo "SBOM_LABEL<> "$GITHUB_ENV" echo "$SBOM_LABEL" >> "$GITHUB_ENV" echo "EOF" >> "$GITHUB_ENV" - - # Debug: show summary without full SBOM contents - echo "[Debug] SBOM label schema/preview:" - jq 'del(.platforms[]?.sbom) | .platforms |= with_entries(.value |= {sbom_sha256:.sbom_sha256, package_count:.package_count})' \ - "sbom_label_${VMJ}_${IMT}.json" || true - - - name: Compress SBOM output - run: | - tar -czf sbom_output_${{ matrix.version_major }}_${{ matrix.image_types }}.tar.gz \ - sbom_output/${{ matrix.version_major }}/${{ matrix.image_types }}/ + echo "[Debug] SBOM label payload:" + cat "sbom_label_${VMJ}_${IMT}.json" - name: Upload SBOM artifacts + if: env.check_update != 0 uses: actions/upload-artifact@v4 with: name: sbom-${{ matrix.version_major }}-${{ matrix.image_types }} - path: sbom_output_${{ matrix.version_major }}_${{ matrix.image_types }}.tar.gz + path: sbom_output/${{ matrix.version_major }}/${{ matrix.image_types }}/ - name: Upload RPM diff artifacts if: env.check_update != 0 From 8f1f63cff2b09774aa4ac1be1c8d882bb14c811c Mon Sep 17 00:00:00 2001 From: unixtech Date: Tue, 17 Feb 2026 22:42:09 +0900 Subject: [PATCH 24/25] Fix empty label key: hardcode org.almalinux.sbom in push step --- .github/workflows/build-test-push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index a99600c..aede5ca 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -695,7 +695,7 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: | - ${{ env.LABEL_KEY }}=${{ env.SBOM_LABEL }} + org.almalinux.sbom=${{ env.SBOM_LABEL }} - name: Prepare Mattermost message if: env.check_update != 0 From 3904596adf36aba226fb8d023c06f506c9943984 Mon Sep 17 00:00:00 2001 From: unixtech Date: Thu, 19 Feb 2026 13:23:31 +0900 Subject: [PATCH 25/25] Store full SBOM as OCI artifact via ORAS instead of embedding in image label --- .github/workflows/build-test-push.yml | 169 +++++++++++++++++++++++--- 1 file changed, 153 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-test-push.yml b/.github/workflows/build-test-push.yml index aede5ca..e0c3018 100644 --- a/.github/workflows/build-test-push.yml +++ b/.github/workflows/build-test-push.yml @@ -379,6 +379,16 @@ jobs: sudo tar -C /usr/local/bin -xzf /tmp/crane.tgz crane crane version + - name: Install ORAS + if: env.check_update != 0 + run: | + set -euo pipefail + ver=v1.2.2 + curl -sSL -o /tmp/oras.tgz \ + "https://github.com/oras-project/oras/releases/download/${ver}/oras_${ver#v}_linux_amd64.tar.gz" + sudo tar -C /usr/local/bin -xzf /tmp/oras.tgz oras + oras version + # # ── SBOM-based changelog generation ─────────────────────────────────── # @@ -450,7 +460,7 @@ jobs: sudo rm -rf "${rootfs_dir}" done - - name: Fetch previous SBOM from image label (if exists) + - name: Fetch previous SBOM from OCI artifact (if exists) if: env.check_update != 0 id: sbom-fetch-prev env: @@ -459,7 +469,6 @@ jobs: PLATFORMS: ${{ env.platforms }} VMJ: ${{ matrix.version_major }} IMT: ${{ matrix.image_types }} - LABEL_KEY: org.almalinux.sbom run: | set -euo pipefail mkdir -p sbom_prev/${VMJ}/${IMT} @@ -470,32 +479,88 @@ jobs: exit 0 fi - echo "Fetching previous SBOM label from ${IMG_PREV}:${TAG_PREV}..." - label_json=$(crane config "${IMG_PREV}:${TAG_PREV}" \ - | jq -r --arg key "${LABEL_KEY}" '.config.Labels[$key] // empty') || true + full_ref="${IMG_PREV}:${TAG_PREV}" + echo "Fetching previous SBOMs from OCI artifacts on ${full_ref}..." - if [ -z "${label_json}" ]; then - echo "No previous SBOM label found." + # Get manifest list for previous image + manifest_list=$(crane manifest "${full_ref}" 2>/dev/null) || { + echo "Could not fetch manifest for ${full_ref}" echo "prev_sbom_found=false" >> "$GITHUB_OUTPUT" exit 0 - fi + } - # The label contains per-platform SBOM data; extract each platform's SBOM + any_found=false for p in ${PLATFORMS//,/ }; do plat=$(echo "$p" | xargs) key="${plat//\//_}" - prev_sbom=$(echo "${label_json}" | jq -r --arg plat "${plat}" '.platforms[$plat].sbom // empty' 2>/dev/null || true) - if [ -n "${prev_sbom}" ]; then - echo "${prev_sbom}" | jq . > "sbom_prev/${VMJ}/${IMT}/sbom_${key}.spdx.json" 2>/dev/null || \ - echo "${prev_sbom}" > "sbom_prev/${VMJ}/${IMT}/sbom_${key}.spdx.json" - echo "Restored previous SBOM for ${plat}" + IFS='/' read -r os arch variant <<< "${plat}" + + # Find per-platform digest from manifest list + if echo "${manifest_list}" | jq -e '.manifests' >/dev/null 2>&1; then + if [ -n "${variant}" ]; then + plat_digest=$(echo "${manifest_list}" | jq -r \ + --arg os "${os}" --arg arch "${arch}" --arg var "${variant}" \ + '.manifests[] | select(.platform.os==$os and .platform.architecture==$arch and .platform.variant==$var) | .digest') + else + plat_digest=$(echo "${manifest_list}" | jq -r \ + --arg os "${os}" --arg arch "${arch}" \ + '.manifests[] | select(.platform.os==$os and .platform.architecture==$arch and (.platform.variant==null or .platform.variant=="")) | .digest') + fi else - echo "No previous SBOM entry for ${plat}" + plat_digest=$(crane digest "${full_ref}" 2>/dev/null) || true fi + + if [ -z "${plat_digest}" ]; then + echo "No digest for ${plat} in previous image, skipping" + continue + fi + + # Discover SBOM referrers + sbom_desc=$(oras discover \ + --artifact-type "application/spdx+json" \ + --output json \ + "${IMG_PREV}@${plat_digest}" 2>/dev/null) || { + echo "No referrers found for ${plat}" + continue + } + + # Get the most recent SBOM artifact digest + sbom_artifact_digest=$(echo "${sbom_desc}" | jq -r \ + '.manifests // [] | sort_by(.annotations["org.opencontainers.image.created"] // "") | last | .digest // empty') + + if [ -z "${sbom_artifact_digest}" ]; then + echo "No SBOM artifact found for ${plat}" + continue + fi + + # Pull the SBOM artifact + pull_dir=$(mktemp -d) + oras pull \ + --output "${pull_dir}" \ + "${IMG_PREV}@${sbom_artifact_digest}" 2>/dev/null || { + echo "Failed to pull SBOM artifact for ${plat}" + rm -rf "${pull_dir}" + continue + } + + # Find the SPDX JSON file in the pulled content + sbom_pulled=$(find "${pull_dir}" -name '*.spdx.json' -o -name '*.json' | head -1) + if [ -n "${sbom_pulled}" ]; then + cp "${sbom_pulled}" "sbom_prev/${VMJ}/${IMT}/sbom_${key}.spdx.json" + echo "Restored previous SBOM for ${plat} ($(wc -c < "${sbom_pulled}") bytes)" + any_found=true + else + echo "No SPDX JSON found in pulled artifact for ${plat}" + fi + rm -rf "${pull_dir}" done - echo "prev_sbom_found=true" >> "$GITHUB_OUTPUT" + if [ "${any_found}" = "true" ]; then + echo "prev_sbom_found=true" >> "$GITHUB_OUTPUT" + else + echo "prev_sbom_found=false" >> "$GITHUB_OUTPUT" + fi - name: Diff SBOMs and build Markdown changelog if: env.check_update != 0 @@ -640,6 +705,7 @@ jobs: image_type: $imt, built_at: $dts, prev_tag: ( $prev | select(length>0) // null ), + sbom_storage: "oci-referrer", platforms: {} }' > sbom_label_${VMJ}_${IMT}.json for p in ${PLATFORMS//,/ }; do @@ -697,6 +763,77 @@ jobs: labels: | org.almalinux.sbom=${{ env.SBOM_LABEL }} + - name: Push SBOM artifacts to registries (OCI referrers) + if: env.check_update != 0 + id: sbom-push-oci + env: + PLATFORMS: ${{ env.platforms }} + VMJ: ${{ matrix.version_major }} + IMT: ${{ matrix.image_types }} + IMAGE_NAMES: ${{ env.IMAGE_NAMES }} + run: | + set -euo pipefail + + for image_ref in ${IMAGE_NAMES//,/ }; do + image_ref=$(echo "$image_ref" | xargs) + tag="${VMJ}" + full_ref="${image_ref}:${tag}" + + echo "=== Processing ${full_ref} ===" + + # Get the manifest list to find per-platform digests + manifest_list=$(crane manifest "${full_ref}" 2>/dev/null) || { + echo "Warning: Could not fetch manifest for ${full_ref}, skipping" + continue + } + + for p in ${PLATFORMS//,/ }; do + plat=$(echo "$p" | xargs) + key="${plat//\//_}" + sbom_file="sbom_output/${VMJ}/${IMT}/sbom_${key}.spdx.json" + + [ -f "${sbom_file}" ] || { + echo "No SBOM for ${plat}, skipping" + continue + } + + # Parse platform into os/arch/variant + IFS='/' read -r os arch variant <<< "${plat}" + + # Extract the per-platform digest from manifest list + if echo "${manifest_list}" | jq -e '.manifests' >/dev/null 2>&1; then + if [ -n "${variant}" ]; then + plat_digest=$(echo "${manifest_list}" | jq -r \ + --arg os "${os}" --arg arch "${arch}" --arg var "${variant}" \ + '.manifests[] | select(.platform.os==$os and .platform.architecture==$arch and .platform.variant==$var) | .digest') + else + plat_digest=$(echo "${manifest_list}" | jq -r \ + --arg os "${os}" --arg arch "${arch}" \ + '.manifests[] | select(.platform.os==$os and .platform.architecture==$arch and (.platform.variant==null or .platform.variant=="")) | .digest') + fi + else + plat_digest=$(crane digest "${full_ref}" 2>/dev/null) || true + fi + + if [ -z "${plat_digest}" ]; then + echo "Warning: No digest found for ${plat} in ${full_ref}, skipping" + continue + fi + + echo "Pushing SBOM for ${plat} -> ${image_ref}@${plat_digest}" + + oras attach \ + --artifact-type "application/spdx+json" \ + "${image_ref}@${plat_digest}" \ + "${sbom_file}:application/spdx+json" || { + echo "Warning: Failed to push SBOM artifact for ${plat} to ${image_ref}, continuing" + continue + } + + echo "Successfully attached SBOM for ${plat} to ${image_ref}@${plat_digest}" + done + done + - name: Prepare Mattermost message if: env.check_update != 0 run: |