From e83012d252c88bcf24a3ac1acca37bcd6d23be43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20=C4=8Ciha=C5=99?= Date: Sat, 4 Jul 2026 14:28:50 +0200 Subject: [PATCH] fix: bump chart version on Weblate updates Renovate updates the Weblate image version without changing the chart version, which can leave releases trying to republish an existing chart version. Add a guarded pull request workflow that computes the next chart patch version from the base branch and lets pre-commit-ci/lite-action push the generated bump back to Renovate branches. Fixes #871 --- .../scripts/bump-renovate-chart-version.py | 157 ++++++++++++++++++ .github/workflows/renovate-chart-version.yaml | 55 ++++++ .pre-commit-config.yaml | 14 ++ 3 files changed, 226 insertions(+) create mode 100644 .github/scripts/bump-renovate-chart-version.py create mode 100644 .github/workflows/renovate-chart-version.yaml diff --git a/.github/scripts/bump-renovate-chart-version.py b/.github/scripts/bump-renovate-chart-version.py new file mode 100644 index 00000000..c92a8192 --- /dev/null +++ b/.github/scripts/bump-renovate-chart-version.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +"""Bump chart version for Renovate Weblate image updates.""" + +from __future__ import annotations + +import os +import re +import subprocess +import sys +from pathlib import Path + +CHART_PATH = Path("charts/weblate/Chart.yaml") +README_PATH = Path("charts/weblate/README.md") +VALUES_PATH = Path("charts/weblate/values.yaml") + +ALLOWED_PATHS = { + CHART_PATH.as_posix(), + README_PATH.as_posix(), + VALUES_PATH.as_posix(), +} +REQUIRED_PATHS = { + CHART_PATH.as_posix(), + VALUES_PATH.as_posix(), +} + + +def run_git(*args: str) -> str: + completed = subprocess.run( + ("git", *args), + check=True, + capture_output=True, + text=True, + ) + return completed.stdout + + +def git_show(base_ref: str, path: Path) -> str: + return run_git("show", f"origin/{base_ref}:{path.as_posix()}") + + +def root_scalar(chart: str, key: str) -> str: + match = re.search( + rf"^{re.escape(key)}:\s*([^\s#]+).*$", + chart, + flags=re.MULTILINE, + ) + if match is None: + raise SystemExit(f"Unable to find top-level {key!r} in Chart.yaml") + return match.group(1).strip("'\"") + + +def bump_patch(version: str) -> str: + match = re.fullmatch(r"(\d+)\.(\d+)\.(\d+)", version) + if match is None: + raise SystemExit(f"Unsupported chart version: {version}") + major, minor, patch = match.groups() + return f"{major}.{minor}.{int(patch) + 1}" + + +def changed_paths(base_ref: str) -> set[str]: + return set( + run_git( + "diff", + "--name-only", + "--diff-filter=ACMRT", + f"origin/{base_ref}...HEAD", + ).splitlines() + ) + + +def update_chart_version(chart: str, target_version: str) -> str: + new_chart, count = re.subn( + r"^(version:\s*)[^\s#]+(.*)$", + lambda match: f"{match.group(1)}{target_version}{match.group(2)}", + chart, + count=1, + flags=re.MULTILINE, + ) + if count != 1: + raise SystemExit("Unable to update chart version in Chart.yaml") + return new_chart + + +def update_readme_badge(readme: str, target_version: str) -> str: + version_badge = ( + f"![Version: {target_version}]" + f"(https://img.shields.io/badge/Version-{target_version}" + "-informational?style=flat-square)" + ) + new_readme, count = re.subn( + r"!\[Version: [^\]]+\]\(" + r"https://img\.shields\.io/badge/Version-[^-)]*" + r"-informational\?style=flat-square\)", + version_badge, + readme, + count=1, + ) + if count != 1: + raise SystemExit("Unable to update chart version badge in README.md") + return new_readme + + +def main() -> int: + base_ref = os.environ["BASE_REF"] + changed = changed_paths(base_ref) + unexpected = changed - ALLOWED_PATHS + + if unexpected: + print( + "Skipping chart version bump; unexpected changed paths: " + + ", ".join(sorted(unexpected)) + ) + return 0 + + if not REQUIRED_PATHS.issubset(changed): + print( + "Skipping chart version bump; Weblate Chart.yaml and values.yaml " + "changes were not both detected." + ) + return 0 + + base_chart = git_show(base_ref, CHART_PATH) + head_chart = CHART_PATH.read_text(encoding="utf-8") + + base_app_version = root_scalar(base_chart, "appVersion") + head_app_version = root_scalar(head_chart, "appVersion") + if base_app_version == head_app_version: + print("Skipping chart version bump; appVersion did not change.") + return 0 + + base_chart_version = root_scalar(base_chart, "version") + head_chart_version = root_scalar(head_chart, "version") + target_chart_version = bump_patch(base_chart_version) + if head_chart_version not in {base_chart_version, target_chart_version}: + raise SystemExit( + f"Unexpected chart version {head_chart_version}; expected " + f"{base_chart_version} or {target_chart_version}." + ) + + new_chart = update_chart_version(head_chart, target_chart_version) + readme = README_PATH.read_text(encoding="utf-8") + new_readme = update_readme_badge(readme, target_chart_version) + + CHART_PATH.write_text(new_chart, encoding="utf-8") + README_PATH.write_text(new_readme, encoding="utf-8") + + if head_chart_version == target_chart_version: + print(f"Chart version already set to {target_chart_version}.") + else: + print( + f"Bumped chart version from {head_chart_version} to {target_chart_version}." + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/workflows/renovate-chart-version.yaml b/.github/workflows/renovate-chart-version.yaml new file mode 100644 index 00000000..f41ff789 --- /dev/null +++ b/.github/workflows/renovate-chart-version.yaml @@ -0,0 +1,55 @@ +# Copyright © Michal Čihař +# +# SPDX-License-Identifier: CC0-1.0 + +name: Renovate chart version + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - charts/weblate/Chart.yaml + - charts/weblate/README.md + - charts/weblate/values.yaml + +permissions: + contents: read + +jobs: + bump-version: + name: Bump chart version + runs-on: ubuntu-24.04 + if: >- + github.event.pull_request.user.login == 'renovate[bot]' && + github.event.pull_request.head.repo.full_name == github.repository && + ( + startsWith(github.event.pull_request.head.ref, 'renovate/weblate-weblate-') || + contains(github.event.pull_request.title, 'weblate/weblate') + ) + steps: + - name: Checkout pull request + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + persist-credentials: false + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + + - name: Fetch base branch + env: + BASE_REF: ${{ github.base_ref }} + run: git fetch --no-tags --prune origin "+refs/heads/${BASE_REF}:refs/remotes/origin/${BASE_REF}" + + - name: Bump chart version + env: + BASE_REF: ${{ github.base_ref }} + run: python3 .github/scripts/bump-renovate-chart-version.py + + - name: diff + if: always() + run: git diff + + - uses: pre-commit-ci/lite-action@5d6cc0eb514c891a40562a58a8e71576c5c7fb43 # v1.1.0 + if: success() + with: + msg: 'chore: bump chart version for Weblate update' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aa73c7c3..1eda185a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,6 +27,19 @@ repos: rev: v1.38.0 hooks: - id: yamllint +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.20 + hooks: + - id: ruff-check + args: + - --fix + - --exit-non-zero-on-fix + - id: ruff-format +- repo: https://github.com/astral-sh/ty-pre-commit + rev: v0.0.56 + hooks: + - id: ty + args: [--isolated] - repo: https://github.com/norwoodj/helm-docs rev: v1.14.2 hooks: @@ -60,3 +73,4 @@ ci: autoupdate_schedule: quarterly skip: - kingfisher-auto + - ty