Skip to content
Open
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
364 changes: 363 additions & 1 deletion .github/workflows/build-test-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -348,6 +347,254 @@ 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 }}
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 "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

-
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): <image>:<tag>
if [ -n "${IMG_PREV:-}" ] && [ -n "${TAG_PREV:-}" ]; then
docker pull --platform "${plat}" "${IMG_PREV}:${TAG_PREV}" >/dev/null 2>&1 || true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to run the previous image and extract the list of packages again, now that it's attached as a label you could just get it from there. If you use crane, you can even get the label without having to pull the image!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll switch to using crane.
It seems that crane isn’t included in the default ubuntu-latest runner image, so I’ll add installation steps for it as well.
https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md

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


# Collapse each platform's RPM diff into a <details> section
printf "\n<details>\n<summary>Platform: \`%s\`</summary>\n\n" "$plat" >> "$

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 ' ')

printed=false

# Added
if [ "$addc" -gt 0 ]; then
{
echo ""
echo "**Added (${addc})**"
echo ""
awk '{print "- " $0}' added.tmp
echo ""
} >> "${out}"
printed=true
fi

# Updated
if [ "$updc" -gt 0 ]; then
{
echo ""
echo "**Updated (${updc})**"
echo ""
awk -F'\t' '{printf "- %s: %s\n",$1,$2}' "${updated_tmp}"
echo ""
} >> "${out}"
printed=true
fi

# 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

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
# Close the <details> section for this platform
printf "\n</details>\n" >> "${out}"
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: 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dev.hdd.rechunk.info isn't a standardized label or anything, and given the format is different maybe we shouldn't use the same label. How about org.almalinux.sbom? Reference: https://github.com/opencontainers/image-spec/blob/main/annotations.md

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds good
I'll use org.almalinux.sbom instead.

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 "name<TAB>version-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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand wanting to reduce the size of this data, but making it binary makes it a bit less useful. It adds an extra couple of steps to be able to read and use it. There's no limit to the value size (as far as I can tell), so why not just store the json directly?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ll change it to store the JSON directly in the label instead of encoding it.

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
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
Expand All @@ -360,6 +607,8 @@ jobs:
platforms: ${{ env.platforms }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: |
${{ env.LABLE_KEY }}=${{ env.RCHUNK_INFO_LABEL }}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
${{ env.LABLE_KEY }}=${{ env.RCHUNK_INFO_LABEL }}
${{ env.LABEL_KEY }}=${{ env.RCHUNK_INFO_LABEL }}


-
name: Prepare Mattermost message
Expand Down Expand Up @@ -524,6 +773,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
Expand Down