From 0e470a6b07c3a2efdb9c3161b548478b263ce7b0 Mon Sep 17 00:00:00 2001 From: Ant Somers Date: Tue, 13 Oct 2026 18:09:00 +0300 Subject: [PATCH] ci(scripts): drop bump_decdn's changelog step now every artifact is released The `scripts` job failed on main after the first releases: the Changelog tests in scripts/tests/test_bump_decdn.py copy the real collection changelogs and expected an [Unreleased] section, which the release commits turned into dated 0.1.0 sections. `bump_decdn.py changelog` only ever wrote an entry under [Unreleased], until each collection's first release; release.sh writes every later section from the PR titles. Both collections and the chart are released, so the step can never write again. Remove the subcommand, its tests, its call in open-decdn-bump.sh and the checklist text in the decdn_node and sponsord READMEs. test_leaves_the_changelogs stays, with its own list of the three changelogs (now the chart's too), and also plants the image digest, so rewrite's CHANGELOG.md exclusion is checked for both digests. Co-Authored-By: Claude Opus 5.5 --- ansible/roles/decdn_node/README.md | 9 +--- ansible/roles/sponsord/README.md | 4 +- scripts/bump_decdn.py | 63 ----------------------- scripts/open-decdn-bump.sh | 4 +- scripts/tests/test_bump_decdn.py | 81 +++++------------------------- 5 files changed, 16 insertions(+), 145 deletions(-) diff --git a/ansible/roles/decdn_node/README.md b/ansible/roles/decdn_node/README.md index 6cefa21..2c92a85 100644 --- a/ansible/roles/decdn_node/README.md +++ b/ansible/roles/decdn_node/README.md @@ -700,7 +700,7 @@ The repo pins one decdn/decdn release everywhere, and a new one moves every pin one change. `scripts/open-decdn-bump.sh [vX.Y.Z]` opens that PR: decdn's `publish-crates.sh` runs it at the end of each decdn release, and a maintainer runs it by hand from a decdn/devops clone (with no tag, it takes decdn's latest release). -It does steps 1 to 6 and the changelog entries in a worktree at `origin/main`, takes +It does steps 1 to 6 in a worktree at `origin/main`, takes the digests from the release's assets once their signatures verify against `files/decdn-release-KEYS.asc`, and lists in the PR body what is left: the prose of step 7, the chart check against the real binary in step 5, any schema-key or KEYS @@ -731,12 +731,7 @@ The steps: copy, and `SECURITY.md`'s fingerprints). 7. The prose that names the version: `git grep -n ''` outside the changelogs. The changelog entry is the PR title (`feat`, `fix` or `chore`, not `ci` or - `build`, which the changelogs leave out; RELEASING.md); also a `[Unreleased]` - entry in each of `ansible/galaxy/node/CHANGELOG.md`, - `ansible/galaxy/publisher/CHANGELOG.md` (the onramp's decdn pin) and - `charts/decdn-node/CHANGELOG.md` whose artifact is not released yet. The - collections each keep one such entry, which a later bump rewrites - (`scripts/bump_decdn.py`). + `build`, which the changelogs leave out; RELEASING.md). `make test-scripts` fails while the role, the onramp's decdn pin (role and Compose example), the chart's `appVersion` and its `artifacthub.io/images` image, or the KEYS diff --git a/ansible/roles/sponsord/README.md b/ansible/roles/sponsord/README.md index 4c8f329..e77d45d 100644 --- a/ansible/roles/sponsord/README.md +++ b/ansible/roles/sponsord/README.md @@ -249,9 +249,7 @@ move every pin in one change: `crates/*/src/config.rs` (AGENTS.md, `compose/`). 5. The prose that names the version: `git grep -n ''` outside the changelogs. The changelog entry is the PR title (`feat`, `fix` or `chore`, not `ci` or - `build`, which the changelogs leave out; RELEASING.md); before the `decdn.publisher` - collection's first release, also a `[Unreleased]` entry in - `ansible/galaxy/publisher/CHANGELOG.md`. + `build`, which the changelogs leave out; RELEASING.md). `make test-scripts` fails while the two versions disagree; the weekly `upstream-drift` job checks the digests against the release. A diff --git a/scripts/bump_decdn.py b/scripts/bump_decdn.py index 4d35d9c..66f03aa 100755 --- a/scripts/bump_decdn.py +++ b/scripts/bump_decdn.py @@ -9,7 +9,6 @@ rewrite --sums-sha256 --image-digest sha256: move every hand-copied pin (checklist steps 1, 3, 4 and 5 of ansible/roles/decdn_node/README.md, "Bumping the decdn release") - changelog add the bump entry under each collection's [Unreleased] The generated mirrors (step 2) and the vendored KEYS (step 6) are open-decdn-bump.sh's: they come from a checkout of the tag, not from this repo. Prose that names a version @@ -46,25 +45,6 @@ CHART = "charts/decdn-node/Chart.yaml" CHART_VALUES = "charts/decdn-node/values.yaml" -# Collection changelogs that get a bump entry while they hold an [Unreleased] section -# (until each collection's first release; release.sh generates every later section). -# The marker starts the entry, so a later bump replaces an earlier one; no other entry -# may start with it. -CHANGELOGS = { - "ansible/galaxy/node/CHANGELOG.md": ( - "- `decdn_node`: `decdn_node_version` defaults to", - "- `decdn_node`: `decdn_node_version` defaults to `{v}`, decdn/decdn v{v}. The\n" - " generated mirrors (`vars/main/networks.yml`, `schema-keys.txt`) are\n" - " regenerated from that tag.\n", - ), - "ansible/galaxy/publisher/CHANGELOG.md": ( - "- `sponsord_onramp`: the installers' decdn pin defaults to", - "- `sponsord_onramp`: the installers' decdn pin defaults to decdn/decdn `v{v}`\n" - " and that release's `SHA256SUMS` digest, following the `decdn_node` role's\n" - " `decdn_node_version`.\n", - ), -} - class ShapeError(Exception): """A file is not in the shape the bump expects.""" @@ -222,45 +202,6 @@ def verify(root: Path, version: str, sums: str, digest: str) -> None: raise ShapeError(f"{CHART_VALUES}: the example digest for v{version} is {example}, expected [{digest!r}]") -def changelog(root: Path, version: str) -> list[str]: - """Write the bump entry under [Unreleased]: in place of an earlier one, else first - under ### Changed.""" - if not VERSION.match(version): - raise ShapeError(f"{version} is not X.Y.Z") - changed = [] - for rel, (marker, entry) in CHANGELOGS.items(): - lines = read(root, rel).splitlines(keepends=True) - try: - start = lines.index("## [Unreleased]\n") - except ValueError: - # Released: release.sh writes every section from the PR titles. - print(f"bump_decdn: {rel} has no [Unreleased] section; no entry written", file=sys.stderr) - continue - end = next((i for i in range(start + 1, len(lines)) if lines[i].startswith("## ")), len(lines)) - earlier = [i for i in range(start, end) if lines[i].startswith(marker)] - if len(earlier) > 1: - raise ShapeError(f"{rel}: {len(earlier)} [Unreleased] entries start {marker!r}, expected at most 1") - if earlier: - # An earlier bump's entry: its bullet and the indented lines that continue it. - at = earlier[0] - stop = at + 1 - while stop < end and lines[stop].startswith(" "): - stop += 1 - del lines[at:stop] - else: - changed_at = next((i for i in range(start, end) if lines[i] == "### Changed\n"), None) - if changed_at is None: - raise ShapeError(f"{rel}: [Unreleased] has no ### Changed") - # First bullet of ### Changed: after the heading and its blank line. - at = changed_at + 1 - while at < end and lines[at].strip() == "": - at += 1 - lines[at:at] = [entry.format(v=version)] - (root / rel).write_text("".join(lines)) - changed.append(rel) - return changed - - def main() -> None: ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("--root", type=Path, default=Path.cwd(), help="devops checkout (default: the current directory)") @@ -271,7 +212,6 @@ def main() -> None: rw.add_argument("version") rw.add_argument("--sums-sha256", required=True) rw.add_argument("--image-digest", required=True) - sub.add_parser("changelog").add_argument("version") args = ap.parse_args() try: @@ -282,9 +222,6 @@ def main() -> None: elif args.cmd == "rewrite": for rel in rewrite(args.root, args.version, args.sums_sha256, args.image_digest): print(f"wrote {rel}") - elif args.cmd == "changelog": - for rel in changelog(args.root, args.version): - print(f"wrote {rel}") except ShapeError as e: print(f"bump_decdn: {e}", file=sys.stderr) sys.exit(1) diff --git a/scripts/open-decdn-bump.sh b/scripts/open-decdn-bump.sh index 533fcc2..60011db 100755 --- a/scripts/open-decdn-bump.sh +++ b/scripts/open-decdn-bump.sh @@ -13,7 +13,7 @@ # `origin` credentials and opens the PR with your `gh` login: a PR opened that # way runs CI, which one opened by Actions' GITHUB_TOKEN would not. # -# The bump does checklist steps 1 to 6 and the collections' changelog entries. +# The bump does checklist steps 1 to 6. # Release facts (the SHA256SUMS digest, the decdn-node image digest) come from # the release's assets once their signatures verify against the vendored # decdn-release-KEYS.asc; the generated mirrors and KEYS come from a clone of the @@ -226,8 +226,6 @@ if ! cmp -s "$UPSTREAM/KEYS" "$KEYS"; then (\`ansible/roles/sponsord/files/sponsord-release-KEYS.asc\`) and SECURITY.md's fingerprints." fi -bump changelog "$VERSION" >/dev/null || die "could not add the changelog entries; no PR opened" - # Step 7 is a reviewer's: decdn/sponsord pins carry version numbers too, so a # bare old version is not always decdn's. The list holds the old version as a # whole word (not inside 127.0.0.1, nor a prefix of 0.0.10), outside the diff --git a/scripts/tests/test_bump_decdn.py b/scripts/tests/test_bump_decdn.py index cc0145e..93dfffc 100644 --- a/scripts/tests/test_bump_decdn.py +++ b/scripts/tests/test_bump_decdn.py @@ -9,9 +9,7 @@ from __future__ import annotations -import contextlib import importlib.util -import io import shutil import subprocess import sys @@ -34,7 +32,12 @@ bd.CHART, bd.CHART_VALUES, "ansible/molecule/validation-sponsord/converge.yml", - *bd.CHANGELOGS, +] +# rewrite never touches these: release.sh writes them. +CHANGELOGS = [ + "ansible/galaxy/node/CHANGELOG.md", + "ansible/galaxy/publisher/CHANGELOG.md", + "charts/decdn-node/CHANGELOG.md", ] SUMS = "a" * 64 DIGEST = "sha256:" + "b" * 64 @@ -44,7 +47,7 @@ class Fixture(unittest.TestCase): def setUp(self) -> None: self._tmp = tempfile.TemporaryDirectory() self.root = Path(self._tmp.name) - for rel in FILES: + for rel in FILES + CHANGELOGS: (self.root / rel).parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(REPO / rel, self.root / rel) subprocess.run(["git", "init", "-q", str(self.root)], check=True) @@ -116,12 +119,14 @@ def test_leaves_the_sponsord_pin(self) -> None: bd.ONRAMP_DEFAULTS), sponsord) def test_leaves_the_changelogs(self) -> None: - for rel in bd.CHANGELOGS: - self.write(rel, self.text(rel) + f"\n{self.sums}\n") + image = self.digest.removeprefix("sha256:") + for rel in CHANGELOGS: + self.write(rel, self.text(rel) + f"\n{self.sums}\n{image}\n") subprocess.run(["git", "-C", str(self.root), "add", "-A"], check=True) bd.rewrite(self.root, "9.8.7", SUMS, DIGEST) - for rel in bd.CHANGELOGS: + for rel in CHANGELOGS: self.assertIn(self.sums, self.text(rel)) + self.assertIn(image, self.text(rel)) def test_refuses_a_reshaped_file(self) -> None: self.write(bd.COMPOSE_ENV, self.text(bd.COMPOSE_ENV).replace(f"# decdn/decdn v{self.version}\n", "")) @@ -180,68 +185,6 @@ def test_skips_binary_and_refuses_undecodable_files(self) -> None: bd.rewrite(self.root, "1.2.3", "e" * 64, DIGEST) -class Changelog(Fixture): - def unreleased_entries(self, rel: str) -> list[str]: - marker = bd.CHANGELOGS[rel][0] - return [line for line in self.text(rel).splitlines() if line.startswith(marker)] - - def test_replaces_the_entry_in_place(self) -> None: - bd.changelog(self.root, "9.8.7") - for rel in bd.CHANGELOGS: - with self.subTest(rel=rel): - entries = self.unreleased_entries(rel) - self.assertEqual(len(entries), 1) - self.assertIn("9.8.7", entries[0]) - # Only the entry moved. - old = self.original[rel].splitlines() - new = self.text(rel).splitlines() - self.assertEqual(len(old), len(new)) - self.assertEqual(sum(a != b for a, b in zip(old, new)), 1) - - def test_current_version_is_a_no_op(self) -> None: - bd.changelog(self.root, self.version) - for rel in bd.CHANGELOGS: - self.assertEqual(self.text(rel), self.original[rel]) - - def test_inserts_first_under_changed(self) -> None: - rel = "ansible/galaxy/node/CHANGELOG.md" - marker = bd.CHANGELOGS[rel][0] - lines = self.text(rel).splitlines(keepends=True) - at = next(i for i, line in enumerate(lines) if line.startswith(marker)) - while lines[at + 1].startswith(" "): - del lines[at + 1] - del lines[at] - self.write(rel, "".join(lines)) - bd.changelog(self.root, "9.8.7") - text = self.text(rel) - self.assertIn("### Changed\n\n" + bd.CHANGELOGS[rel][1].format(v="9.8.7"), text) - - def test_refuses_an_unreleased_without_changed(self) -> None: - rel = "ansible/galaxy/node/CHANGELOG.md" - marker = bd.CHANGELOGS[rel][0] - lines = [line for line in self.text(rel).splitlines(keepends=True) - if line != "### Changed\n" and not line.startswith(marker)] - self.write(rel, "".join(lines)) - with self.assertRaisesRegex(bd.ShapeError, "no ### Changed"): - bd.changelog(self.root, "9.8.7") - - def test_refuses_two_entries(self) -> None: - rel = "ansible/galaxy/publisher/CHANGELOG.md" - entry = bd.CHANGELOGS[rel][1].format(v="0.0.0") - self.write(rel, self.text(rel).replace("### Changed\n\n", "### Changed\n\n" + entry, 1)) - with self.assertRaisesRegex(bd.ShapeError, "2 \\[Unreleased\\] entries"): - bd.changelog(self.root, "9.8.7") - - def test_skips_a_released_collection(self) -> None: - rel = "ansible/galaxy/node/CHANGELOG.md" - released = self.text(rel).replace("## [Unreleased]\n", "## [0.1.0] — 2026-01-01\n") - self.write(rel, released) - with contextlib.redirect_stderr(io.StringIO()) as err: - self.assertNotIn(rel, bd.changelog(self.root, "9.8.7")) - self.assertIn(f"{rel} has no [Unreleased] section", err.getvalue()) - self.assertEqual(self.text(rel), released) - - class Cli(Fixture): """open-decdn-bump.sh reads stdout and the exit code; a refusal must exit 1."""