From 8d12b6c10af6f4d5130b6a6599b58bdfa50f8736 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 10 Sep 2026 11:12:48 +0100 Subject: [PATCH 1/2] Stop insisting on chart version bump on stable branches also In PR #3195 we modified the chart release process so that we no longer required a bump in the chart version on `master`. Unfortunately, we still have an issue on stable `release-*` branches. Anyone that backports a fix using the `/cherry-pick` slash command will see a failing chart lint job on said stable branch. This can only be fixed by incrementing the `version` field, but no one (including the maintainers) has the ability to push to the `k8s-infra-cherrypick-robot` repo fork that the bot uses. This means the only practical way to backport fixes that include chart changes is to do so manually, which is a lot more work for very little gain. Rather than doing this, drop the version check entirely. This means we will have to manually bump the chart version periodically, but that's not a huge ask and is very similar to what we already do for CPO itself. To make this easier, we make the `hack/bump-version.sh` smarter so that it can start automatically bumping chart versions. Signed-off-by: Stephen Finucane --- .github/workflows/pr.yaml | 5 -- docs/release-procedure.md | 15 ++-- hack/bump-release.py | 147 ++++++++++++++++++++++++++++++++++---- 3 files changed, 144 insertions(+), 23 deletions(-) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 8430a2f7cd..d1e2b0b421 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -34,13 +34,8 @@ jobs: uses: helm/chart-testing-action@afea100a513515fbd68b0e72a7bb0ae34cb62aec - name: Run chart-testing (lint) - if: github.base_ref == 'master' run: ct lint --target-branch ${GITHUB_BASE_REF} --check-version-increment=false - - name: Run chart-testing (lint) - if: github.base_ref != 'master' - run: ct lint --target-branch ${GITHUB_BASE_REF} - # v1.0.3 is the latest release compatible with Helm v3.10.0. - name: Install helm-unittest v1.0.3 run: >- diff --git a/docs/release-procedure.md b/docs/release-procedure.md index a23f1ca6b5..59f4f66277 100644 --- a/docs/release-procedure.md +++ b/docs/release-procedure.md @@ -165,9 +165,12 @@ Chart versions on `master` use a `-dev` pre-release suffix (e.g. `2.37.0-dev`) and are **not** bumped for individual PRs. Version bumps only happen at release time (see [Major releases](#major-releases-xy0) above). -On `release-*` branches the chart version (`version`) **must** be bumped for -every backported change to a chart(s) including changes to `appVersion`. A CI -job enforces this for PRs targeting those branches. Once version change is -merged, tags are automatically created for any charts whose version changed -(i.e. `openstack-cloud-controller-manager-X.Y.Z`, `openstack-cinder-csi-X.Y.Z`, -and `openstack-manila-csi-X.Y.Z`). +On `release-*` branches the chart version (`version`) **should** be bumped for +every backported change to a chart(s) including changes to `appVersion`. This +can be done in the backport PR or later, via a separate PR. The +`hack/bump-release.py` will automatically bump the correct chart if there have +been any changes. + +Once version change is merged, tags are automatically created for any charts +whose version changed (i.e. `openstack-cloud-controller-manager-X.Y.Z`, +`openstack-cinder-csi-X.Y.Z`, and `openstack-manila-csi-X.Y.Z`). diff --git a/hack/bump-release.py b/hack/bump-release.py index 80ed1f280f..cf9309d752 100755 --- a/hack/bump-release.py +++ b/hack/bump-release.py @@ -62,7 +62,7 @@ def git_check(*args: str) -> bool: def find_base_branch() -> str | None: - """Return 'master' or a 'release-N.N' name, whichever is the closest ancestor of HEAD. + """Return 'master' or a 'release-N.N' name for the closest ancestor of HEAD. Checks all remotes so the result is not tied to a specific remote name. """ @@ -95,6 +95,30 @@ def find_base_branch() -> str | None: return best_name +def find_last_cpo_tag() -> str: + """Return the most recent vX.Y.Z CPO tag reachable from HEAD.""" + try: + return git( + "describe", "--tags", "--abbrev=0", "--match", "v[0-9]*.[0-9]*.[0-9]*" + ) + except subprocess.CalledProcessError: + sys.exit("ERROR: No CPO release tag (vX.Y.Z) found reachable from HEAD.") + + +def find_last_chart_tag(chart_name: str) -> str | None: + """Return the most recent release tag for a chart, or None if none exists.""" + try: + return git( + "describe", + "--tags", + "--abbrev=0", + "--match", + f"{chart_name}-[0-9]*.[0-9]*.[0-9]*", + ) + except subprocess.CalledProcessError: + return None + + def load_chart(path: Path) -> tuple[YAML, dict]: yaml = YAML() yaml.preserve_quotes = True @@ -190,18 +214,12 @@ def release_master() -> None: update_files(app_version, new_app_version) -def release_stable(base_branch: str) -> None: - print(f"Base release branch: {base_branch}") - print() - print( - "If you are only bumping the Helm chart version independently of a CPO release, " - "manually update the 'version' field in each Chart.yaml instead." - ) - if not Confirm.ask("Releasing a new CPO version?", default=False): - print("Exiting. Manually update 'version' in each Chart.yaml if needed.") - sys.exit(0) - print() +def changed_files(merge_base: str) -> list[str]: + return git("diff", "--name-only", merge_base, "HEAD").splitlines() + +def release_stable_full(base_branch: str) -> None: + """Full CPO release: bump appVersion, chart versions, and image references.""" app_version = read_app_version() m_app = APP_VERSION_RE.match(app_version) @@ -211,6 +229,17 @@ def release_stable(base_branch: str) -> None: app_x, app_y = int(m_app.group("x")), int(m_app.group("y")) new_app_version = f"v{m_app.group('major')}.{app_x}.{app_y + 1}" + console.print(f"[dim]Detected base branch:[/dim] [bold]{base_branch}[/bold]") + console.print( + f"[dim]Changes to CPO code detected. Continuing will release a new CPO patch version " + f"([bold]{new_app_version}[/bold]) and bump all Helm chart versions.[/dim]" + ) + console.print() + if not Confirm.ask(f"Release CPO {new_app_version}?", default=False): + print("Exiting.") + sys.exit(0) + print() + print("Bumping versions:") print(f" appVersion: {app_version} -> {new_app_version}") @@ -238,6 +267,100 @@ def release_stable(base_branch: str) -> None: update_files(app_version, new_app_version) +def release_stable_chart_only(base_branch: str, affected: list[Path]) -> None: + """Chart-only release: bump chart versions without touching appVersion or manifests.""" + app_version = read_app_version() + + m_app = APP_VERSION_RE.match(app_version) + if not m_app: + sys.exit(f"ERROR: Expected appVersion to match 'v1.X.Y', got: {app_version!r}") + + app_x = int(m_app.group("x")) + + console.print(f"[dim]Detected base branch:[/dim] [bold]{base_branch}[/bold]") + console.print( + "[dim]No changes to CPO code detected. Continuing will bump only the Helm chart " + "versions for changed charts (appVersion and image references will not change).[/dim]" + ) + console.print() + if not Confirm.ask("Release chart-only update?", default=False): + print("Exiting.") + sys.exit(0) + print() + + print("Bumping chart versions:") + + for path in affected: + _, data = load_chart(path) + current_version = data["version"] + + m_ver = VERSION_RE.match(current_version) + if not m_ver: + sys.exit( + f"ERROR: {path.parent.name}: expected version 'MAJOR.X.Z' (no -dev suffix), " + f"got: {current_version!r}" + ) + if int(m_ver.group("x")) != app_x: + sys.exit( + f"ERROR: {path.parent.name}: minor version mismatch with " + f"appVersion ({app_version}): {current_version!r}" + ) + + new_version = f"{m_ver.group('major')}.{app_x}.{int(m_ver.group('z')) + 1}" + print(f" {path.parent.name}: version {current_version} -> {new_version}") + update_chart(path, app_version, new_version) + + +def release_stable(base_branch: str) -> None: + last_cpo_tag = find_last_cpo_tag() + app_version = read_app_version() + + # If appVersion is already ahead of the last CPO tag, a full release has + # been prepared but not yet tagged — don't double-bump. + if app_version != last_cpo_tag: + console.print( + f"[yellow]Warning:[/yellow] appVersion is already at {app_version!r} " + f"(last CPO tag: {last_cpo_tag!r}). " + f"A CPO release has already been prepared; create the tag to complete it." + ) + sys.exit(0) + + # Code changes are always measured against the last CPO tag. + has_code_changes = any( + f.startswith("cmd/") or f.startswith("pkg/") + for f in changed_files(last_cpo_tag) + ) + + if has_code_changes: + release_stable_full(base_branch) + return + + # Chart changes are measured against each chart's own last tag so that + # previously released chart-only changes are not counted again. + # If a chart's version is already ahead of its last tag, a chart-only + # release has been prepared but not yet tagged — skip it. + affected: list[Path] = [] + for chart_path in CHART_FILES: + chart_name = chart_path.parent.name + last_chart_tag = find_last_chart_tag(chart_name) + if last_chart_tag: + _, data = load_chart(chart_path) + if data["version"] != last_chart_tag.rsplit("-", 1)[-1]: + continue + ref = last_chart_tag if last_chart_tag else last_cpo_tag + if any(f.startswith(f"charts/{chart_name}/") for f in changed_files(ref)): + affected.append(chart_path) + + if not affected: + print( + f"No changes to CPO code or charts detected since {last_cpo_tag}. " + "Nothing to release." + ) + sys.exit(0) + + release_stable_chart_only(base_branch, affected) + + def main() -> None: if not CHART_FILES: sys.exit( From 35129f3e22b8db9e82ec382bfe81d63425caaf5e Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 11 Sep 2026 17:31:09 +0100 Subject: [PATCH 2/2] Improve release process docs further Encourage users to run the master version of the release script (it doesn't exist on older branches). Also explain the meaning of the 3 chart-only tags. Signed-off-by: Stephen Finucane --- docs/release-procedure.md | 85 +++++++++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 16 deletions(-) diff --git a/docs/release-procedure.md b/docs/release-procedure.md index 59f4f66277..4cd50b1f68 100644 --- a/docs/release-procedure.md +++ b/docs/release-procedure.md @@ -29,7 +29,7 @@ separate PRs: `github.com/gophercloud/gophercloud` before bumping the version of `k8s.io/kubernetes`. -2. Bump the version of `k8s.io/kubernetes` to the latest minor version. +3. Bump the version of `k8s.io/kubernetes` to the latest minor version. ```bash go get -u k8s.io/kubernetes@latest @@ -47,7 +47,7 @@ separate PRs: Example: https://github.com/kubernetes/cloud-provider-openstack/pull/3010 -3. Bump remaining dependencies. +4. Bump remaining dependencies. Once again, pay close attention to any major version bumps of packages, ensuring API changes are accounted for. @@ -118,10 +118,17 @@ dependency or sidecar container. $ git push origin release-X.Y ``` -1. Reset the `version` field of the Helm Charts to `2.{X+1}.0-dev` +1. Reset the `version` field of the Helm Charts to `2.{X+1}.0-dev`. + + Leave `appVersion` set to the version just released (e.g. `vX.Y.0`). + The `hack/bump-release.py` script relies on this value when computing + the next release version, reading it to determine the current minor and + validates that the chart `version` is already at the next minor's `dev` + placeholder. Any bugfixes for the Helm Charts must be backported to the `release-*` - stable branches and released from there. + stable branches and released from there. See [Helm Charts](#helm-charts) + below for details. 1. Make PR modifying [images.yaml](https://github.com/kubernetes/k8s.io/blob/main/registry.k8s.io/images/k8s-staging-provider-os/images.yaml) @@ -152,25 +159,71 @@ dependency or sidecar container. ### Minor releases (`X.Y.Z`, `Z` > 0) -The release process for a minor release is effectively the same as the release -process for major releases but with the following changes: +The release process for a minor release is the same as for major releases with +the following differences: + +1. Check out the `release-X.Y` branch and run the `master` version of + `hack/bump-release.py`. + + ```bash + $ git fetch upstream + $ git checkout release-X.Y + $ git pull --rebase upstream release-X.Y + $ tmp=$(mktemp --suffix=.py) + $ git show upstream/master:hack/bump-release.py > "$tmp" + $ uv run "$tmp" + $ rm "$tmp" + ``` + + The script must be run from the release branch so it can detect it is on a + stable branch. Running the `master` version ensures you always use the + latest version of the script. -1. You must always bump the Helm Chart `appVersion` and `version` fields. + The script automatically bumps both `appVersion` and `version` in all Helm + Charts (whereas a chart-only release on a stable branch bumps only the + `version` of affected charts). -1. It is not necessary to create a new branch or add new jobs. +1. It is not necessary to create a new release branch or add new CI jobs. ## Helm Charts +### Release tag types + +There are four independent release artifacts, each with its own tag: + +- The overall CPO binary release (e.g. `v1.36.0`) +- The CCM chart release (e.g. `openstack-cloud-controller-manager-2.36.0`) +- The Cinder CSI chart release (e.g. `openstack-cinder-csi-2.36.0`) +- The Manila CSI chart release (e.g. `openstack-manila-csi-2.36.0`) + +A CPO binary release always triggers new versions of all three charts (because +all charts reference the CPO image via `appVersion`). A chart-only release — +where only chart files change with no changes to `cmd/` or `pkg/` — produces +new chart tags for the affected charts without a new CPO binary tag. + +### Versioning on `master` + Chart versions on `master` use a `-dev` pre-release suffix (e.g. -`2.37.0-dev`) and are **not** bumped for individual PRs. Version bumps only +`2.37.0-dev`) and are **not** bumped for individual PRs. The `appVersion` +field reflects the most recently published CPO release. Version bumps only happen at release time (see [Major releases](#major-releases-xy0) above). -On `release-*` branches the chart version (`version`) **should** be bumped for -every backported change to a chart(s) including changes to `appVersion`. This -can be done in the backport PR or later, via a separate PR. The -`hack/bump-release.py` will automatically bump the correct chart if there have -been any changes. +### Versioning on `release-*` branches + +On `release-*` branches the chart `version` **should** be bumped for every +backported change to a chart or to CPO code (`cmd/`, `pkg/`). This can be done +in the backport PR itself or later via a separate PR. + +Run `hack/bump-release.py` from the `release-*` branch to apply the correct +bump automatically. The script compares the branch tip against the most recent +CPO tag (e.g. `v1.36.0`) and determines what changed: + +- **CPO code changed** (`cmd/` or `pkg/`): bumps `appVersion` in all charts, + bumps `version` in all charts, and updates image references in `docs/`, + `manifests/`, and `examples/`. +- **Only chart files changed**: bumps `version` only in the charts that + changed; `appVersion` and image references are left untouched. -Once version change is merged, tags are automatically created for any charts -whose version changed (i.e. `openstack-cloud-controller-manager-X.Y.Z`, +Once a version change is merged, tags are automatically created for any charts +whose `version` changed (i.e. `openstack-cloud-controller-manager-X.Y.Z`, `openstack-cinder-csi-X.Y.Z`, and `openstack-manila-csi-X.Y.Z`).