Skip to content

v4.2.0-rc1

v4.2.0-rc1 #8

name: Release Notes Diff
on:
release:
types:
- published
workflow_dispatch:
inputs:
tag:
description: "Release tag to (re)generate notes for"
required: true
type: string
permissions:
contents: write
actions: read
security-events: read
jobs:
generate-diff:
if: ${{ !github.event.release.prerelease && !github.event.release.draft }}
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ github.token }}
CURRENT_TAG: ${{ github.event.release.tag_name || inputs.tag }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Check for CVEs in standard k3s/k0s images
id: cve-check
run: |
tag="${CURRENT_TAG}"
# Find workflow runs for this tag from both release workflows
workflow_runs=$(gh api "repos/${{ github.repository }}/actions/runs?event=push&per_page=100" \
--jq "[.workflow_runs[] | select(.head_branch == \"$tag\" and (.name | test(\"Release (AMD64|ARM)\")))]")
if [ -z "$workflow_runs" ] || [ "$workflow_runs" = "[]" ]; then
echo "No matching workflow runs found for tag $tag"
echo "has_cve=false" >> "$GITHUB_OUTPUT"
exit 0
fi
cve_warning=""
has_k3s_cve=false
has_k0s_cve=false
# Check each workflow run for failed or successful jobs with CVE indicators
for run_id in $(echo "$workflow_runs" | jq -r '.[].id'); do
jobs=$(gh api "repos/${{ github.repository }}/actions/runs/${run_id}/jobs?per_page=100" --jq '.jobs')
# Check for standard k3s jobs with security issues (jobs that have grype/trivy and are standard)
k3s_jobs=$(echo "$jobs" | jq -r '[.[] | select(.name | test("standard.*k3s"; "i"))]')
k0s_jobs=$(echo "$jobs" | jq -r '[.[] | select(.name | test("standard.*k0s"; "i"))]')
# For report-only mode, we need to check the job logs or annotations for CVE findings
# Since we changed to report-only, jobs will succeed but may have CVE warnings in annotations
for job_id in $(echo "$k3s_jobs" | jq -r '.[].id // empty'); do
annotations=$(gh api "repos/${{ github.repository }}/check-runs/${job_id}/annotations" 2>/dev/null || echo "[]")
if echo "$annotations" | jq -e '.[] | select(.annotation_level == "warning" and (.message | test("CVE|vulnerability|critical|high"; "i")))' > /dev/null 2>&1; then
has_k3s_cve=true
fi
done
for job_id in $(echo "$k0s_jobs" | jq -r '.[].id // empty'); do
annotations=$(gh api "repos/${{ github.repository }}/check-runs/${job_id}/annotations" 2>/dev/null || echo "[]")
if echo "$annotations" | jq -e '.[] | select(.annotation_level == "warning" and (.message | test("CVE|vulnerability|critical|high"; "i")))' > /dev/null 2>&1; then
has_k0s_cve=true
fi
done
done
# Also check code scanning alerts for this ref
alerts=$(gh api "repos/${{ github.repository }}/code-scanning/alerts?ref=refs/tags/${tag}&per_page=100" 2>/dev/null || echo "[]")
if [ "$alerts" != "[]" ] && [ -n "$alerts" ]; then
# Check for k3s related CVEs
if echo "$alerts" | jq -e '.[] | select(.rule.description | test("k3s"; "i"))' > /dev/null 2>&1; then
has_k3s_cve=true
fi
# Check for k0s related CVEs
if echo "$alerts" | jq -e '.[] | select(.rule.description | test("k0s"; "i"))' > /dev/null 2>&1; then
has_k0s_cve=true
fi
fi
# Build warning message
if [ "$has_k3s_cve" = "true" ] || [ "$has_k0s_cve" = "true" ]; then
cve_warning="> [!WARNING]\n> **Security Notice:** The standard images in this release contain known CVEs from upstream components"
if [ "$has_k3s_cve" = "true" ] && [ "$has_k0s_cve" = "true" ]; then
cve_warning="${cve_warning} (k3s and k0s)."
elif [ "$has_k3s_cve" = "true" ]; then
cve_warning="${cve_warning} (k3s)."
else
cve_warning="${cve_warning} (k0s)."
fi
cve_warning="${cve_warning} These vulnerabilities originate from the Kubernetes distribution binaries and are outside our control. Please review the security scan results before deploying to production.\n"
echo "has_cve=true" >> "$GITHUB_OUTPUT"
# Use a delimiter to handle multiline output
{
echo "warning<<EOF"
echo -e "$cve_warning"
echo "EOF"
} >> "$GITHUB_OUTPUT"
else
echo "has_cve=false" >> "$GITHUB_OUTPUT"
fi
- name: Prepend CVE warning to release notes
if: ${{ steps.cve-check.outputs.has_cve == 'true' }}
run: |
warning_header="> [!WARNING]"
gh release view "$CURRENT_TAG" --repo "${{ github.repository }}" --json body --jq '.body // ""' > current_body.md
# Check if warning already exists
if grep -Fq "$warning_header" current_body.md; then
echo "CVE warning already present in release body. Skipping prepend."
exit 0
fi
{
echo '${{ steps.cve-check.outputs.warning }}'
cat current_body.md
} > updated_body.md
gh release edit "$CURRENT_TAG" --repo "${{ github.repository }}" --notes-file updated_body.md
echo "CVE warning prepended to release notes."
- name: Resolve previous final release tag
id: prev
run: |
# NOTE: per_page=100 reliably times out (HTTP 504) on this repo's releases
# endpoint and returns an HTML error page instead of JSON. Releases are
# returned newest-first, so a smaller page is enough to find the relevant
# releases. We also guard against a non-JSON response so a transient API
# hiccup skips diff generation instead of failing the release.
releases_json=$(gh api "repos/${{ github.repository }}/releases?per_page=50") || {
echo "Could not fetch releases (API error). Skipping diff generation."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
}
if ! echo "$releases_json" | jq -e 'type == "array"' > /dev/null 2>&1; then
echo "Unexpected releases response (not a JSON array). Skipping diff generation."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# Resolve the final release that immediately precedes CURRENT_TAG in the
# newest-first list. This works for both the release:published trigger
# (where CURRENT_TAG is the newest release) and manual workflow_dispatch
# runs that target an older tag. If CURRENT_TAG is not among the recent
# final releases (e.g. too old for this page), we skip rather than diff
# against the wrong base.
previous_tag=$(echo "$releases_json" | jq -r --arg current "$CURRENT_TAG" '[.[] | select(.draft == false and .prerelease == false) | .tag_name] as $finals | ($finals | index($current)) as $i | (if $i == null then empty else $finals[$i + 1] end) // empty')
if [ -z "$previous_tag" ] || [ "$previous_tag" = "null" ]; then
echo "No previous final release found for $CURRENT_TAG. Skipping diff generation."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Previous final release: $previous_tag"
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "tag=$previous_tag" >> "$GITHUB_OUTPUT"
- name: Generate release diff markdown
if: ${{ steps.prev.outputs.skip == 'false' }}
run: |
output_file="RELEASE_DIFF_${CURRENT_TAG}.md"
./scripts/kairos-diff.sh "${{ steps.prev.outputs.tag }}" "$CURRENT_TAG" --output "$output_file"
echo "output_file=$output_file" >> "$GITHUB_ENV"
- name: Append diff to release description
if: ${{ steps.prev.outputs.skip == 'false' }}
id: append
run: |
header="# Changes since previous version (${{ steps.prev.outputs.tag }})"
gh release view "$CURRENT_TAG" --repo "${{ github.repository }}" --json body --jq '.body // ""' > current_body.md
if grep -Fq "$header" current_body.md; then
echo "Header already present in release body. Skipping append."
echo "appended=false" >> "$GITHUB_OUTPUT"
exit 0
fi
{
cat current_body.md
echo
echo "$header"
echo
cat "${output_file}"
} > combined_body.md
gh release edit "$CURRENT_TAG" --repo "${{ github.repository }}" --notes-file combined_body.md
echo "appended=true" >> "$GITHUB_OUTPUT"
- name: Write workflow summary
if: ${{ steps.prev.outputs.skip == 'false' }}
run: |
{
echo "## Release diff generated"
echo
echo "- Current release: \`${CURRENT_TAG}\`"
echo "- Previous final release: \`${{ steps.prev.outputs.tag }}\`"
if [ "${{ steps.append.outputs.appended }}" = "true" ]; then
echo "- Release description updated: \`yes\`"
else
echo "- Release description updated: \`no (already contained section header)\`"
fi
} >> "$GITHUB_STEP_SUMMARY"