feat(ci): the chart bump follows the app bump
Validate Helm Charts / Detect changed charts (pull_request) Successful in 5s
Validate Helm Charts / Build ${{ matrix.chart }} (pull_request) Skipped
Validate Helm Charts / Install-test (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 18s
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 14s
Validate Helm Charts / Bump level (pull_request) Successful in 7s
Validate Helm Charts / Trivy config cyberchef (pull_request) Successful in 20s
Validate Helm Charts / Trivy config matrix-synapse (pull_request) Successful in 22s
Validate Helm Charts / Trivy config samba (pull_request) Successful in 21s
SonarQube Analysis / SonarQube Analysis (push) Successful in 48s

The version-bump hook classified every app update that was not a major
as a chart patch, so an app minor arrived as a patch - the mechanism
behind the sonarr incident, where a security default was switched off
inside a bump the conventions told the reviewer not to read (Gitea #128).

ci/renovate-bump-chart.sh now takes the level from Renovate's update
type, passed in as a second argument: major -> major, minor -> MINOR,
patch/digest/pin -> patch. The level comes from Renovate rather than
from comparing appVersion because an appVersion comparison is blind to
the case that prompted the rule - freesailarr keeps a hand-maintained
appVersion for ten upstreams, and prowlarr's 2.5.2 -> 2.6.5 moved an
image tag in values.yaml while appVersion stood still.

Three properties come with it:

- Maximum, not replacement. The chart jump is the greater of the
  chart's own classification and the app jump, so a value that becomes
  required stays a major under an app patch. Mechanically it is also
  what makes a grouped branch deterministic: the hook runs once per
  update from the same base commit, so without it the last call would
  win.
- Non-semver app versions stay at patch for a minor (seven charts). In
  CalVer and date tags the second position is a month; in a four-part
  or suffixed version the positions are not semver positions. An app
  major is not subject to it.
- A missing or unknown update type falls back to the pre-#128
  behaviour. postUpgradeTasks only runs where the command is
  allow-listed on the shared instance, which is not visible from here
  and fails silently, so the worst case is the old classification
  rather than every chart PR red on the version check.

ci/run-bump-level-test.sh holds 49 cases against the version string the
script writes, wired up as the bump-level job: a wrong level is a valid
version string, so the PR would go green and the only symptom would be
a number that understates what it publishes.

The convention texts follow in CLAUDE.md and README, including the
limit the decision deliberately keeps: the reading duty was rejected,
so the incident stays repeatable and the honest number is the whole of
the remedy.

Fixes #128
This commit was merged in pull request #130.
This commit is contained in:
jklein committed 2026-10-13 18:47:44 +00:00
1 parent 978e6fbaa8
commit 59d2614384
6 files changed
+548 -55

No files matched your search

+189 -33
View File
@@ -11,23 +11,56 @@
# failing the `build-chart` version-bump check. This script closes that gap and
# is wired up as a `postUpgradeTasks` command in renovate.json5.
#
# Usage: ci/renovate-bump-chart.sh <chart-directory>
# Usage: ci/renovate-bump-chart.sh <chart-directory> [update-type]
#
# Bump level (README "Versioning"):
# - app major changed -> chart major, minor/patch reset to 0
# - anything else -> chart patch (plain app updates, sidecar images)
# Bump level (README "Versioning", decided in Gitea #128):
# - update type major -> chart major, minor/patch reset to 0
# - update type minor -> chart MINOR, patch reset to 0
# - patch / digest / pin -> chart patch
# - no or unknown update type -> the appVersion comparison below, i.e. the
# behaviour this script had before #128
# - and never BELOW what the branch already carries (see the maximum rule
# further down)
#
# THE UPDATE TYPE COMES FROM RENOVATE, AND THAT IS THE POINT (Gitea #128).
# Renovate has already classified the jump; passing `{{{updateType}}}` in is
# what makes a chart minor possible at all. Deriving it here from appVersion
# would be blind to exactly the case that prompted the rule: `freesailarr`
# carries a hand-maintained `appVersion: "1.0.0"` for ten upstreams and it never
# moves, so prowlarr's 2.5.2 -> 2.6.5 — an image tag in values.yaml — is a
# minor with an unchanged appVersion. README, "Dependency updates (Renovate)".
#
# WHAT A CHART MINOR BUYS, AND WHAT IT DOES NOT. It makes the number honest: an
# app minor stops arriving as a chart patch. It does NOT oblige anyone to read
# the release notes — that variant was decided against — so an app minor that
# changes a default can still reach `main` unremarked. What the number does is
# make the jump VISIBLE to everything downstream that sees only our chart
# version (Fleet's `fleet.yaml` in `cluster-config` among them), where a patch
# is invisible. See the wiki, Lessons-Learned.
#
# THE SECOND ARGUMENT IS OPTIONAL ON PURPOSE. `postUpgradeTasks` only runs when
# the command is allow-listed in the *instance* config, which is not visible
# from this repository, and a command that does not match the allow-list is
# skipped SILENTLY. So a missing update type — a narrower allow-list pattern, an
# older Renovate, a template field that is not populated — must not be a
# failure: without it the script falls back to what it did before #128 and every
# chart PR still gets its version bump.
#
# Everything is derived from the base commit (HEAD, which during
# postUpgradeTasks still points at the base branch), never from the working
# tree's `version:`. That makes repeated runs on the same branch idempotent —
# Renovate calls the script once per dependency update, and a branch may carry
# more than one update for the same chart.
# more than one update for the same chart. The one place the working tree IS
# read is the maximum rule below, and only to keep a higher version.
#
# Runs inside the Renovate container, so: POSIX sh, no bashisms, no jq/yq.
#
# Tested by ci/run-bump-level-test.sh, which is where the cases live.
set -eu
dir=${1:-}
update_type=${2:-}
[ -n "$dir" ] || exit 0
# Strip a trailing slash so "outline/" and "outline" behave the same.
@@ -49,6 +82,72 @@ field() {
sed -n "s/^$1:[[:space:]]*//p" | head -n 1 | tr -d "\"' "
}
# Splits "<major>.<minor>.<patch>" into p_major/p_minor/p_patch. Returns 1 if
# the string is not exactly three numeric parts, which is also how a four-part
# version (3.3.1.0) and a suffixed one (9.1.2-alpine) are rejected: the last
# part then is not numeric.
parse_semver() {
_sv=$1
case "$_sv" in
*.*.*) ;;
*) return 1 ;;
esac
p_major=${_sv%%.*}
_rest=${_sv#*.}
p_minor=${_rest%%.*}
p_patch=${_rest#*.}
for _part in "$p_major" "$p_minor" "$p_patch"; do
case "$_part" in
'' | *[!0-9]*) return 1 ;;
esac
done
return 0
}
# Is <a.b.c> strictly greater than <d.e.f>? Six arguments, componentwise.
semver_gt() {
if [ "$1" -ne "$4" ]; then
if [ "$1" -gt "$4" ]; then return 0; else return 1; fi
fi
if [ "$2" -ne "$5" ]; then
if [ "$2" -gt "$5" ]; then return 0; else return 1; fi
fi
if [ "$3" -gt "$6" ]; then return 0; else return 1; fi
}
# Does this app version's MINOR POSITION carry a statement about jump width?
# Only a plain [v]X.Y.Z does. Everything else keeps the pre-#128 behaviour, i.e.
# a patch, and the reason is in the convention rather than in a footnote: in
# CalVer and in date tags the second position is a MONTH, so following it would
# produce a chart minor every month that says nothing about the risk, and in a
# four-part or suffixed version the positions are not semver positions at all.
#
# Measured on origin/main, this covers seven charts and only those seven:
# rss-bridge 2026-09-30 (no dots at all)
# homeassistant 2026.8.3 (CalVer — caught by the year guard)
# searxng 2026.9.13-3b8009065 (CalVer plus a commit hash)
# minecraft-server 2026.8.2-java25 (CalVer plus a JRE suffix)
# apache-tika 3.3.1.0-full (four parts plus a flavour)
# teamspeak-server 6.0.0-beta13.1 (prerelease line)
# valkey 9.1.2-alpine (flavour suffix)
#
# The last two are the honest edge of this rule: their numeric part IS a semver
# line, so the fallback understates a real minor there. That is the decided
# trade — conservative by default, and a chart that wants the minor gets a
# documented exception rather than a widened pattern.
#
# A `major` update type is NOT subject to this: a year rolling over in a date
# tag arrives as a major, and that was a chart major before #128 too.
app_minor_is_meaningful() {
_av=${1#v}
_av=${_av#V}
parse_semver "$_av" || return 1
# A four-digit leading component from 2000 on is a calendar year, not a
# semver major.
if [ "$p_major" -ge 2000 ]; then return 1; fi
return 0
}
base=$(git show "HEAD:$chart" 2>/dev/null) || {
echo "renovate-bump: $chart is new on this branch, leaving its version alone"
exit 0
@@ -57,6 +156,7 @@ base=$(git show "HEAD:$chart" 2>/dev/null) || {
base_version=$(printf '%s\n' "$base" | field version)
base_app=$(printf '%s\n' "$base" | field appVersion)
new_app=$(field appVersion <"$chart")
tree_version=$(field version <"$chart")
if [ -z "$base_version" ] || [ -z "$new_app" ]; then
echo "renovate-bump: $chart has no version/appVersion to work with" >&2
@@ -65,27 +165,13 @@ fi
# <chart-semver> is everything before the +up build suffix.
semver=${base_version%%+*}
case "$semver" in
*.*.*) ;;
*)
echo "renovate-bump: cannot parse chart semver '$semver' in $chart" >&2
exit 1
;;
esac
major=${semver%%.*}
rest=${semver#*.}
minor=${rest%%.*}
patch=${rest#*.}
for part in "$major" "$minor" "$patch"; do
case "$part" in
'' | *[!0-9]*)
echo "renovate-bump: non-numeric component in chart semver '$semver'" >&2
exit 1
;;
esac
done
parse_semver "$semver" || {
echo "renovate-bump: cannot parse chart semver '$semver' in $chart" >&2
exit 1
}
major=$p_major
minor=$p_minor
patch=$p_patch
# Leading major of an app version: v1.159.0 -> 1, 3.3.1.0-full -> 3,
# 2025-06-03 -> 2025.
@@ -93,17 +179,87 @@ app_major() {
printf '%s' "$1" | sed -e 's/^[vV]//' -e 's/[^0-9].*$//'
}
if [ "$new_app" != "$base_app" ] && [ "$(app_major "$new_app")" != "$(app_major "$base_app")" ]; then
reason="app major $base_app -> $new_app"
new_semver="$((major + 1)).0.0"
else
reason="patch-level change"
new_semver="$major.$minor.$((patch + 1))"
# The pre-#128 level: app major changed -> major, anything else -> patch. Also
# the fallback whenever no usable update type arrives.
app_version_level() {
if [ "$new_app" != "$base_app" ] && [ "$(app_major "$new_app")" != "$(app_major "$base_app")" ]; then
echo major
else
echo patch
fi
}
case "$update_type" in
major)
level=major
reason="update type major"
;;
minor)
if app_minor_is_meaningful "$new_app"; then
level=minor
reason="update type minor"
else
level=$(app_version_level)
reason="update type minor, but appVersion '$new_app' is not a plain semver line -> $level"
fi
;;
patch | digest | pin | pinDigest)
level=patch
reason="update type $update_type"
;;
'')
level=$(app_version_level)
reason="no update type given, derived from appVersion -> $level"
;;
*)
level=$(app_version_level)
reason="unknown update type '$update_type', derived from appVersion -> $level"
;;
esac
case "$level" in
major)
new_major=$((major + 1))
new_minor=0
new_patch=0
;;
minor)
new_major=$major
new_minor=$((minor + 1))
new_patch=0
;;
*)
new_major=$major
new_minor=$minor
new_patch=$((patch + 1))
;;
esac
# THE MAXIMUM RULE (Gitea #128): the chart level is the GREATER of the chart's
# own classification and the app jump — never a replacement. Two cases, and the
# second is the mechanical one:
#
# - a chart-own bump already in the working tree stands. A value that becomes
# required is a chart major even when the app jump is a patch, and the chart
# version must not stop reporting the chart's own breaks.
# - a branch carrying SEVERAL updates. Renovate calls this script once per
# update and every call recomputes from the same base commit, so without
# this the last call would win: `freesailarr`'s grouped PR has arrived with
# three minors among five patches, and the chart level would then depend on
# the order the hook happened to run in.
#
# It only ever keeps a HIGHER version, so repeated runs stay idempotent.
if parse_semver "${tree_version%%+*}" &&
semver_gt "$p_major" "$p_minor" "$p_patch" "$new_major" "$new_minor" "$new_patch"; then
reason="$reason, kept the higher $p_major.$p_minor.$p_patch already on the branch"
new_major=$p_major
new_minor=$p_minor
new_patch=$p_patch
fi
# Dashes in the app version become dots in the +up suffix.
suffix=$(printf '%s' "$new_app" | tr '-' '.')
new_version="$new_semver+up$suffix"
new_version="$new_major.$new_minor.$new_patch+up$suffix"
sed -i.bak "s|^version:.*|version: $new_version|" "$chart"
rm -f "$chart.bak"
+229
View File
@@ -0,0 +1,229 @@
#!/usr/bin/env bash
# Bump-level test: every case of ci/renovate-bump-chart.sh, held against the
# version string it writes into a throwaway chart.
#
# Usage: ci/run-bump-level-test.sh
#
# WHY THIS TEST EXISTS (Gitea #128)
#
# Until #128 the hook was a calculator with one branch: app major -> chart
# major, everything else -> chart patch. With the update type coming from
# Renovate it became DECIDING LOGIC — it is now the thing that says how big a
# change the cluster is being offered, and the chart version is the only signal
# anything downstream sees (README, "Chart dependencies": Fleet's `fleet.yaml`
# in `cluster-config` sees our chart version and nothing inside the package).
#
# And its failure mode is silence. A wrong level is a valid version string: the
# PR goes green, the chart publishes, and the only symptom is a number that
# understates what it ships. Nothing in the pipeline reads it, so nothing but a
# test can.
#
# HOW A CASE IS BUILT, AND WHY A REAL GIT REPO
#
# The script reads the BASE commit (`git show HEAD:<chart>/Chart.yaml`) and the
# working tree, so a case is not a function call — it is a repository with a
# commit and a dirty file. Each case therefore gets a throwaway `git init` in a
# unique temp directory, one commit carrying the base Chart.yaml, and the new
# Chart.yaml written over it uncommitted. That is exactly the state Renovate's
# `postUpgradeTasks` hook runs in.
#
# Signing is switched off per command (`-c commit.gpgsign=false`): this
# workspace signs every commit through an SSH signer, and a throwaway repo has
# no business invoking it. The temp root comes from `mktemp -d`, because /tmp is
# shared with whatever else runs on this machine.
#
# The runners have no pyyaml, no yq and no Docker — the same constraint
# ci/run-selector-match-test.sh and ci/run-quantity-render-test.sh work under.
# None is needed: the fixtures are six-line Chart.yaml files written by printf,
# and the assertion is one `version:` line read back with sed. Helm is not
# needed either, which is why this job carries no Helm step.
#
# WHAT IS ASSERTED, AND WHAT IS NOT
#
# Asserted: the full `version:` line (so the `+up` suffix and its dash-to-dot
# normalization are covered by every case), the exit code, and for the cases
# that must leave the chart alone, that the line is untouched.
#
# NOT asserted, and not assertable from here: that Renovate passes the update
# type at all. The hook only runs when the command is allow-listed in the
# *instance* config, which lives outside this repository — and a pattern that
# does not match is skipped silently. That is why a missing second argument is
# itself a case here (it must fall back, not fail), and why the PR that
# introduced this carries a dry-run instead of a claim.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SCRIPT="$ROOT/ci/renovate-bump-chart.sh"
[ -x "$SCRIPT" ] || {
echo "FAIL: $SCRIPT is not executable" >&2
exit 1
}
TMP="$(mktemp -d "${TMPDIR:-/tmp}/bump-level-test.XXXXXX")"
trap 'rm -rf "$TMP"' EXIT
GIT_OPTS=(-c user.name=ci -c user.email=ci@example.invalid -c commit.gpgsign=false -c tag.gpgsign=false)
passed=0
failed=0
chart_yaml() { # <name> <version> <appVersion>
printf 'apiVersion: v2\nname: %s\ndescription: test fixture\ntype: application\nversion: %s\nappVersion: "%s"\nicon: https://example.com/icon.png\n' \
"$1" "$2" "$3"
}
# Builds a repository whose HEAD carries <base-content> for <dir>/Chart.yaml
# (skipped when empty, which is the "new chart on this branch" case) and whose
# working tree carries <tree-content>. Prints the repository path.
scenario() { # <dir> <base-content> <tree-content>
local dir=$1 base=$2 tree=$3 repo
repo="$(mktemp -d "$TMP/case.XXXXXX")"
git -C "$repo" init -q
printf 'root commit so HEAD exists\n' >"$repo/README.md"
git -C "$repo" add README.md
git -C "$repo" "${GIT_OPTS[@]}" commit -qm "root"
mkdir -p "$repo/$dir"
if [ -n "$base" ]; then
printf '%s' "$base" >"$repo/$dir/Chart.yaml"
git -C "$repo" add "$dir/Chart.yaml"
git -C "$repo" "${GIT_OPTS[@]}" commit -qm "base chart"
fi
[ -z "$tree" ] || printf '%s' "$tree" >"$repo/$dir/Chart.yaml"
printf '%s' "$repo"
}
version_of() { sed -n 's/^version:[[:space:]]*//p' "$1" | head -n 1; }
report() { # <name> <expected> <got> <expected-rc> <got-rc> <output>
if [ "$2" = "$3" ] && [ "$4" = "$5" ]; then
passed=$((passed + 1))
printf ' ok %s\n' "$1"
else
failed=$((failed + 1))
printf ' FAIL %s\n expected version %-24s rc %s\n got version %-24s rc %s\n output: %s\n' \
"$1" "$2" "$4" "$3" "$5" "$6"
fi
}
# The ordinary case: one chart, a base version, an app version that may or may
# not have moved, and an update type. UPDATE_TYPE of "-" calls the script with
# ONE argument (the allow-list / older-Renovate case), "" passes an empty
# second argument (a template field that resolved to nothing).
bump() { # <name> <base-version> <base-app> <tree-version> <tree-app> <update-type> <expected-version> [expected-rc]
local name=$1 base_version=$2 base_app=$3 tree_version=$4 tree_app=$5 utype=$6 expected=$7 rc_expected=${8:-0}
local repo out rc got
repo="$(scenario demo "$(chart_yaml demo "$base_version" "$base_app")" "$(chart_yaml demo "$tree_version" "$tree_app")")"
rc=0
if [ "$utype" = "-" ]; then
out="$(cd "$repo" && "$SCRIPT" demo 2>&1)" || rc=$?
else
out="$(cd "$repo" && "$SCRIPT" demo "$utype" 2>&1)" || rc=$?
fi
got="$(version_of "$repo/demo/Chart.yaml")"
report "$name" "$expected" "$got" "$rc_expected" "$rc" "$out"
}
echo "== update type decides the level (plain semver appVersion) =="
bump "major -> chart major" 1.2.3+up1.4.0 1.4.0 1.2.3+up1.4.0 2.0.0 major 2.0.0+up2.0.0
bump "minor -> chart minor" 1.2.3+up1.4.0 1.4.0 1.2.3+up1.4.0 1.5.0 minor 1.3.0+up1.5.0
bump "patch -> chart patch" 1.2.3+up1.4.0 1.4.0 1.2.3+up1.4.0 1.4.1 patch 1.2.4+up1.4.1
bump "digest -> chart patch" 1.2.3+up1.4.0 1.4.0 1.2.3+up1.4.0 1.4.0 digest 1.2.4+up1.4.0
bump "pin -> chart patch" 1.2.3+up1.4.0 1.4.0 1.2.3+up1.4.0 1.4.0 pin 1.2.4+up1.4.0
bump "pinDigest -> chart patch" 1.2.3+up1.4.0 1.4.0 1.2.3+up1.4.0 1.4.0 pinDigest 1.2.4+up1.4.0
bump "minor resets the patch" 1.2.3+up1.4.0 1.4.0 1.2.3+up1.4.0 1.5.0 minor 1.3.0+up1.5.0
bump "major resets minor and patch" 4.5.6+up1.4.0 1.4.0 4.5.6+up1.4.0 2.0.0 major 5.0.0+up2.0.0
echo "== the case that prompted the rule: appVersion never moves (freesailarr) =="
# prowlarr 2.5.2 -> 2.6.5 is an image tag in values.yaml; freesailarr's
# appVersion is a hand-maintained marker and stays at 1.0.0. An appVersion
# comparison sees nothing and would bump the patch.
bump "sidecar/bundle minor, appVersion unchanged" 3.1.1+up1.0.0 1.0.0 3.1.1+up1.0.0 1.0.0 minor 3.2.0+up1.0.0
bump "sidecar/bundle patch, appVersion unchanged" 3.1.1+up1.0.0 1.0.0 3.1.1+up1.0.0 1.0.0 patch 3.1.2+up1.0.0
echo "== non-semver app versions stay at patch (all seven on origin/main) =="
bump "rss-bridge date tag, month roll" 2.1.1+up2026.09.30 2026-09-30 2.1.1+up2026.09.30 2026-10-05 minor 2.1.2+up2026.10.05
bump "homeassistant CalVer" 6.2.0+up2026.8.3 2026.8.3 6.2.0+up2026.8.3 2026.9.0 minor 6.2.1+up2026.9.0
bump "searxng CalVer plus hash" 1.1.0+up2026.9.13.3b8009065 2026.9.13-3b8009065 1.1.0+up2026.9.13.3b8009065 2026.10.1-aa12bb34c minor 1.1.1+up2026.10.1.aa12bb34c
bump "minecraft-server CalVer plus JRE" 6.0.1+up2026.8.2.java25 2026.8.2-java25 6.0.1+up2026.8.2.java25 2026.9.1-java25 minor 6.0.2+up2026.9.1.java25
bump "apache-tika four parts" 10.0.2+up3.3.1.0.full 3.3.1.0-full 10.0.2+up3.3.1.0.full 3.4.0.0-full minor 10.0.3+up3.4.0.0.full
bump "teamspeak prerelease line" 5.1.3+up6.0.0.beta13.1 6.0.0-beta13.1 5.1.3+up6.0.0.beta13.1 6.0.0-beta14.1 minor 5.1.4+up6.0.0.beta14.1
bump "valkey flavour suffix" 1.0.0+up9.1.2.alpine 9.1.2-alpine 1.0.0+up9.1.2.alpine 9.2.0-alpine minor 1.0.1+up9.2.0.alpine
# A major is NOT subject to the fallback - a year rolling over was a chart major
# before #128 as well.
bump "date tag, year roll, major" 2.1.1+up2026.09.30 2026-09-30 2.1.1+up2026.09.30 2027-01-05 major 3.0.0+up2027.01.05
bump "four parts, app major, major" 10.0.2+up3.3.1.0.full 3.3.1.0-full 10.0.2+up3.3.1.0.full 4.0.0.0-full major 11.0.0+up4.0.0.0.full
# The year guard is a guard, not a blanket: a v-prefixed plain line still counts.
bump "v-prefixed plain semver, minor" 7.2.0+upv1.159.0 v1.159.0 7.2.0+upv1.159.0 v1.160.0 minor 7.3.0+upv1.160.0
echo "== maximum, not replacement =="
# A chart-own bump already in the working tree stands, whatever the app jump is.
bump "chart major already on branch, app patch" 1.2.3+up1.4.0 1.4.0 2.0.0+up1.4.0 1.4.1 patch 2.0.0+up1.4.1
bump "chart minor already on branch, app patch" 1.2.3+up1.4.0 1.4.0 1.3.0+up1.4.0 1.4.1 patch 1.3.0+up1.4.1
# A grouped branch: the hook runs once per update from the same base, so the
# minor must survive a patch call that follows it.
bump "minor on branch, later patch call" 3.1.1+up1.0.0 1.0.0 3.2.0+up1.0.0 1.0.0 patch 3.2.0+up1.0.0
bump "patch on branch, later minor call" 3.1.1+up1.0.0 1.0.0 3.1.2+up1.0.0 1.0.0 minor 3.2.0+up1.0.0
bump "repeated minor call is idempotent" 3.1.1+up1.0.0 1.0.0 3.2.0+up1.0.0 1.0.0 minor 3.2.0+up1.0.0
bump "repeated major call is idempotent" 1.2.3+up1.4.0 1.4.0 2.0.0+up2.0.0 2.0.0 major 2.0.0+up2.0.0
bump "major on branch wins over a later minor" 1.2.3+up1.4.0 1.4.0 2.0.0+up1.4.0 1.5.0 minor 2.0.0+up1.5.0
# Stale branch after a rebase: the base moved ahead of what the branch carries,
# so the recomputed version wins and nothing is kept.
bump "stale branch version is not kept" 3.3.0+up1.0.0 1.0.0 3.1.2+up1.0.0 1.0.0 patch 3.3.1+up1.0.0
echo "== missing and unknown update type fall back to the pre-#128 behaviour =="
bump "no second argument, app major" 1.2.3+up1.4.0 1.4.0 1.2.3+up1.4.0 2.0.0 - 2.0.0+up2.0.0
bump "no second argument, app minor" 1.2.3+up1.4.0 1.4.0 1.2.3+up1.4.0 1.5.0 - 1.2.4+up1.5.0
bump "no second argument, app patch" 1.2.3+up1.4.0 1.4.0 1.2.3+up1.4.0 1.4.1 - 1.2.4+up1.4.1
bump "no second argument, unchanged" 3.1.1+up1.0.0 1.0.0 3.1.1+up1.0.0 1.0.0 - 3.1.2+up1.0.0
bump "empty second argument, app major" 1.2.3+up1.4.0 1.4.0 1.2.3+up1.4.0 2.0.0 "" 2.0.0+up2.0.0
bump "empty second argument, app minor" 1.2.3+up1.4.0 1.4.0 1.2.3+up1.4.0 1.5.0 "" 1.2.4+up1.5.0
bump "unknown type, app minor" 1.2.3+up1.4.0 1.4.0 1.2.3+up1.4.0 1.5.0 replacement 1.2.4+up1.5.0
bump "unknown type, app major" 1.2.3+up1.4.0 1.4.0 1.2.3+up1.4.0 2.0.0 lockFileMaintenance 2.0.0+up2.0.0
bump "unknown type, maximum still holds" 1.2.3+up1.4.0 1.4.0 2.0.0+up1.4.0 1.4.1 nonsense 2.0.0+up1.4.1
echo "== charts the script must leave alone, and the hard failures =="
bump "unparsable chart semver fails loudly" 1.2+up1.4.0 1.4.0 1.2+up1.4.0 1.5.0 minor 1.2+up1.4.0 1
bump "missing appVersion fails loudly" 1.2.3+up1.4.0 1.4.0 1.2.3+up1.4.0 "" minor 1.2.3+up1.4.0 1
new_chart_repo="$(scenario demo "" "$(chart_yaml demo 0.1.0+up1.0.0 1.0.0)")"
rc=0
out="$(cd "$new_chart_repo" && "$SCRIPT" demo minor 2>&1)" || rc=$?
report "a chart new on the branch keeps its version" \
"0.1.0+up1.0.0" "$(version_of "$new_chart_repo/demo/Chart.yaml")" 0 "$rc" "$out"
case "$out" in
*"new on this branch"*) passed=$((passed + 1)); printf ' ok and says so\n' ;;
*) failed=$((failed + 1)); printf ' FAIL new chart: no explanation in the output: %s\n' "$out" ;;
esac
tpl_repo="$(scenario template-chart "$(chart_yaml template-chart 4.2.0+upxxx xxx)" "$(chart_yaml template-chart 4.2.0+upxxx xxx)")"
rc=0
out="$(cd "$tpl_repo" && "$SCRIPT" template-chart minor 2>&1)" || rc=$?
report "template-chart is never touched" \
"4.2.0+upxxx" "$(version_of "$tpl_repo/template-chart/Chart.yaml")" 0 "$rc" "$out"
# Renovate calls the hook for every update it makes, chart or not.
other_repo="$(scenario demo "$(chart_yaml demo 1.2.3+up1.4.0 1.4.0)" "$(chart_yaml demo 1.2.3+up1.4.0 1.4.0)")"
for arg in ".github/workflows" "ci/gateway-api-crds.sh" "." "" "not-a-chart"; do
rc=0
out="$(cd "$other_repo" && "$SCRIPT" "$arg" major 2>&1)" || rc=$?
report "non-chart argument '$arg' is a no-op" \
"1.2.3+up1.4.0" "$(version_of "$other_repo/demo/Chart.yaml")" 0 "$rc" "$out"
done
rc=0
out="$(cd "$other_repo" && "$SCRIPT" 2>&1)" || rc=$?
report "no arguments at all is a no-op" \
"1.2.3+up1.4.0" "$(version_of "$other_repo/demo/Chart.yaml")" 0 "$rc" "$out"
rc=0
out="$(cd "$other_repo" && "$SCRIPT" demo/ patch 2>&1)" || rc=$?
report "a trailing slash is accepted" \
"1.2.4+up1.4.0" "$(version_of "$other_repo/demo/Chart.yaml")" 0 "$rc" "$out"
echo
if [ "$failed" -gt 0 ]; then
printf 'bump-level test: %d passed, %d FAILED\n' "$passed" "$failed"
exit 1
fi
printf 'bump-level test: %d cases passed\n' "$passed"