From 332499862c24d657d02064cb23bfe982a0c6d1f1 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Thu, 9 Oct 2025 19:25:36 +0200 Subject: [PATCH 1/9] Automate PR creation to release helm chart using gh helm repo --- .evergreen-functions.yml | 19 +++++ .evergreen-release.yml | 16 ++++ requirements.txt | 1 + scripts/create_chart_release_pr.py | 132 +++++++++++++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 scripts/create_chart_release_pr.py diff --git a/.evergreen-functions.yml b/.evergreen-functions.yml index 6b184845b..1733982ad 100644 --- a/.evergreen-functions.yml +++ b/.evergreen-functions.yml @@ -850,6 +850,25 @@ functions: unzip -u macos-notary.zip chmod 755 ./linux_amd64/macnotary + create_chart_release_pr: + - command: github.generate_token + params: + repo: helm-charts + expansion_name: GH_TOKEN + - command: subprocess.exec + type: setup + params: + working_dir: src/github.com/mongodb/mongodb-kubernetes + include_expansions_in_env: + - GH_TOKEN + - workdir + - RELEASE_OPERATOR_VERSION + env: + GH_TOKEN: ${GH_TOKEN} + MCK_DIR: ${workdir}/src/github.com/mongodb/mongodb-kubernetes + # binary: scripts/dev/run_python.sh scripts/create_chart_release_pr.py --chart_version ${RELEASE_OPERATOR_VERSION|*triggered_by_git_tag} + binary: scripts/dev/run_python.sh scripts/create_chart_release_pr.py --chart_version x.y.z + release_kubectl_mongodb_plugin: - command: github.generate_token params: diff --git a/.evergreen-release.yml b/.evergreen-release.yml index 56a24e274..29df75c3f 100644 --- a/.evergreen-release.yml +++ b/.evergreen-release.yml @@ -122,6 +122,13 @@ tasks: - func: install_goreleaser - func: install_macos_notarization_service - func: release_kubectl_mongodb_plugin + + - name: create_chart_release_pr + tags: [ "helm_chart_release_pr" ] + commands: + - func: clone + - func: python_venv + - func: create_chart_release_pr ### Release build variants buildvariants: @@ -239,3 +246,12 @@ buildvariants: allowed_requesters: [ "patch", "github_tag" ] tasks: - name: release_kubectl_mongodb_plugin + + - name: create_chart_release_pr + display_name: create_chart_release_pr + tags: [ "release" ] + run_on: + - release-ubuntu2404-small # This is required for CISA attestation https://jira.mongodb.org/browse/DEVPROD-17780 + allowed_requesters: [ "patch", "github_tag" ] + tasks: + - name: create_chart_release_pr \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 532b23a50..275029bc0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,6 +37,7 @@ python-frontmatter==1.1.0 python-on-whales==0.78.0 yamale==6.0.0 yamllint==1.37.1 +PyGithub==2.7.0 # from kubeobject freezegun==1.5.5 diff --git a/scripts/create_chart_release_pr.py b/scripts/create_chart_release_pr.py new file mode 100644 index 000000000..3eff23e6a --- /dev/null +++ b/scripts/create_chart_release_pr.py @@ -0,0 +1,132 @@ +import argparse +import os +import shutil +import subprocess +import sys +import tempfile + +from github import Github, GithubException + +from lib.base_logger import logger + +REPO_URL = "https://github.com/mongodb/helm-charts.git" +REPO_NAME = "mongodb/helm-charts" +TARGET_CHART_SUBDIR = "charts/mongodb-kubernetes" +BASE_BRANCH = "main" + + +# run_command runs the command `command` from dir cwd +def run_command(command, cwd=None): + logger.info(f"Running command: {' '.join(command)} in directory {cwd}") + result = subprocess.run(command, capture_output=True, text=True, cwd=cwd) + if result.returncode != 0: + logger.error("ERROR:") + logger.error(result.stdout) + logger.error(result.stderr) + raise RuntimeError(f"Command failed: {' '.join(command)}") + logger.info("Command succeeded") + return result.stdout + + +def create_pull_request(branch_name, chart_version): + logger.info("Creating the pull request in the helm-charts repo.") + github_token = os.environ.get("GH_TOKEN") + + if not github_token: + logger.info("Warning: GH_TOKEN environment variable not set.") + pr_url = f"https://github.com/{REPO_NAME}/pull/new/{branch_name}" + logger.info(f"Please create the Pull Request manually by following the link:\n{pr_url}") + + try: + g = Github(github_token) + repo = g.get_repo(REPO_NAME) + pr_title = f"Release MCK {chart_version}" + body = f"This PR publishes the MCK chart version {chart_version}." + + pr = repo.create_pull( + title=pr_title, + body=body, + head=branch_name, + base=BASE_BRANCH, + ) + logger.info(f"Successfully created Pull Request {pr.html_url}") + except GithubException as e: + logger.error(f"ERROR: Could not create Pull Request. GitHub API returned an error: {e.status}") + logger.error(f"Details: {e.data}") + logger.error("Please check your github token permissions and repository details.") + return 1 + except Exception as e: + logger.error(f"An unexpected error occurred while creating the PR: {e}") + return 1 + + +def main(): + parser = argparse.ArgumentParser( + description="Automate PR creation to release MCK helm chart to github helm chart repo." + ) + parser.add_argument( + "--chart_version", help="The version of the chart to be released (e.g., '1.3.0').", required=True + ) + args = parser.parse_args() + + chart_version = args.chart_version + branch_name = f"mck-release-{chart_version}" + + workdir = os.environ.get("MCK_DIR") + if not workdir: + logger.info("The workdir environment variable is not set this should be set to the root of MCK code.") + return 1 + # source_chart_path is local helm chart in MCK repo + source_chart_path = os.path.join(workdir, "helm_chart") + + if not os.path.isdir(source_chart_path): + logger.info(f"The source chart path '{source_chart_path}' is not a valid directory.") + return 1 + + github_token = os.environ.get("GH_TOKEN") + + with tempfile.TemporaryDirectory() as temp_dir: + helm_repo_path = os.path.join(temp_dir, "helm-charts") + logger.info(f"Working in a temporary directory: {temp_dir}") + + try: + run_command(["git", "clone", REPO_URL, helm_repo_path]) + run_command(["git", "checkout", "-b", branch_name], cwd=helm_repo_path) + + target_dir = os.path.join(helm_repo_path, TARGET_CHART_SUBDIR) + logger.info(f"Clearing content from dir '{target_dir}'") + if os.path.exists(target_dir): + for item in os.listdir(target_dir): + item_path = os.path.join(target_dir, item) + if os.path.isdir(item_path): + shutil.rmtree(item_path) + else: + os.remove(item_path) + + logger.info(f"Copying local MCK chart from '{source_chart_path}' to helm repo chart path {target_dir}") + shutil.copytree(source_chart_path, target_dir, dirs_exist_ok=True) + + commit_message = f"Release MCK {chart_version}" + run_command(["git", "add", "."], cwd=helm_repo_path) + run_command(["git", "commit", "-m", commit_message], cwd=helm_repo_path) + + if github_token: + logger.info("Configuring remote URL for authenticated push...") + # Constructs a URL like https://x-access-token:YOUR_TOKEN@github.com/owner/repo.git + authenticated_url = f"https://x-access-token:{github_token}@{REPO_URL.split('//')[1]}" + run_command(["git", "remote", "set-url", "origin", authenticated_url], cwd=helm_repo_path) + else: + logger.error("github token not found. Push may fail if credentials are not cached.") + return 1 + + run_command(["git", "push", "-u", "origin", branch_name], cwd=helm_repo_path) + + create_pull_request(branch_name, chart_version) + + except (RuntimeError, FileNotFoundError, PermissionError) as e: + logger.error(f"\nAn error occurred during local git operations: {e}") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) From f197d2337da49345a0543054868d111e6e0ea836 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Thu, 9 Oct 2025 19:28:44 +0200 Subject: [PATCH 2/9] Add newline' --- .evergreen-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen-release.yml b/.evergreen-release.yml index 29df75c3f..592adf44e 100644 --- a/.evergreen-release.yml +++ b/.evergreen-release.yml @@ -254,4 +254,4 @@ buildvariants: - release-ubuntu2404-small # This is required for CISA attestation https://jira.mongodb.org/browse/DEVPROD-17780 allowed_requesters: [ "patch", "github_tag" ] tasks: - - name: create_chart_release_pr \ No newline at end of file + - name: create_chart_release_pr From 4731aeba96872359174b08edc163d7cfde5541f4 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Fri, 10 Oct 2025 12:04:46 +0200 Subject: [PATCH 3/9] Fix .evergreenn-functions after successful run --- .evergreen-functions.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.evergreen-functions.yml b/.evergreen-functions.yml index 1733982ad..6c10be265 100644 --- a/.evergreen-functions.yml +++ b/.evergreen-functions.yml @@ -866,8 +866,7 @@ functions: env: GH_TOKEN: ${GH_TOKEN} MCK_DIR: ${workdir}/src/github.com/mongodb/mongodb-kubernetes - # binary: scripts/dev/run_python.sh scripts/create_chart_release_pr.py --chart_version ${RELEASE_OPERATOR_VERSION|*triggered_by_git_tag} - binary: scripts/dev/run_python.sh scripts/create_chart_release_pr.py --chart_version x.y.z + binary: scripts/dev/run_python.sh scripts/create_chart_release_pr.py --chart_version ${RELEASE_OPERATOR_VERSION|*triggered_by_git_tag} release_kubectl_mongodb_plugin: - command: github.generate_token From c09f4c41d0076d096757a747c2868f4630513db2 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Mon, 13 Oct 2025 21:38:29 +0200 Subject: [PATCH 4/9] Address review comments 1. Make main smaller 2. Handle exceptions better --- .evergreen-functions.yml | 5 +- .../{ => release}/create_chart_release_pr.py | 89 +++++++++---------- 2 files changed, 44 insertions(+), 50 deletions(-) rename scripts/{ => release}/create_chart_release_pr.py (61%) diff --git a/.evergreen-functions.yml b/.evergreen-functions.yml index 6c10be265..d68dc277b 100644 --- a/.evergreen-functions.yml +++ b/.evergreen-functions.yml @@ -862,11 +862,12 @@ functions: include_expansions_in_env: - GH_TOKEN - workdir - - RELEASE_OPERATOR_VERSION + - OPERATOR_VERSION env: GH_TOKEN: ${GH_TOKEN} MCK_DIR: ${workdir}/src/github.com/mongodb/mongodb-kubernetes - binary: scripts/dev/run_python.sh scripts/create_chart_release_pr.py --chart_version ${RELEASE_OPERATOR_VERSION|*triggered_by_git_tag} + binary: scripts/dev/run_python.sh scripts/release/create_chart_release_pr.py --chart_version a.b.c + # binary: scripts/dev/run_python.sh scripts/release/create_chart_release_pr.py --chart_version ${OPERATOR_VERSION|*triggered_by_git_tag} release_kubectl_mongodb_plugin: - command: github.generate_token diff --git a/scripts/create_chart_release_pr.py b/scripts/release/create_chart_release_pr.py similarity index 61% rename from scripts/create_chart_release_pr.py rename to scripts/release/create_chart_release_pr.py index 3eff23e6a..16237bcae 100644 --- a/scripts/create_chart_release_pr.py +++ b/scripts/release/create_chart_release_pr.py @@ -4,6 +4,7 @@ import subprocess import sys import tempfile +from pathlib import Path from github import Github, GithubException @@ -20,22 +21,14 @@ def run_command(command, cwd=None): logger.info(f"Running command: {' '.join(command)} in directory {cwd}") result = subprocess.run(command, capture_output=True, text=True, cwd=cwd) if result.returncode != 0: - logger.error("ERROR:") - logger.error(result.stdout) - logger.error(result.stderr) - raise RuntimeError(f"Command failed: {' '.join(command)}") + raise RuntimeError(f"Command {' '.join(command)} failed. Stdout: {result.stdout}, stderr: {result.stderr}") logger.info("Command succeeded") return result.stdout -def create_pull_request(branch_name, chart_version): +# create_pull_request creates the pull request to the helm-charts repo +def create_pull_request(branch_name, chart_version, github_token): logger.info("Creating the pull request in the helm-charts repo.") - github_token = os.environ.get("GH_TOKEN") - - if not github_token: - logger.info("Warning: GH_TOKEN environment variable not set.") - pr_url = f"https://github.com/{REPO_NAME}/pull/new/{branch_name}" - logger.info(f"Please create the Pull Request manually by following the link:\n{pr_url}") try: g = Github(github_token) @@ -50,40 +43,29 @@ def create_pull_request(branch_name, chart_version): base=BASE_BRANCH, ) logger.info(f"Successfully created Pull Request {pr.html_url}") - except GithubException as e: - logger.error(f"ERROR: Could not create Pull Request. GitHub API returned an error: {e.status}") - logger.error(f"Details: {e.data}") - logger.error("Please check your github token permissions and repository details.") - return 1 except Exception as e: - logger.error(f"An unexpected error occurred while creating the PR: {e}") - return 1 + pr_url = f"https://github.com/{REPO_NAME}/pull/new/{branch_name}" + logger.error( + f"An unexpected error occurred while creating the PR: {e}. Please create the PR manually by following this link {pr_url}" + ) + raise Exception( + f"An unexpected error occurred while creating the PR: {e}. Please create the PR manually by following this link {pr_url}" + ) -def main(): - parser = argparse.ArgumentParser( - description="Automate PR creation to release MCK helm chart to github helm chart repo." - ) - parser.add_argument( - "--chart_version", help="The version of the chart to be released (e.g., '1.3.0').", required=True - ) - args = parser.parse_args() - - chart_version = args.chart_version +def commit_and_push_chart(chart_version): branch_name = f"mck-release-{chart_version}" - workdir = os.environ.get("MCK_DIR") - if not workdir: - logger.info("The workdir environment variable is not set this should be set to the root of MCK code.") - return 1 + mck_dir = Path(".").resolve() # source_chart_path is local helm chart in MCK repo - source_chart_path = os.path.join(workdir, "helm_chart") + source_chart_path = os.path.join(mck_dir, "helm_chart") if not os.path.isdir(source_chart_path): - logger.info(f"The source chart path '{source_chart_path}' is not a valid directory.") - return 1 + raise Exception(f"The source chart path '{source_chart_path}' is not a valid directory.") github_token = os.environ.get("GH_TOKEN") + if not github_token: + raise Exception("github token not found. Returning because git push will fail.") with tempfile.TemporaryDirectory() as temp_dir: helm_repo_path = os.path.join(temp_dir, "helm-charts") @@ -110,22 +92,33 @@ def main(): run_command(["git", "add", "."], cwd=helm_repo_path) run_command(["git", "commit", "-m", commit_message], cwd=helm_repo_path) - if github_token: - logger.info("Configuring remote URL for authenticated push...") - # Constructs a URL like https://x-access-token:YOUR_TOKEN@github.com/owner/repo.git - authenticated_url = f"https://x-access-token:{github_token}@{REPO_URL.split('//')[1]}" - run_command(["git", "remote", "set-url", "origin", authenticated_url], cwd=helm_repo_path) - else: - logger.error("github token not found. Push may fail if credentials are not cached.") - return 1 - + logger.info("Configuring remote URL for authenticated push...") + # Constructs a URL like https://x-access-token:YOUR_TOKEN@github.com/owner/repo.git + authenticated_url = f"https://x-access-token:{github_token}@{REPO_URL.split('//')[1]}" + run_command(["git", "remote", "set-url", "origin", authenticated_url], cwd=helm_repo_path) run_command(["git", "push", "-u", "origin", branch_name], cwd=helm_repo_path) - create_pull_request(branch_name, chart_version) + create_pull_request(branch_name, chart_version, github_token) + + except Exception as e: + raise Exception(f"An error occurred while performing git commit and push, error: {e}") + + +def main(): + parser = argparse.ArgumentParser( + description="Automate PR creation to release MCK helm chart to github helm chart repo." + ) + parser.add_argument( + "--chart_version", help="The version of the chart to be released (e.g., '1.3.0').", required=True + ) + args = parser.parse_args() - except (RuntimeError, FileNotFoundError, PermissionError) as e: - logger.error(f"\nAn error occurred during local git operations: {e}") - return 1 + chart_version = args.chart_version + try: + commit_and_push_chart(chart_version) + except Exception as e: + logger.error(f"Failed releasing helm chart, error: {e}") + raise e if __name__ == "__main__": From db3da25b92fba740d84bbc40f77b9ae5c7b03dac Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Mon, 13 Oct 2025 21:46:47 +0200 Subject: [PATCH 5/9] Uncomment testing changes --- .evergreen-functions.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.evergreen-functions.yml b/.evergreen-functions.yml index d68dc277b..cb3d4c4a4 100644 --- a/.evergreen-functions.yml +++ b/.evergreen-functions.yml @@ -866,8 +866,7 @@ functions: env: GH_TOKEN: ${GH_TOKEN} MCK_DIR: ${workdir}/src/github.com/mongodb/mongodb-kubernetes - binary: scripts/dev/run_python.sh scripts/release/create_chart_release_pr.py --chart_version a.b.c - # binary: scripts/dev/run_python.sh scripts/release/create_chart_release_pr.py --chart_version ${OPERATOR_VERSION|*triggered_by_git_tag} + binary: scripts/dev/run_python.sh scripts/release/create_chart_release_pr.py --chart_version ${OPERATOR_VERSION|*triggered_by_git_tag} release_kubectl_mongodb_plugin: - command: github.generate_token From 933d6106c2951b1e1aaf2713ec8686b14967c840 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Tue, 14 Oct 2025 16:35:41 +0200 Subject: [PATCH 6/9] Remove unused env var --- .evergreen-functions.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.evergreen-functions.yml b/.evergreen-functions.yml index cb3d4c4a4..57e7c4981 100644 --- a/.evergreen-functions.yml +++ b/.evergreen-functions.yml @@ -865,7 +865,6 @@ functions: - OPERATOR_VERSION env: GH_TOKEN: ${GH_TOKEN} - MCK_DIR: ${workdir}/src/github.com/mongodb/mongodb-kubernetes binary: scripts/dev/run_python.sh scripts/release/create_chart_release_pr.py --chart_version ${OPERATOR_VERSION|*triggered_by_git_tag} release_kubectl_mongodb_plugin: From 1c6a9b00994fd7cb21e6b25a0190bc421241aa20 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Wed, 15 Oct 2025 11:54:21 +0200 Subject: [PATCH 7/9] Address review comments --- scripts/release/create_chart_release_pr.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/release/create_chart_release_pr.py b/scripts/release/create_chart_release_pr.py index 16237bcae..5620a562d 100644 --- a/scripts/release/create_chart_release_pr.py +++ b/scripts/release/create_chart_release_pr.py @@ -45,9 +45,6 @@ def create_pull_request(branch_name, chart_version, github_token): logger.info(f"Successfully created Pull Request {pr.html_url}") except Exception as e: pr_url = f"https://github.com/{REPO_NAME}/pull/new/{branch_name}" - logger.error( - f"An unexpected error occurred while creating the PR: {e}. Please create the PR manually by following this link {pr_url}" - ) raise Exception( f"An unexpected error occurred while creating the PR: {e}. Please create the PR manually by following this link {pr_url}" ) From 5378cfb015e562bb0c994c7d24990fb56dba72a2 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Thu, 16 Oct 2025 11:32:34 +0200 Subject: [PATCH 8/9] Address review comments 1. Change info to debug 2. Specify types of arguments in run_command --- scripts/release/create_chart_release_pr.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/release/create_chart_release_pr.py b/scripts/release/create_chart_release_pr.py index 5620a562d..6f07aa9bd 100644 --- a/scripts/release/create_chart_release_pr.py +++ b/scripts/release/create_chart_release_pr.py @@ -5,6 +5,7 @@ import sys import tempfile from pathlib import Path +from typing import List, Optional from github import Github, GithubException @@ -17,12 +18,12 @@ # run_command runs the command `command` from dir cwd -def run_command(command, cwd=None): - logger.info(f"Running command: {' '.join(command)} in directory {cwd}") +def run_command(command: List[str], cwd: Optional[str] = None): + logger.debug(f"Running command: {' '.join(command)} in directory {cwd}") result = subprocess.run(command, capture_output=True, text=True, cwd=cwd) if result.returncode != 0: raise RuntimeError(f"Command {' '.join(command)} failed. Stdout: {result.stdout}, stderr: {result.stderr}") - logger.info("Command succeeded") + logger.debug("Command succeeded") return result.stdout From 7e8b4383b7daa13249b7483c329f7ffca625d8d7 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Thu, 16 Oct 2025 15:32:10 +0200 Subject: [PATCH 9/9] Address nits --- scripts/release/create_chart_release_pr.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/release/create_chart_release_pr.py b/scripts/release/create_chart_release_pr.py index 6f07aa9bd..00d293cee 100644 --- a/scripts/release/create_chart_release_pr.py +++ b/scripts/release/create_chart_release_pr.py @@ -63,18 +63,18 @@ def commit_and_push_chart(chart_version): github_token = os.environ.get("GH_TOKEN") if not github_token: - raise Exception("github token not found. Returning because git push will fail.") + raise Exception("github token not found, git push will fail.") with tempfile.TemporaryDirectory() as temp_dir: helm_repo_path = os.path.join(temp_dir, "helm-charts") - logger.info(f"Working in a temporary directory: {temp_dir}") + logger.debug(f"Working in a temporary directory: {temp_dir}") try: run_command(["git", "clone", REPO_URL, helm_repo_path]) run_command(["git", "checkout", "-b", branch_name], cwd=helm_repo_path) target_dir = os.path.join(helm_repo_path, TARGET_CHART_SUBDIR) - logger.info(f"Clearing content from dir '{target_dir}'") + logger.debug(f"Clearing content from dir '{target_dir}'") if os.path.exists(target_dir): for item in os.listdir(target_dir): item_path = os.path.join(target_dir, item) @@ -83,14 +83,14 @@ def commit_and_push_chart(chart_version): else: os.remove(item_path) - logger.info(f"Copying local MCK chart from '{source_chart_path}' to helm repo chart path {target_dir}") + logger.debug(f"Copying local MCK chart from '{source_chart_path}' to helm repo chart path {target_dir}") shutil.copytree(source_chart_path, target_dir, dirs_exist_ok=True) commit_message = f"Release MCK {chart_version}" run_command(["git", "add", "."], cwd=helm_repo_path) run_command(["git", "commit", "-m", commit_message], cwd=helm_repo_path) - logger.info("Configuring remote URL for authenticated push...") + logger.debug("Configuring remote URL for authenticated push...") # Constructs a URL like https://x-access-token:YOUR_TOKEN@github.com/owner/repo.git authenticated_url = f"https://x-access-token:{github_token}@{REPO_URL.split('//')[1]}" run_command(["git", "remote", "set-url", "origin", authenticated_url], cwd=helm_repo_path)