Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
42 changes: 41 additions & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ defaults:
# before anything is published: what was validated is what ships. Tags are
# created only by cut-release / tag-release with the release App token.
#
# A caller that adds jobs of its own after this workflow (needs: release) reads
# kind/version/branch/last_rc/sha/digests/draft_url from the outputs and sets
# notify: false to post one Slack summary for the whole run itself.
#
# Harbor is reachable only from the in-network runner (`runner` input); every
# job that pushes to or pulls from Harbor runs there, and arm64 is built under
# QEMU on that runner. GitHub-hosted runners do the rest.
Expand Down Expand Up @@ -53,6 +57,21 @@ on:
required: false
default: ""
type: string
chart_repos:
description: "Helm repositories the chart's dependencies come from, name=url space separated, e.g. 'nfd=https://kubernetes-sigs.github.io/node-feature-discovery/charts'; empty adds none"
required: false
default: ""
type: string
release_notes_extra:
description: "File in the caller repository appended to the release notes: a script (#!, executable) is run in the checkout with VERSION, HARBOR_REGISTRY, DOCKERHUB_REGISTRY, IMAGES and CHART_DIR set and its output appended, a markdown file is appended as-is, e.g. a table of the images this component pins; empty appends nothing"
required: false
default: ""
type: string
notify:
description: "Post the kit's Slack summaries (rc published, GA). Set false when the caller adds jobs after this workflow and posts one summary for the whole run itself"
required: false
default: true
type: boolean
runner:
description: "Runner label with network access to Harbor"
required: false
Expand Down Expand Up @@ -203,6 +222,14 @@ jobs:
mkdir -p "$HOME/.local/bin" && echo "$HOME/.local/bin" >> "$GITHUB_PATH"
curl -sSL "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" -o "$HOME/.local/bin/yq"
chmod +x "$HOME/.local/bin/yq"
# Chart.lock records dependency repositories by URL, but on a fresh runner
# helm dependency build still refuses a URL it has no named repository for.
- name: Add chart repositories
if: inputs.chart_repos != ''
env:
CHART_REPOS: ${{ inputs.chart_repos }}
run: |
for r in $CHART_REPOS; do helm repo add --force-update "${r%%=*}" "${r#*=}"; done
# The rc chart must resolve this component's image from Harbor; the caller
# says how through chart_rc_values because values layouts differ per chart.
- name: Point the rc chart at Harbor
Expand Down Expand Up @@ -246,6 +273,7 @@ jobs:
- name: Release notes since the previous rc (or the previous GA)
env:
VERSION: ${{ needs.classify.outputs.version }}
RELEASE_NOTES_EXTRA: ${{ inputs.release_notes_extra }}
run: |
set -euo pipefail
. .release-kit/scripts/lib.sh
Expand Down Expand Up @@ -276,7 +304,7 @@ jobs:
rc-notify:
name: rc summary -> Slack
needs: [classify, rc-images, rc-chart, rc-prerelease]
if: always() && needs.classify.outputs.kind == 'rc'
if: always() && needs.classify.outputs.kind == 'rc' && inputs.notify
runs-on: ubuntu-latest
steps:
- name: Check out the release kit
Expand Down Expand Up @@ -362,6 +390,14 @@ jobs:
mkdir -p "$HOME/.local/bin" && echo "$HOME/.local/bin" >> "$GITHUB_PATH"
curl -sSL "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" -o "$HOME/.local/bin/yq"
chmod +x "$HOME/.local/bin/yq"
# Chart.lock records dependency repositories by URL, but on a fresh runner
# helm dependency build still refuses a URL it has no named repository for.
- name: Add chart repositories
if: inputs.chart_repos != ''
env:
CHART_REPOS: ${{ inputs.chart_repos }}
run: |
for r in $CHART_REPOS; do helm repo add --force-update "${r%%=*}" "${r#*=}"; done
- name: Package chart (pristine values)
run: |
set -euo pipefail
Expand Down Expand Up @@ -401,6 +437,9 @@ jobs:
# The previous GA is the highest GA tag below this one, not "the tag before
# this one in history": release branches make git describe unreliable.
- name: Generate release notes
env:
VERSION: ${{ needs.classify.outputs.version }}
RELEASE_NOTES_EXTRA: ${{ inputs.release_notes_extra }}
run: |
set -euo pipefail
. .release-kit/scripts/lib.sh
Expand Down Expand Up @@ -443,6 +482,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh label delete "backport ${{ needs.classify.outputs.branch }}" --yes --repo "$GITHUB_REPOSITORY" || echo "::notice::label already gone"
- name: Notify Slack
if: inputs.notify
env:
SLACK_OAUTH_TOKEN: ${{ secrets.SLACK_OAUTH_TOKEN }}
SLACK_CHANNEL_ID: ${{ vars.CLOUD_COMPONENT_SLACK_CHANNEL_ID }}
Expand Down
18 changes: 14 additions & 4 deletions scripts/release-notes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
# - a main commit whose sha appears as a trailer in the previous GA's own
# backports: that fix already shipped in the previous release.
# Commits are grouped by Conventional Commit type. If RELEASE_NOTES_EXTRA names
# a file, its content is appended after Known Issues (repositories that pin
# other images, like the operator, put their component table there).
# a file, it is appended after Known Issues: a script (first line "#!") is run
# and its output appended, anything else is appended as-is (repositories that
# pin other images, like the operator, generate their component table there).
# A script without the executable bit, or a path that is not a file, fails.

