|
| 1 | +#!/usr/bin/env python |
| 2 | + |
| 3 | +import os |
| 4 | +import subprocess |
| 5 | +import yaml |
| 6 | +from typing import Tuple, Set |
| 7 | + |
| 8 | +def run_helm_repo_add(repo_name: str, repo_url: str) -> None: |
| 9 | + try: |
| 10 | + subprocess.run(["helm", "repo", "add", repo_name, repo_url], check=True) |
| 11 | + except subprocess.CalledProcessError as e: |
| 12 | + if "already exists" in e.stderr.decode() if e.stderr else str(e): |
| 13 | + pass # Ignore "already exists" error |
| 14 | + else: |
| 15 | + raise |
| 16 | + |
| 17 | + |
| 18 | +def run_helm_repo_update(repo_name: str) -> None: |
| 19 | + subprocess.run(["helm", "repo", "update", repo_name], check=True) |
| 20 | + |
| 21 | + |
| 22 | +def get_chart_and_app_version(repo_name: str, chart_name: str) -> Tuple[str, str]: |
| 23 | + full_chart = f"{repo_name}/{chart_name}" |
| 24 | + result = subprocess.run( |
| 25 | + ["helm", "show", "chart", full_chart], |
| 26 | + capture_output=True, |
| 27 | + text=True, |
| 28 | + check=True |
| 29 | + ) |
| 30 | + chart_yaml = yaml.safe_load(result.stdout) |
| 31 | + return chart_yaml["version"], chart_yaml["appVersion"] |
| 32 | + |
| 33 | + |
| 34 | +def process_yaml_files(top_dir: str) -> None: |
| 35 | + seen_repos: Set[str] = set() |
| 36 | + |
| 37 | + for root, _, files in os.walk(top_dir): |
| 38 | + for file in files: |
| 39 | + if not file.endswith(".yaml"): |
| 40 | + continue |
| 41 | + |
| 42 | + filepath = os.path.join(root, file) |
| 43 | + |
| 44 | + try: |
| 45 | + with open(filepath, "r") as f: |
| 46 | + raw_lines = f.readlines() |
| 47 | + |
| 48 | + try: |
| 49 | + first_doc = yaml.safe_load("".join(raw_lines)) |
| 50 | + except yaml.YAMLError as e: |
| 51 | + if "expected a single document" in str(e): |
| 52 | + continue # silently skip multi-doc YAMLs |
| 53 | + print(f"⚠️ Skipping invalid YAML: {filepath}") |
| 54 | + continue |
| 55 | + |
| 56 | + if not isinstance(first_doc, dict): |
| 57 | + continue |
| 58 | + |
| 59 | + if not ( |
| 60 | + "releaseName" in first_doc |
| 61 | + and "version" in first_doc |
| 62 | + and "repo" in first_doc |
| 63 | + and "name" in first_doc |
| 64 | + ): |
| 65 | + continue |
| 66 | + |
| 67 | + repo_info = first_doc["repo"] |
| 68 | + chart_name = first_doc["name"] |
| 69 | + repo_name = repo_info.get("name") |
| 70 | + repo_url = repo_info.get("url") |
| 71 | + |
| 72 | + if not repo_name or not repo_url: |
| 73 | + continue |
| 74 | + |
| 75 | + if repo_name not in seen_repos: |
| 76 | + run_helm_repo_add(repo_name, repo_url) |
| 77 | + seen_repos.add(repo_name) |
| 78 | + |
| 79 | + run_helm_repo_update(repo_name) |
| 80 | + |
| 81 | + # print(f"📦 Updating {filepath} -> {repo_name}/{chart_name}") |
| 82 | + new_chart_version, new_app_version = get_chart_and_app_version(repo_name, chart_name) |
| 83 | + |
| 84 | + updated_lines = [] |
| 85 | + for line in raw_lines: |
| 86 | + if line.strip().startswith("version:"): |
| 87 | + new_line = f'version: {new_chart_version} # {new_app_version}\n' |
| 88 | + updated_lines.append(new_line) |
| 89 | + else: |
| 90 | + updated_lines.append(line) |
| 91 | + |
| 92 | + with open(filepath, "w") as f: |
| 93 | + f.writelines(updated_lines) |
| 94 | + |
| 95 | + print(f"✅ Updated {filepath}") |
| 96 | + |
| 97 | + except Exception as e: |
| 98 | + print(f"⚠️ Error processing {filepath}: {e}") |
| 99 | + |
| 100 | +if __name__ == "__main__": |
| 101 | + print('⚠️⚠️⚠️ This script is best-effort! Always check the result using "git diff"! ⚠️⚠️⚠️') |
| 102 | + print('Notably, it skips invalid YAMLs, which can be the case because we sometimes use templating syntax, even for helm-chart definitions') |
| 103 | + print('Please judge on the skipped files if they contain a helm-chart and should be manually bumped') |
| 104 | + print('The script can be improved to handle such files in the future') |
| 105 | + print() |
| 106 | + |
| 107 | + process_yaml_files(".") |
0 commit comments