set -euo pipefail
# shellcheck source=lib.sh
Expand Down Expand Up @@ -83,8 +85,16 @@ improvements=$(for t in docs refactor perf style build chore test ci; do get "$t
echo "## Known Issues"
echo "- TBD"
echo
if [ -n "${RELEASE_NOTES_EXTRA:-}" ] && [ -f "$RELEASE_NOTES_EXTRA" ]; then
cat "$RELEASE_NOTES_EXTRA"
if [ -n "${RELEASE_NOTES_EXTRA:-}" ]; then
extra=$RELEASE_NOTES_EXTRA
[ -f "$extra" ] || fail "RELEASE_NOTES_EXTRA '$extra' is not a file"
if [ "$(head -c 2 "$extra")" = '#!' ]; then
[ -x "$extra" ] || fail "RELEASE_NOTES_EXTRA '$extra' is a script but not executable (chmod +x)"
# A bare name would be looked up on PATH instead of in the checkout.
case $extra in */*) "$extra" ;; *) "./$extra" ;; esac
else
cat "$extra"
fi
echo
fi
if [ -n "$prev" ]; then
Expand Down
12 changes: 11 additions & 1 deletion templates/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,19 @@ jobs:
images: CHANGE-ME
# Prefix of the GitHub release title.
release_name: CHANGE-ME
# Helm chart directory, or remove both chart lines if there is no chart.
# Helm chart directory, or remove the chart_* lines if there is no chart.
chart_dir: ""
# yq expression that points the rc chart's image at Harbor, e.g.
# '.image.registry = env(HARBOR_HOST)'.
chart_rc_values: ""
# Helm repositories the chart's dependencies come from, "name=url" space
# separated; empty when the chart has no dependencies.
chart_repos: ""
# Script (#!, executable) whose output, or markdown file whose content, is
# appended to the release notes, e.g. a table of the images this component
# pins. Empty for none.
release_notes_extra: ""
# Set false when this repository adds its own jobs after `release` and
# posts one Slack summary for the whole run itself.
notify: true
secrets: inherit
52 changes: 52 additions & 0 deletions tests/release-notes.bats
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,55 @@ teardown() { teardown_repo; }
grep -q 'feat: first' "$TEST_TMP/notes.md"
grep -q '_Generated for v0.1.0._' "$TEST_TMP/notes.md"
}

@test "an executable RELEASE_NOTES_EXTRA is run and its output appended" {
commit "feat: first" a.txt >/dev/null
cut v0.1.0
git tag -a v0.1.0 -m ga
printf '#!/usr/bin/env bash\necho "| generated | $GITHUB_REPOSITORY |"\n' >"$TEST_TMP/extra.sh"
chmod +x "$TEST_TMP/extra.sh"
RELEASE_NOTES_EXTRA="$TEST_TMP/extra.sh" run "$SCRIPTS/release-notes.sh" v0.1.0 "" "$TEST_TMP/notes.md"
[ "$status" -eq 0 ]
grep -q '| generated | acme/widget |' "$TEST_TMP/notes.md"
}

@test "a RELEASE_NOTES_EXTRA that is not a file fails" {
commit "feat: first" a.txt >/dev/null
cut v0.1.0
git tag -a v0.1.0 -m ga
RELEASE_NOTES_EXTRA="$TEST_TMP/missing.md" run "$SCRIPTS/release-notes.sh" v0.1.0 "" "$TEST_TMP/notes.md"
[ "$status" -ne 0 ]
[[ "$output" == *"is not a file"* ]]
}

@test "a script given as a bare name runs from the checkout, not from PATH" {
commit "feat: first" a.txt >/dev/null
cut v0.1.0
git tag -a v0.1.0 -m ga
printf '#!/usr/bin/env bash\necho "| bare | name |"\n' >extra.sh
chmod +x extra.sh
RELEASE_NOTES_EXTRA=extra.sh run "$SCRIPTS/release-notes.sh" v0.1.0 "" "$TEST_TMP/notes.md"
[ "$status" -eq 0 ]
grep -q '| bare | name |' "$TEST_TMP/notes.md"
}

@test "a script without the executable bit fails instead of being pasted" {
commit "feat: first" a.txt >/dev/null
cut v0.1.0
git tag -a v0.1.0 -m ga
printf '#!/usr/bin/env bash\necho nope\n' >"$TEST_TMP/extra.sh"
RELEASE_NOTES_EXTRA="$TEST_TMP/extra.sh" run "$SCRIPTS/release-notes.sh" v0.1.0 "" "$TEST_TMP/notes.md"
[ "$status" -ne 0 ]
[[ "$output" == *"not executable"* ]]
}

@test "a markdown file with the executable bit is appended, not run" {
commit "feat: first" a.txt >/dev/null
cut v0.1.0
git tag -a v0.1.0 -m ga
echo "| plain | markdown |" >"$TEST_TMP/extra.md"
chmod +x "$TEST_TMP/extra.md"
RELEASE_NOTES_EXTRA="$TEST_TMP/extra.md" run "$SCRIPTS/release-notes.sh" v0.1.0 "" "$TEST_TMP/notes.md"
[ "$status" -eq 0 ]
grep -q '| plain | markdown |' "$TEST_TMP/notes.md"
}