From fa8128bc4273ecaea5457dfcb0e63b817991fff1 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Thu, 7 Aug 2025 16:26:42 +0200 Subject: [PATCH 01/16] Update `build_multi_cluster_binary` evg function to use `goreleaser` to build kubectl mongodb binary --- .evergreen-functions.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.evergreen-functions.yml b/.evergreen-functions.yml index 4b0131b34..9884f5d3a 100644 --- a/.evergreen-functions.yml +++ b/.evergreen-functions.yml @@ -511,6 +511,21 @@ functions: build_multi_cluster_binary: - command: subprocess.exec params: + include_expansions_in_env: + - github_commit + - GRS_USERNAME + - GRS_PASSWORD + - PKCS11_URI + - ARTIFACTORY_URL + - ARTIFACTORY_PASSWORD + - SIGNING_IMAGE_URI + - macos_notary_keyid + - macos_notary_secret + - workdir + - triggered_by_git_tag + env: + MACOS_NOTARY_KEY: ${macos_notary_keyid} + MACOS_NOTARY_SECRET: ${macos_notary_secret} working_dir: src/github.com/mongodb/mongodb-kubernetes binary: scripts/dev/run_python.sh scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py From ffd5d17d11ee5f801028a0b6432a2592365d320d Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Mon, 1 Sep 2025 18:39:32 +0200 Subject: [PATCH 02/16] Update the download plugin script to download the plugins for all architectures --- scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py b/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py index 5e0d9b3bb..c682aa5f8 100755 --- a/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py +++ b/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py @@ -16,7 +16,6 @@ GORELEASER_DIST_DIR = "dist" - def run_goreleaser(): try: command = ["goreleaser", "build", "--snapshot", "--clean", "--skip", "post-hooks"] From b348aaea0c03ed10a2913220182ba6c3de483d13 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Tue, 2 Sep 2025 11:33:16 +0200 Subject: [PATCH 03/16] Fix lint issues --- scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py b/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py index c682aa5f8..5e0d9b3bb 100755 --- a/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py +++ b/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py @@ -16,6 +16,7 @@ GORELEASER_DIST_DIR = "dist" + def run_goreleaser(): try: command = ["goreleaser", "build", "--snapshot", "--clean", "--skip", "post-hooks"] From 03a03f366844dd2791b31f44e71c58601543b285 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Fri, 17 Oct 2025 19:31:45 +0200 Subject: [PATCH 04/16] Rebase with master for build scenario changes --- .evergreen-functions.yml | 15 --------------- .../python/build_kubectl_plugin.py | 1 - 2 files changed, 16 deletions(-) diff --git a/.evergreen-functions.yml b/.evergreen-functions.yml index 9884f5d3a..4b0131b34 100644 --- a/.evergreen-functions.yml +++ b/.evergreen-functions.yml @@ -511,21 +511,6 @@ functions: build_multi_cluster_binary: - command: subprocess.exec params: - include_expansions_in_env: - - github_commit - - GRS_USERNAME - - GRS_PASSWORD - - PKCS11_URI - - ARTIFACTORY_URL - - ARTIFACTORY_PASSWORD - - SIGNING_IMAGE_URI - - macos_notary_keyid - - macos_notary_secret - - workdir - - triggered_by_git_tag - env: - MACOS_NOTARY_KEY: ${macos_notary_keyid} - MACOS_NOTARY_SECRET: ${macos_notary_secret} working_dir: src/github.com/mongodb/mongodb-kubernetes binary: scripts/dev/run_python.sh scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py diff --git a/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py b/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py index 5e0d9b3bb..cd2320c21 100755 --- a/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py +++ b/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py @@ -147,6 +147,5 @@ def main(): download_plugin_for_tests_image(kubectl_plugin_build_info.s3_store, version) - if __name__ == "__main__": main() From 539235e5758347d036b3ad4036ec752881eadea4 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Thu, 7 Aug 2025 16:26:42 +0200 Subject: [PATCH 05/16] Update `build_multi_cluster_binary` evg function to use `goreleaser` to build kubectl mongodb binary --- .evergreen-functions.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.evergreen-functions.yml b/.evergreen-functions.yml index 4b0131b34..57dfef605 100644 --- a/.evergreen-functions.yml +++ b/.evergreen-functions.yml @@ -505,6 +505,21 @@ functions: publish_helm_chart: - command: subprocess.exec params: + include_expansions_in_env: + - github_commit + - GRS_USERNAME + - GRS_PASSWORD + - PKCS11_URI + - ARTIFACTORY_URL + - ARTIFACTORY_PASSWORD + - SIGNING_IMAGE_URI + - macos_notary_keyid + - macos_notary_secret + - workdir + - triggered_by_git_tag + env: + MACOS_NOTARY_KEY: ${macos_notary_keyid} + MACOS_NOTARY_SECRET: ${macos_notary_secret} working_dir: src/github.com/mongodb/mongodb-kubernetes binary: scripts/release/publish_helm_chart.sh From 05793c67836f1e86ff5f3c976116e3f722429c54 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Thu, 7 Aug 2025 16:26:42 +0200 Subject: [PATCH 06/16] Add an evergreen function to build kubectl plugin and push to S3 --- scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh b/scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh index 06a69e640..c9422f347 100755 --- a/scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh +++ b/scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh @@ -29,6 +29,14 @@ if [[ -f "./dist/kubectl-mongodb_darwin_amd64_v1/kubectl-mongodb" && -f "./dist/ -b com.mongodb.mongodb-kubectl-mongodb \ -o ./dist/kubectl-mongodb_macos_signed.zip + echo "printing output zip file content" + unzip -l ./dist/kubectl-mongodb_macos_signed.zip + echo "done" + + echo "running unzip -t to verify that the op zip file of macnotary is correct" + unzip -t ./dist/kubectl-mongodb_macos_signed.zip + echo "done" + echo "replacing original files" unzip -oj ./dist/kubectl-mongodb_macos_signed.zip dist/kubectl-mongodb_darwin_amd64_v1/kubectl-mongodb -d ./dist/kubectl-mongodb_darwin_amd64_v1/ unzip -oj ./dist/kubectl-mongodb_macos_signed.zip dist/kubectl-mongodb_darwin_arm64/kubectl-mongodb -d ./dist/kubectl-mongodb_darwin_arm64/ From f42c6fe6e0b23562e78195aac901c78ef91ee1cc Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Thu, 7 Aug 2025 17:24:35 +0200 Subject: [PATCH 07/16] test --- .goreleaser.yaml | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 884ae3fc4..4f8d158c7 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -18,19 +18,19 @@ builds: - ppc64le hooks: # This will notarize Apple binaries and replace goreleaser bins with the notarized ones - post: - - cmd: ./scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh - output: true - - cmd: ./scripts/release/kubectl-mongodb/sign.sh {{ .Path }} - env: - - GRS_USERNAME={{ .Env.GRS_USERNAME }} - - GRS_PASSWORD={{ .Env.GRS_PASSWORD }} - - PKCS11_URI={{ .Env.PKCS11_URI }} - - ARTIFACTORY_URL={{ .Env.ARTIFACTORY_URL }} - - SIGNING_IMAGE_URI={{ .Env.SIGNING_IMAGE_URI }} - - ARTIFACTORY_USERNAME=mongodb-enterprise-kubernetes-operator - - ARTIFACTORY_PASSWORD={{ .Env.ARTIFACTORY_PASSWORD }} - - cmd: ./scripts/release/kubectl-mongodb/verify.sh {{ .Path }} && echo "VERIFIED OK" +# post: +# - cmd: ./scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh +# output: true +# - cmd: ./scripts/release/kubectl-mongodb/sign.sh {{ .Path }} +# env: +# - GRS_USERNAME={{ .Env.GRS_USERNAME }} +# - GRS_PASSWORD={{ .Env.GRS_PASSWORD }} +# - PKCS11_URI={{ .Env.PKCS11_URI }} +# - ARTIFACTORY_URL={{ .Env.ARTIFACTORY_URL }} +# - SIGNING_IMAGE_URI={{ .Env.SIGNING_IMAGE_URI }} +# - ARTIFACTORY_USERNAME=mongodb-enterprise-kubernetes-operator +# - ARTIFACTORY_PASSWORD={{ .Env.ARTIFACTORY_PASSWORD }} +# - cmd: ./scripts/release/kubectl-mongodb/verify.sh {{ .Path }} && echo "VERIFIED OK" archives: - format: tar.gz @@ -53,3 +53,9 @@ release: git: tag_sort: -version:creatordate + +blobs: + - provider: s3 + region: eu-north-1 + bucket: mongodb-kubernetes-dev + directory: test-dir From 8aa524fe9024fdc467f99c0771859012c32d666e Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Fri, 8 Aug 2025 13:38:16 +0200 Subject: [PATCH 08/16] Add evergreen function to promote and release kubectl plugin --- .evergreen.yml | 6 + .../python/build_kubectl_plugin.py | 1 + .../python/promote_kubectl_plugin.py | 155 ++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 scripts/release/kubectl-mongodb/python/promote_kubectl_plugin.py diff --git a/.evergreen.yml b/.evergreen.yml index f99cc4c41..643dc2bc3 100644 --- a/.evergreen.yml +++ b/.evergreen.yml @@ -1700,6 +1700,12 @@ buildvariants: tasks: - name: generate_perf_tasks_30_thread + - name: kubectl_plugin_release + run_on: + - ubuntu2204-small + tasks: + - name: release_kubectl_plugin + ### Prerequisites for E2E test suite - name: init_test_run diff --git a/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py b/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py index cd2320c21..b900ad96d 100755 --- a/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py +++ b/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py @@ -149,3 +149,4 @@ def main(): if __name__ == "__main__": main() + \ No newline at end of file diff --git a/scripts/release/kubectl-mongodb/python/promote_kubectl_plugin.py b/scripts/release/kubectl-mongodb/python/promote_kubectl_plugin.py new file mode 100644 index 000000000..1243a05e7 --- /dev/null +++ b/scripts/release/kubectl-mongodb/python/promote_kubectl_plugin.py @@ -0,0 +1,155 @@ +import argparse +import os +import sys +import tarfile + +import boto3 +import build_kubectl_plugin +from botocore.exceptions import ClientError, NoCredentialsError, PartialCredentialsError +from github import Github, GithubException + +GITHUB_REPO = "mongodb/mongodb-kubernetes" +GITHUB_TOKEN = os.environ.get("GH_TOKEN") + +LOCAL_ARTIFACTS_DIR = "artifacts" + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--release_version", + required=True, + help="product release version, ideally should match with github tag/release.", + ) + parser.add_argument("--staging_commit", required=True, help="staging commit that we want to promote to release.") + + args = parser.parse_args() + download_artifacts_from_s3(args.release_version, args.staging_commit) + arcs = create_tarballs() + # promote to release s3 bucket + print(f"created archives are {arcs}") + upload_assets_to_github_release(arcs, args.release_version) + + +def s3_artifacts_path_to_local_path(commit_sha: str): + return { + f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_darwin_amd64_v1/": "kubectl-mongodb_darwin_amd64_v1", + f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_darwin_arm64/": "kubectl-mongodb_darwin_arm64", + f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_linux_amd64_v1/": "kubectl-mongodb_linux_amd64_v1", + f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_linux_arm64/": "kubectl-mongodb_linux_arm64", + } + + +def download_artifacts_from_s3(release_version: str, commit_sha: str): + print(f"Starting download of artifacts from S3 bucket: {build_kubectl_plugin.DEV_S3_BUCKET_NAME}") + + try: + s3_client = boto3.client("s3", region_name=build_kubectl_plugin.AWS_REGION) + except (NoCredentialsError, PartialCredentialsError): + print("ERROR: AWS credentials were not set.") + sys.exit(1) + except Exception as e: + print(f"An error occurred connecting to S3: {e}") + sys.exit(1) + + artifacts_to_promote = s3_artifacts_path_to_local_path(commit_sha) + + # Create the local temporary directory if it doesn't exist + os.makedirs(LOCAL_ARTIFACTS_DIR, exist_ok=True) + + download_count = 0 + for s3_artifact_dir, local_subdir in artifacts_to_promote.items(): + print(f"Copying from s3://{build_kubectl_plugin.DEV_S3_BUCKET_NAME}/{s3_artifact_dir} to {local_subdir}/") + try: + paginator = s3_client.get_paginator("list_objects_v2") + pages = paginator.paginate(Bucket=build_kubectl_plugin.DEV_S3_BUCKET_NAME, Prefix=s3_artifact_dir) + + for page in pages: + if "Contents" not in page: + continue + for obj in page["Contents"]: + s3_key = obj["Key"] + if s3_key.endswith("/"): + continue + + # Get the path of the file relative to its S3 prefix, this would mostly be the object name itself + # if s3_artifact_dir doesn't container directories and has just the objects. + relative_path = os.path.relpath(s3_key, s3_artifact_dir) + + final_local_path = os.path.join(LOCAL_ARTIFACTS_DIR, local_subdir, relative_path) + + # Create the local directory structure if it doesn't exist + os.makedirs(os.path.dirname(final_local_path), exist_ok=True) + + print(f"Downloading {s3_key} to {final_local_path}") + s3_client.download_file(build_kubectl_plugin.DEV_S3_BUCKET_NAME, s3_key, final_local_path) + download_count += 1 + + except ClientError as e: + print(f"ERROR: Failed to list or download from prefix '{s3_artifact_dir}'. S3 Client Error: {e}") + return False + + print("All the artifacts have been downloaded successfully.") + return True + + +def create_tarballs(): + print(f"Creating archives for subdirectories in {LOCAL_ARTIFACTS_DIR}") + created_archives = [] + original_cwd = os.getcwd() + try: + os.chdir(LOCAL_ARTIFACTS_DIR) + + for dir_name in os.listdir("."): + if os.path.isdir(dir_name): + archive_name = f"{dir_name}.tar.gz" + + with tarfile.open(archive_name, "w:gz") as tar: + tar.add(dir_name) + + full_archive_path = os.path.join(original_cwd, LOCAL_ARTIFACTS_DIR, archive_name) + print(f"Successfully created archive at {full_archive_path}") + created_archives.append(full_archive_path) + + except Exception as e: + print(f"ERROR: Failed to create tar.gz archives: {e}") + return [] + finally: + os.chdir(original_cwd) + + return created_archives + + +def upload_assets_to_github_release(asset_paths, release_version: str): + if not GITHUB_TOKEN: + print("ERROR: GITHUB_TOKEN environment variable not set.") + sys.exit(1) + + try: + g = Github(GITHUB_TOKEN) + repo = g.get_repo(GITHUB_REPO) + except GithubException as e: + print(f"ERROR: Could not connect to GitHub or find repository '{GITHUB_REPO}', Error {e}.") + sys.exit(1) + + try: + release = repo.get_release(release_version) + except GithubException as e: + print( + f"ERROR: Could not find release with tag '{release_version}'. Please ensure release exists already. Error: {e}" + ) + return + + for asset_path in asset_paths: + asset_name = os.path.basename(asset_path) + print(f"Uploading artifact '{asset_name}' to github release as asset") + try: + release.upload_asset(path=asset_path, name=asset_name, content_type="application/gzip") + except GithubException as e: + print(f"ERROR: Failed to upload asset {asset_name}. Error: {e}") + except Exception as e: + print(f"An unexpected error occurred during upload of {asset_name}: {e}") + + +if __name__ == "__main__": + main() From caff410b67116c6ac7ac90b50b8ff9f340ca3171 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Fri, 8 Aug 2025 18:33:26 +0200 Subject: [PATCH 09/16] Update --- .../python/promote_kubectl_plugin.py | 98 ++++++++++++++++--- 1 file changed, 83 insertions(+), 15 deletions(-) diff --git a/scripts/release/kubectl-mongodb/python/promote_kubectl_plugin.py b/scripts/release/kubectl-mongodb/python/promote_kubectl_plugin.py index 1243a05e7..3d0ec762a 100644 --- a/scripts/release/kubectl-mongodb/python/promote_kubectl_plugin.py +++ b/scripts/release/kubectl-mongodb/python/promote_kubectl_plugin.py @@ -25,23 +25,91 @@ def main(): args = parser.parse_args() download_artifacts_from_s3(args.release_version, args.staging_commit) - arcs = create_tarballs() - # promote to release s3 bucket - print(f"created archives are {arcs}") - upload_assets_to_github_release(arcs, args.release_version) + promote_to_release_bucket(args.staging_commit, args.release_version) -def s3_artifacts_path_to_local_path(commit_sha: str): + artifacts_tar = create_tarballs() + + upload_assets_to_github_release(artifacts_tar, args.release_version) + + +def artifacts_source_dir_s3(commit_sha: str): + return f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/" + + +def artifacts_dest_dir_s3(release_verion: str): + return f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{release_verion}/dist/" + + +def promote_to_release_bucket(commit_sha: str, release_version: str): + try: + s3_client = boto3.client("s3", region_name=build_kubectl_plugin.AWS_REGION) + except (NoCredentialsError, PartialCredentialsError): + print("ERROR: AWS credentials not found. Please configure AWS credentials.") + sys.exit(1) + except Exception as e: + print(f"An error occurred connecting to S3: {e}") + sys.exit(1) + + copy_count = 0 + try: + paginator = s3_client.get_paginator("list_objects_v2") + pages = paginator.paginate( + Bucket=build_kubectl_plugin.DEV_S3_BUCKET_NAME, Prefix=artifacts_source_dir_s3(commit_sha) + ) + + for page in pages: + if "Contents" not in page: + break + + for obj in page["Contents"]: + source_key = obj["Key"] + + if source_key.endswith("/"): + continue + + # Determine the new key for the destination + relative_path = os.path.relpath(source_key, artifacts_source_dir_s3(commit_sha)) + + destination_dir = artifacts_dest_dir_s3(release_version) + destination_key = os.path.join(destination_dir, relative_path) + + # Ensure forward slashes for S3 compatibility + destination_key = destination_key.replace(os.sep, "/") + + print(f"Copying {source_key} to {destination_key}...") + + # Prepare the source object for the copy operation + copy_source = {"Bucket": build_kubectl_plugin.DEV_S3_BUCKET_NAME, "Key": source_key} + + # Perform the server-side copy + s3_client.copy_object( + CopySource=copy_source, Bucket=build_kubectl_plugin.STAGING_S3_BUCKET_NAME, Key=destination_key + ) + copy_count += 1 + + except ClientError as e: + print(f"ERROR: A client error occurred during the copy operation. Error: {e}") + sys.exit(1) + except Exception as e: + print(f"An unexpected error occurred: {e}") + sys.exit(1) + + if copy_count > 0: + print(f"Successfully copied {copy_count} object(s).") + + +def s3_artifacts_path_to_local_path(release_version: str, commit_sha: str): return { - f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_darwin_amd64_v1/": "kubectl-mongodb_darwin_amd64_v1", - f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_darwin_arm64/": "kubectl-mongodb_darwin_arm64", - f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_linux_amd64_v1/": "kubectl-mongodb_linux_amd64_v1", - f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_linux_arm64/": "kubectl-mongodb_linux_arm64", + f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_darwin_amd64_v1/": f"kubectl-mongodb_{release_version}_darwin_amd64", + f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_darwin_arm64/": f"kubectl-mongodb_{release_version}_darwin_arm64", + f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_linux_amd64_v1/": f"kubectl-mongodb_{release_version}_linux_amd64", + f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_linux_arm64/": f"kubectl-mongodb_{release_version}_linux_arm64", } def download_artifacts_from_s3(release_version: str, commit_sha: str): - print(f"Starting download of artifacts from S3 bucket: {build_kubectl_plugin.DEV_S3_BUCKET_NAME}") + print(f"\nStarting download of artifacts from S3 bucket: {build_kubectl_plugin.DEV_S3_BUCKET_NAME}") try: s3_client = boto3.client("s3", region_name=build_kubectl_plugin.AWS_REGION) @@ -52,24 +120,26 @@ def download_artifacts_from_s3(release_version: str, commit_sha: str): print(f"An error occurred connecting to S3: {e}") sys.exit(1) - artifacts_to_promote = s3_artifacts_path_to_local_path(commit_sha) + artifacts_to_promote = s3_artifacts_path_to_local_path(release_version, commit_sha) # Create the local temporary directory if it doesn't exist os.makedirs(LOCAL_ARTIFACTS_DIR, exist_ok=True) download_count = 0 for s3_artifact_dir, local_subdir in artifacts_to_promote.items(): - print(f"Copying from s3://{build_kubectl_plugin.DEV_S3_BUCKET_NAME}/{s3_artifact_dir} to {local_subdir}/") try: paginator = s3_client.get_paginator("list_objects_v2") pages = paginator.paginate(Bucket=build_kubectl_plugin.DEV_S3_BUCKET_NAME, Prefix=s3_artifact_dir) for page in pages: + # "Contents" corresponds to the directory in the S3 bucket if "Contents" not in page: continue for obj in page["Contents"]: + # obj is the S3 object in page["Contents"] directory s3_key = obj["Key"] if s3_key.endswith("/"): + # it's a directory continue # Get the path of the file relative to its S3 prefix, this would mostly be the object name itself @@ -94,7 +164,7 @@ def download_artifacts_from_s3(release_version: str, commit_sha: str): def create_tarballs(): - print(f"Creating archives for subdirectories in {LOCAL_ARTIFACTS_DIR}") + print(f"\nCreating archives for subdirectories in {LOCAL_ARTIFACTS_DIR}") created_archives = [] original_cwd = os.getcwd() try: @@ -114,8 +184,6 @@ def create_tarballs(): except Exception as e: print(f"ERROR: Failed to create tar.gz archives: {e}") return [] - finally: - os.chdir(original_cwd) return created_archives From 6176e438b3a330b1469ba3a9ded7a80f48c804d3 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Wed, 20 Aug 2025 22:27:27 +0200 Subject: [PATCH 10/16] Test --- .evergreen.yml | 1 + .goreleaser.yaml | 32 ++- .../kubectl-mongodb/kubectl_mac_notarize.sh | 31 +-- .../python/promote_kubectl_plugin.py | 219 ++++++++++-------- 4 files changed, 159 insertions(+), 124 deletions(-) diff --git a/.evergreen.yml b/.evergreen.yml index 643dc2bc3..df456f2f5 100644 --- a/.evergreen.yml +++ b/.evergreen.yml @@ -1701,6 +1701,7 @@ buildvariants: - name: generate_perf_tasks_30_thread - name: kubectl_plugin_release + display_name: kubectl_plugin_release run_on: - ubuntu2204-small tasks: diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 4f8d158c7..884ae3fc4 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -18,19 +18,19 @@ builds: - ppc64le hooks: # This will notarize Apple binaries and replace goreleaser bins with the notarized ones -# post: -# - cmd: ./scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh -# output: true -# - cmd: ./scripts/release/kubectl-mongodb/sign.sh {{ .Path }} -# env: -# - GRS_USERNAME={{ .Env.GRS_USERNAME }} -# - GRS_PASSWORD={{ .Env.GRS_PASSWORD }} -# - PKCS11_URI={{ .Env.PKCS11_URI }} -# - ARTIFACTORY_URL={{ .Env.ARTIFACTORY_URL }} -# - SIGNING_IMAGE_URI={{ .Env.SIGNING_IMAGE_URI }} -# - ARTIFACTORY_USERNAME=mongodb-enterprise-kubernetes-operator -# - ARTIFACTORY_PASSWORD={{ .Env.ARTIFACTORY_PASSWORD }} -# - cmd: ./scripts/release/kubectl-mongodb/verify.sh {{ .Path }} && echo "VERIFIED OK" + post: + - cmd: ./scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh + output: true + - cmd: ./scripts/release/kubectl-mongodb/sign.sh {{ .Path }} + env: + - GRS_USERNAME={{ .Env.GRS_USERNAME }} + - GRS_PASSWORD={{ .Env.GRS_PASSWORD }} + - PKCS11_URI={{ .Env.PKCS11_URI }} + - ARTIFACTORY_URL={{ .Env.ARTIFACTORY_URL }} + - SIGNING_IMAGE_URI={{ .Env.SIGNING_IMAGE_URI }} + - ARTIFACTORY_USERNAME=mongodb-enterprise-kubernetes-operator + - ARTIFACTORY_PASSWORD={{ .Env.ARTIFACTORY_PASSWORD }} + - cmd: ./scripts/release/kubectl-mongodb/verify.sh {{ .Path }} && echo "VERIFIED OK" archives: - format: tar.gz @@ -53,9 +53,3 @@ release: git: tag_sort: -version:creatordate - -blobs: - - provider: s3 - region: eu-north-1 - bucket: mongodb-kubernetes-dev - directory: test-dir diff --git a/scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh b/scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh index c9422f347..9f00c170b 100755 --- a/scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh +++ b/scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh @@ -20,24 +20,27 @@ set -Eeou pipefail # This depends on binaries being generated in a goreleaser manner and gon being set up. # goreleaser should already take care of calling this script as a hook. -if [[ -f "./dist/kubectl-mongodb_darwin_amd64_v1/kubectl-mongodb" && -f "./dist/kubectl-mongodb_darwin_arm64/kubectl-mongodb" && ! -f "./dist/kubectl-mongodb_macos_signed.zip" ]]; then +if [ -z "${1-}" ]; then + echo "Error: Missing required argument as first positional parameter to script" + echo "Usage: ./kubectl_mac_notarize.sh " + exit 1 +fi + +version=$1 + +darwin_amd64_dir="./artifacts/kubectl-mongodb_${version}_darwin_amd64" +darwin_arm64_dir="./artifacts/kubectl-mongodb_${version}_darwin_arm64" + +if [[ -f "${darwin_amd64_dir}/kubectl-mongodb" && -f "${darwin_arm64_dir}/kubectl-mongodb" && ! -f "./artifacts/kubectl-mongodb_macos_signed.zip" ]]; then echo "notarizing macOs binaries" - zip -r ./dist/kubectl-mongodb_amd64_arm64_bin.zip ./dist/kubectl-mongodb_darwin_amd64_v1/kubectl-mongodb ./dist/kubectl-mongodb_darwin_arm64/kubectl-mongodb # The Notarization Service takes an archive as input + zip -r ./artifacts/kubectl-mongodb_amd64_arm64_bin.zip "${darwin_amd64_dir}/kubectl-mongodb" "${darwin_arm64_dir}/kubectl-mongodb" # The Notarization Service takes an archive as input "${workdir:-.}"/linux_amd64/macnotary \ - -f ./dist/kubectl-mongodb_amd64_arm64_bin.zip \ + -f ./artifacts/kubectl-mongodb_amd64_arm64_bin.zip \ -m notarizeAndSign -u https://dev.macos-notary.build.10gen.cc/api \ -b com.mongodb.mongodb-kubectl-mongodb \ - -o ./dist/kubectl-mongodb_macos_signed.zip - - echo "printing output zip file content" - unzip -l ./dist/kubectl-mongodb_macos_signed.zip - echo "done" - - echo "running unzip -t to verify that the op zip file of macnotary is correct" - unzip -t ./dist/kubectl-mongodb_macos_signed.zip - echo "done" + -o ./artifacts/kubectl-mongodb_macos_signed.zip echo "replacing original files" - unzip -oj ./dist/kubectl-mongodb_macos_signed.zip dist/kubectl-mongodb_darwin_amd64_v1/kubectl-mongodb -d ./dist/kubectl-mongodb_darwin_amd64_v1/ - unzip -oj ./dist/kubectl-mongodb_macos_signed.zip dist/kubectl-mongodb_darwin_arm64/kubectl-mongodb -d ./dist/kubectl-mongodb_darwin_arm64/ + unzip -oj ./artifacts/kubectl-mongodb_macos_signed.zip "artifacts/kubectl-mongodb_${version}_darwin_amd64/kubectl-mongodb" -d "${darwin_amd64_dir}/" + unzip -oj ./artifacts/kubectl-mongodb_macos_signed.zip "artifacts/kubectl-mongodb_${version}_darwin_arm64/kubectl-mongodb" -d "${darwin_arm64_dir}/" fi diff --git a/scripts/release/kubectl-mongodb/python/promote_kubectl_plugin.py b/scripts/release/kubectl-mongodb/python/promote_kubectl_plugin.py index 3d0ec762a..6ee1a4619 100644 --- a/scripts/release/kubectl-mongodb/python/promote_kubectl_plugin.py +++ b/scripts/release/kubectl-mongodb/python/promote_kubectl_plugin.py @@ -1,18 +1,26 @@ import argparse +import hashlib import os import sys import tarfile - +import subprocess +from pathlib import Path import boto3 -import build_kubectl_plugin + from botocore.exceptions import ClientError, NoCredentialsError, PartialCredentialsError -from github import Github, GithubException +# from github import Github, GithubException GITHUB_REPO = "mongodb/mongodb-kubernetes" GITHUB_TOKEN = os.environ.get("GH_TOKEN") LOCAL_ARTIFACTS_DIR = "artifacts" +CHECKSUMS_PATH = f"{LOCAL_ARTIFACTS_DIR}/checksums.txt" + +DEV_S3_BUCKET_NAME = "mongodb-kubernetes-dev" +STAGING_S3_BUCKET_NAME = "mongodb-kubernetes-staging" +S3_BUCKET_KUBECTL_PLUGIN_SUBPATH = "kubectl-mongodb" +AWS_REGION = "eu-north-1" def main(): parser = argparse.ArgumentParser() @@ -26,93 +34,121 @@ def main(): args = parser.parse_args() download_artifacts_from_s3(args.release_version, args.staging_commit) - promote_to_release_bucket(args.staging_commit, args.release_version) + notarize_artifacts(args.release_version) + + sign_and_verify_artifacts() artifacts_tar = create_tarballs() - upload_assets_to_github_release(artifacts_tar, args.release_version) + artifacts = generate_checksums(artifacts_tar) + promote_artifacts(artifacts, args.release_version) -def artifacts_source_dir_s3(commit_sha: str): - return f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/" + # upload_assets_to_github_release(artifacts_tar, args.release_version) +def generate_checksums(artifacts: list[str]): + checksums_path = Path(CHECKSUMS_PATH) -def artifacts_dest_dir_s3(release_verion: str): - return f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{release_verion}/dist/" + with checksums_path.open("w") as out_file: + for artifact in artifacts: + artifact_path = Path(artifact) + if not artifact_path.is_file() or not artifact_path.name.endswith(".tar.gz"): + print(f"skipping invalid tar file: {artifact_path}") + continue + sha256 = hashlib.sha256() + with open(artifact_path, "rb") as f: + for chunk in iter(lambda : f.read(8192), b""): + sha256.update(chunk) -def promote_to_release_bucket(commit_sha: str, release_version: str): - try: - s3_client = boto3.client("s3", region_name=build_kubectl_plugin.AWS_REGION) - except (NoCredentialsError, PartialCredentialsError): - print("ERROR: AWS credentials not found. Please configure AWS credentials.") - sys.exit(1) - except Exception as e: - print(f"An error occurred connecting to S3: {e}") - sys.exit(1) + checksum_line = f"{sha256.hexdigest()} {artifact_path.name}" + out_file.write(checksum_line+"\n") - copy_count = 0 - try: - paginator = s3_client.get_paginator("list_objects_v2") - pages = paginator.paginate( - Bucket=build_kubectl_plugin.DEV_S3_BUCKET_NAME, Prefix=artifacts_source_dir_s3(commit_sha) - ) + print(f"Checksums written to {checksums_path}") + all_artifacts = list(artifacts) + [str(checksums_path.resolve())] + return all_artifacts - for page in pages: - if "Contents" not in page: - break +def promote_artifacts(artifacts: list[str], release_version: str): + s3_client = boto3.client("s3", region_name=AWS_REGION) + for file in artifacts: + if not os.path.isfile(file) or not file.endswith(('.tar.gz', '.txt')): + print(f"skipping invalid or non-tar file: {file}") + continue - for obj in page["Contents"]: - source_key = obj["Key"] + file_name = os.path.basename(file) + s3_key = os.path.join(S3_BUCKET_KUBECTL_PLUGIN_SUBPATH, release_version, file_name) - if source_key.endswith("/"): - continue + try: + s3_client.upload_file(file, STAGING_S3_BUCKET_NAME, s3_key) + except ClientError as e: + print(f"failed to upload the file {file}: {e}") + sys.exit(1) + + print("artifacts were promoted to release bucket successfully") - # Determine the new key for the destination - relative_path = os.path.relpath(source_key, artifacts_source_dir_s3(commit_sha)) - destination_dir = artifacts_dest_dir_s3(release_version) - destination_key = os.path.join(destination_dir, relative_path) +def notarize_artifacts(release_version: str): + notarize_result = subprocess.run(["scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh", release_version], capture_output=True, text=True) + if notarize_result.returncode == 0: + print("notarization of artifacts was successful") + else: + print(f"notarization of artifacts failed. \nstdout: {notarize_result.stdout} \nstderr: {notarize_result.stderr}") + sys.exit(1) - # Ensure forward slashes for S3 compatibility - destination_key = destination_key.replace(os.sep, "/") +# sign_and_verify_artifacts iterates over the goreleaser artifacts, that have been downloaded from S3, and +# signs and verifies them. +def sign_and_verify_artifacts(): + cwd = os.getcwd() + artifacts_dir = os.path.join(cwd, LOCAL_ARTIFACTS_DIR) - print(f"Copying {source_key} to {destination_key}...") + for subdir in os.listdir(artifacts_dir): + subdir_path = os.path.join(artifacts_dir, subdir) - # Prepare the source object for the copy operation - copy_source = {"Bucket": build_kubectl_plugin.DEV_S3_BUCKET_NAME, "Key": source_key} + # just work on dirs and not files + if os.path.isdir(subdir_path): + for file in os.listdir(subdir_path): + file_path = os.path.join(subdir_path, file) - # Perform the server-side copy - s3_client.copy_object( - CopySource=copy_source, Bucket=build_kubectl_plugin.STAGING_S3_BUCKET_NAME, Key=destination_key - ) - copy_count += 1 + if os.path.isfile(file_path): + sign_result = subprocess.run(["scripts/release/kubectl-mongodb/sign.sh", file_path], capture_output=True, text=True) + if sign_result.returncode == 0: + print(f"artifact {file_path} was signed successfully") + else: + print(f"signing the artifact {file_path} failed. \nstdout: {sign_result.stdout} \nstderr: {sign_result.stderr}") + sys.exit(1) + + verify_result = subprocess.run(["scripts/release/kubectl-mongodb/verify.sh", file_path], capture_output=True, text=True) + if verify_result.returncode == 0: + print(f"artifact {file_path} was verified successfully") + else: + print(f"verification of the artifact {file_path} failed. \nstdout: {verify_result.stdout} \nstderr: {verify_result.stderr}") + sys.exit(1) - except ClientError as e: - print(f"ERROR: A client error occurred during the copy operation. Error: {e}") - sys.exit(1) - except Exception as e: - print(f"An unexpected error occurred: {e}") - sys.exit(1) - if copy_count > 0: - print(f"Successfully copied {copy_count} object(s).") +def artifacts_source_dir_s3(commit_sha: str): + return f"{S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/" + + +def artifacts_dest_dir_s3(release_verion: str): + return f"{S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{release_verion}/dist/" def s3_artifacts_path_to_local_path(release_version: str, commit_sha: str): return { - f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_darwin_amd64_v1/": f"kubectl-mongodb_{release_version}_darwin_amd64", - f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_darwin_arm64/": f"kubectl-mongodb_{release_version}_darwin_arm64", - f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_linux_amd64_v1/": f"kubectl-mongodb_{release_version}_linux_amd64", - f"{build_kubectl_plugin.S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_linux_arm64/": f"kubectl-mongodb_{release_version}_linux_arm64", + f"{S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_darwin_amd64_v1/": f"kubectl-mongodb_{release_version}_darwin_amd64", + f"{S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_darwin_arm64/": f"kubectl-mongodb_{release_version}_darwin_arm64", + f"{S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_linux_amd64_v1/": f"kubectl-mongodb_{release_version}_linux_amd64", + f"{S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_linux_arm64/": f"kubectl-mongodb_{release_version}_linux_arm64", } +# download_artifacts_from_s3 downloads the staging artifacts from S3 and puts them in the local dir LOCAL_ARTIFACTS_DIR +# ToDo: if the artifacts are not present at correct location, this is going to fail silently, we should instead fail this def download_artifacts_from_s3(release_version: str, commit_sha: str): - print(f"\nStarting download of artifacts from S3 bucket: {build_kubectl_plugin.DEV_S3_BUCKET_NAME}") + print(f"\nStarting download of artifacts from S3 bucket: {DEV_S3_BUCKET_NAME}") try: - s3_client = boto3.client("s3", region_name=build_kubectl_plugin.AWS_REGION) + s3_client = boto3.client("s3", region_name=AWS_REGION) except (NoCredentialsError, PartialCredentialsError): print("ERROR: AWS credentials were not set.") sys.exit(1) @@ -129,8 +165,7 @@ def download_artifacts_from_s3(release_version: str, commit_sha: str): for s3_artifact_dir, local_subdir in artifacts_to_promote.items(): try: paginator = s3_client.get_paginator("list_objects_v2") - pages = paginator.paginate(Bucket=build_kubectl_plugin.DEV_S3_BUCKET_NAME, Prefix=s3_artifact_dir) - + pages = paginator.paginate(Bucket=DEV_S3_BUCKET_NAME, Prefix=s3_artifact_dir) for page in pages: # "Contents" corresponds to the directory in the S3 bucket if "Contents" not in page: @@ -152,7 +187,7 @@ def download_artifacts_from_s3(release_version: str, commit_sha: str): os.makedirs(os.path.dirname(final_local_path), exist_ok=True) print(f"Downloading {s3_key} to {final_local_path}") - s3_client.download_file(build_kubectl_plugin.DEV_S3_BUCKET_NAME, s3_key, final_local_path) + s3_client.download_file(DEV_S3_BUCKET_NAME, s3_key, final_local_path) download_count += 1 except ClientError as e: @@ -184,39 +219,41 @@ def create_tarballs(): except Exception as e: print(f"ERROR: Failed to create tar.gz archives: {e}") return [] + finally: + os.chdir(original_cwd) return created_archives -def upload_assets_to_github_release(asset_paths, release_version: str): - if not GITHUB_TOKEN: - print("ERROR: GITHUB_TOKEN environment variable not set.") - sys.exit(1) - - try: - g = Github(GITHUB_TOKEN) - repo = g.get_repo(GITHUB_REPO) - except GithubException as e: - print(f"ERROR: Could not connect to GitHub or find repository '{GITHUB_REPO}', Error {e}.") - sys.exit(1) - - try: - release = repo.get_release(release_version) - except GithubException as e: - print( - f"ERROR: Could not find release with tag '{release_version}'. Please ensure release exists already. Error: {e}" - ) - return - - for asset_path in asset_paths: - asset_name = os.path.basename(asset_path) - print(f"Uploading artifact '{asset_name}' to github release as asset") - try: - release.upload_asset(path=asset_path, name=asset_name, content_type="application/gzip") - except GithubException as e: - print(f"ERROR: Failed to upload asset {asset_name}. Error: {e}") - except Exception as e: - print(f"An unexpected error occurred during upload of {asset_name}: {e}") +# def upload_assets_to_github_release(asset_paths, release_version: str): +# if not GITHUB_TOKEN: +# print("ERROR: GITHUB_TOKEN environment variable not set.") +# sys.exit(1) +# +# try: +# g = Github(GITHUB_TOKEN) +# repo = g.get_repo(GITHUB_REPO) +# except GithubException as e: +# print(f"ERROR: Could not connect to GitHub or find repository '{GITHUB_REPO}', Error {e}.") +# sys.exit(1) +# +# try: +# release = repo.get_release(release_version) +# except GithubException as e: +# print( +# f"ERROR: Could not find release with tag '{release_version}'. Please ensure release exists already. Error: {e}" +# ) +# return +# +# for asset_path in asset_paths: +# asset_name = os.path.basename(asset_path) +# print(f"Uploading artifact '{asset_name}' to github release as asset") +# try: +# release.upload_asset(path=asset_path, name=asset_name, content_type="application/gzip") +# except GithubException as e: +# print(f"ERROR: Failed to upload asset {asset_name}. Error: {e}") +# except Exception as e: +# print(f"An unexpected error occurred during upload of {asset_name}: {e}") if __name__ == "__main__": From c441a0975e41555f142c7003862d2e1eb5394168 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Thu, 21 Aug 2025 20:46:58 +0200 Subject: [PATCH 11/16] Remove `-` from pkg and add comments --- .goreleaser.yaml | 6 +- .../install_istio_separate_network.sh | 0 .../kubectl_mac_notarize.sh | 0 .../python/build_kubectl_plugin.py | 0 .../python/promote_kubectl_plugin.py | 174 +++++++++++------- .../setup_tls.sh | 0 .../sign.sh | 0 .../verify.sh | 0 8 files changed, 106 insertions(+), 74 deletions(-) rename scripts/release/{kubectl-mongodb => kubectl_mongodb}/install_istio_separate_network.sh (100%) rename scripts/release/{kubectl-mongodb => kubectl_mongodb}/kubectl_mac_notarize.sh (100%) rename scripts/release/{kubectl-mongodb => kubectl_mongodb}/python/build_kubectl_plugin.py (100%) rename scripts/release/{kubectl-mongodb => kubectl_mongodb}/python/promote_kubectl_plugin.py (54%) rename scripts/release/{kubectl-mongodb => kubectl_mongodb}/setup_tls.sh (100%) rename scripts/release/{kubectl-mongodb => kubectl_mongodb}/sign.sh (100%) rename scripts/release/{kubectl-mongodb => kubectl_mongodb}/verify.sh (100%) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 884ae3fc4..221b1f5bd 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -19,9 +19,9 @@ builds: hooks: # This will notarize Apple binaries and replace goreleaser bins with the notarized ones post: - - cmd: ./scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh + - cmd: ./scripts/release/kubectl_mongodb/kubectl_mac_notarize.sh output: true - - cmd: ./scripts/release/kubectl-mongodb/sign.sh {{ .Path }} + - cmd: ./scripts/release/kubectl_mongodb/sign.sh {{ .Path }} env: - GRS_USERNAME={{ .Env.GRS_USERNAME }} - GRS_PASSWORD={{ .Env.GRS_PASSWORD }} @@ -30,7 +30,7 @@ builds: - SIGNING_IMAGE_URI={{ .Env.SIGNING_IMAGE_URI }} - ARTIFACTORY_USERNAME=mongodb-enterprise-kubernetes-operator - ARTIFACTORY_PASSWORD={{ .Env.ARTIFACTORY_PASSWORD }} - - cmd: ./scripts/release/kubectl-mongodb/verify.sh {{ .Path }} && echo "VERIFIED OK" + - cmd: ./scripts/release/kubectl_mongodb/verify.sh {{ .Path }} && echo "VERIFIED OK" archives: - format: tar.gz diff --git a/scripts/release/kubectl-mongodb/install_istio_separate_network.sh b/scripts/release/kubectl_mongodb/install_istio_separate_network.sh similarity index 100% rename from scripts/release/kubectl-mongodb/install_istio_separate_network.sh rename to scripts/release/kubectl_mongodb/install_istio_separate_network.sh diff --git a/scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh b/scripts/release/kubectl_mongodb/kubectl_mac_notarize.sh similarity index 100% rename from scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh rename to scripts/release/kubectl_mongodb/kubectl_mac_notarize.sh diff --git a/scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py b/scripts/release/kubectl_mongodb/python/build_kubectl_plugin.py similarity index 100% rename from scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py rename to scripts/release/kubectl_mongodb/python/build_kubectl_plugin.py diff --git a/scripts/release/kubectl-mongodb/python/promote_kubectl_plugin.py b/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py similarity index 54% rename from scripts/release/kubectl-mongodb/python/promote_kubectl_plugin.py rename to scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py index 6ee1a4619..f061032db 100644 --- a/scripts/release/kubectl-mongodb/python/promote_kubectl_plugin.py +++ b/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py @@ -1,26 +1,31 @@ import argparse import hashlib import os +import subprocess import sys import tarfile -import subprocess from pathlib import Path -import boto3 +import boto3 from botocore.exceptions import ClientError, NoCredentialsError, PartialCredentialsError -# from github import Github, GithubException +from github import Github, GithubException + +from lib.base_logger import logger -GITHUB_REPO = "mongodb/mongodb-kubernetes" GITHUB_TOKEN = os.environ.get("GH_TOKEN") +GITHUB_REPO = "mongodb/mongodb-kubernetes" + +AWS_REGION = "eu-north-1" + +STAGING_S3_BUCKET_NAME = "mongodb-kubernetes-dev" +RELEASE_S3_BUCKET_NAME = "mongodb-kubernetes-staging" + +KUBECTL_PLUGIN_BINARY_NAME = "kubectl-mongodb" +S3_BUCKET_KUBECTL_PLUGIN_SUBPATH = KUBECTL_PLUGIN_BINARY_NAME LOCAL_ARTIFACTS_DIR = "artifacts" CHECKSUMS_PATH = f"{LOCAL_ARTIFACTS_DIR}/checksums.txt" -DEV_S3_BUCKET_NAME = "mongodb-kubernetes-dev" -STAGING_S3_BUCKET_NAME = "mongodb-kubernetes-staging" - -S3_BUCKET_KUBECTL_PLUGIN_SUBPATH = "kubectl-mongodb" -AWS_REGION = "eu-north-1" def main(): parser = argparse.ArgumentParser() @@ -44,8 +49,11 @@ def main(): promote_artifacts(artifacts, args.release_version) - # upload_assets_to_github_release(artifacts_tar, args.release_version) + upload_assets_to_github_release(artifacts, args.release_version) + +# generate_checksums generates checksums for the artifacts that we are going to upload to github release as assets. +# It's formatted: checksum artifact_name def generate_checksums(artifacts: list[str]): checksums_path = Path(CHECKSUMS_PATH) @@ -53,48 +61,57 @@ def generate_checksums(artifacts: list[str]): for artifact in artifacts: artifact_path = Path(artifact) if not artifact_path.is_file() or not artifact_path.name.endswith(".tar.gz"): - print(f"skipping invalid tar file: {artifact_path}") + logger.info(f"skipping invalid tar file: {artifact_path}") continue sha256 = hashlib.sha256() with open(artifact_path, "rb") as f: - for chunk in iter(lambda : f.read(8192), b""): + # read chunk of 8192 bites until end of file (b"") is received + for chunk in iter(lambda: f.read(8192), b""): sha256.update(chunk) checksum_line = f"{sha256.hexdigest()} {artifact_path.name}" - out_file.write(checksum_line+"\n") + out_file.write(checksum_line + "\n") - print(f"Checksums written to {checksums_path}") + logger.info(f"Checksums written to {checksums_path}") all_artifacts = list(artifacts) + [str(checksums_path.resolve())] - return all_artifacts + return all_artifacts + +# promote_artifacts promotes (copies) the downloaded staging artifacts to release S3 bucket. def promote_artifacts(artifacts: list[str], release_version: str): s3_client = boto3.client("s3", region_name=AWS_REGION) for file in artifacts: - if not os.path.isfile(file) or not file.endswith(('.tar.gz', '.txt')): - print(f"skipping invalid or non-tar file: {file}") + if not os.path.isfile(file) or not file.endswith((".tar.gz", ".txt")): + logger.info(f"Skipping invalid or non-tar/checksum file: {file}") continue file_name = os.path.basename(file) s3_key = os.path.join(S3_BUCKET_KUBECTL_PLUGIN_SUBPATH, release_version, file_name) try: - s3_client.upload_file(file, STAGING_S3_BUCKET_NAME, s3_key) + s3_client.upload_file(file, RELEASE_S3_BUCKET_NAME, s3_key) except ClientError as e: - print(f"failed to upload the file {file}: {e}") + logger.debug(f"failed to upload the file {file}: {e}") sys.exit(1) - print("artifacts were promoted to release bucket successfully") + logger.info("Artifacts were promoted to release bucket successfully") +# notarize_artifacts notarizes the darwin goreleaser binaries in-place. def notarize_artifacts(release_version: str): - notarize_result = subprocess.run(["scripts/release/kubectl-mongodb/kubectl_mac_notarize.sh", release_version], capture_output=True, text=True) + notarize_result = subprocess.run( + ["scripts/release/kubectl_mongodb/kubectl_mac_notarize.sh", release_version], capture_output=True, text=True + ) if notarize_result.returncode == 0: - print("notarization of artifacts was successful") + logger.info("Notarization of artifacts was successful") else: - print(f"notarization of artifacts failed. \nstdout: {notarize_result.stdout} \nstderr: {notarize_result.stderr}") + logger.debug( + f"Notarization of artifacts failed. \nstdout: {notarize_result.stdout} \nstderr: {notarize_result.stderr}" + ) sys.exit(1) + # sign_and_verify_artifacts iterates over the goreleaser artifacts, that have been downloaded from S3, and # signs and verifies them. def sign_and_verify_artifacts(): @@ -110,18 +127,27 @@ def sign_and_verify_artifacts(): file_path = os.path.join(subdir_path, file) if os.path.isfile(file_path): - sign_result = subprocess.run(["scripts/release/kubectl-mongodb/sign.sh", file_path], capture_output=True, text=True) + # signing an already signed artifact fails with `Signature already exixts. Displaying proof`. + sign_result = subprocess.run( + ["scripts/release/kubectl_mongodb/sign.sh", file_path], capture_output=True, text=True + ) if sign_result.returncode == 0: - print(f"artifact {file_path} was signed successfully") + logger.info(f"Artifact {file_path} was signed successfully") else: - print(f"signing the artifact {file_path} failed. \nstdout: {sign_result.stdout} \nstderr: {sign_result.stderr}") + logger.debug( + f"Signing the artifact {file_path} failed. \nstdout: {sign_result.stdout} \nstderr: {sign_result.stderr}" + ) sys.exit(1) - verify_result = subprocess.run(["scripts/release/kubectl-mongodb/verify.sh", file_path], capture_output=True, text=True) + verify_result = subprocess.run( + ["scripts/release/kubectl_mongodb/verify.sh", file_path], capture_output=True, text=True + ) if verify_result.returncode == 0: - print(f"artifact {file_path} was verified successfully") + logger.info(f"Artifact {file_path} was verified successfully") else: - print(f"verification of the artifact {file_path} failed. \nstdout: {verify_result.stdout} \nstderr: {verify_result.stderr}") + logger.debug( + f"Verification of the artifact {file_path} failed. \nstdout: {verify_result.stdout} \nstderr: {verify_result.stderr}" + ) sys.exit(1) @@ -142,18 +168,19 @@ def s3_artifacts_path_to_local_path(release_version: str, commit_sha: str): } -# download_artifacts_from_s3 downloads the staging artifacts from S3 and puts them in the local dir LOCAL_ARTIFACTS_DIR +# download_artifacts_from_s3 downloads the staging artifacts (only that ones that we would later promote) from S3 and puts +# them in the local dir LOCAL_ARTIFACTS_DIR. # ToDo: if the artifacts are not present at correct location, this is going to fail silently, we should instead fail this def download_artifacts_from_s3(release_version: str, commit_sha: str): - print(f"\nStarting download of artifacts from S3 bucket: {DEV_S3_BUCKET_NAME}") + logger.info(f"Starting download of artifacts from staging S3 bucket: {STAGING_S3_BUCKET_NAME}") try: s3_client = boto3.client("s3", region_name=AWS_REGION) except (NoCredentialsError, PartialCredentialsError): - print("ERROR: AWS credentials were not set.") + logger.debug("ERROR: AWS credentials were not set.") sys.exit(1) except Exception as e: - print(f"An error occurred connecting to S3: {e}") + logger.debug(f"An error occurred connecting to S3: {e}") sys.exit(1) artifacts_to_promote = s3_artifacts_path_to_local_path(release_version, commit_sha) @@ -165,7 +192,7 @@ def download_artifacts_from_s3(release_version: str, commit_sha: str): for s3_artifact_dir, local_subdir in artifacts_to_promote.items(): try: paginator = s3_client.get_paginator("list_objects_v2") - pages = paginator.paginate(Bucket=DEV_S3_BUCKET_NAME, Prefix=s3_artifact_dir) + pages = paginator.paginate(Bucket=STAGING_S3_BUCKET_NAME, Prefix=s3_artifact_dir) for page in pages: # "Contents" corresponds to the directory in the S3 bucket if "Contents" not in page: @@ -186,20 +213,21 @@ def download_artifacts_from_s3(release_version: str, commit_sha: str): # Create the local directory structure if it doesn't exist os.makedirs(os.path.dirname(final_local_path), exist_ok=True) - print(f"Downloading {s3_key} to {final_local_path}") - s3_client.download_file(DEV_S3_BUCKET_NAME, s3_key, final_local_path) + logger.info(f"Downloading staging artifact {s3_key} to {final_local_path}") + s3_client.download_file(STAGING_S3_BUCKET_NAME, s3_key, final_local_path) download_count += 1 except ClientError as e: - print(f"ERROR: Failed to list or download from prefix '{s3_artifact_dir}'. S3 Client Error: {e}") + logger.debug(f"ERROR: Failed to list or download from prefix '{s3_artifact_dir}'. S3 Client Error: {e}") return False - print("All the artifacts have been downloaded successfully.") + logger.info("All the artifacts have been downloaded successfully.") return True +# create_tarballs creates `.tar.gz` archives for the artifacts that before promoting them. def create_tarballs(): - print(f"\nCreating archives for subdirectories in {LOCAL_ARTIFACTS_DIR}") + logger.info(f"Creating archives for subdirectories in {LOCAL_ARTIFACTS_DIR}") created_archives = [] original_cwd = os.getcwd() try: @@ -213,47 +241,51 @@ def create_tarballs(): tar.add(dir_name) full_archive_path = os.path.join(original_cwd, LOCAL_ARTIFACTS_DIR, archive_name) - print(f"Successfully created archive at {full_archive_path}") + logger.info(f"Successfully created archive at {full_archive_path}") created_archives.append(full_archive_path) except Exception as e: - print(f"ERROR: Failed to create tar.gz archives: {e}") - return [] + logger.debug(f"ERROR: Failed to create tar.gz archives: {e}") + sys.exit(1) finally: os.chdir(original_cwd) return created_archives -# def upload_assets_to_github_release(asset_paths, release_version: str): -# if not GITHUB_TOKEN: -# print("ERROR: GITHUB_TOKEN environment variable not set.") -# sys.exit(1) -# -# try: -# g = Github(GITHUB_TOKEN) -# repo = g.get_repo(GITHUB_REPO) -# except GithubException as e: -# print(f"ERROR: Could not connect to GitHub or find repository '{GITHUB_REPO}', Error {e}.") -# sys.exit(1) -# -# try: -# release = repo.get_release(release_version) -# except GithubException as e: -# print( -# f"ERROR: Could not find release with tag '{release_version}'. Please ensure release exists already. Error: {e}" -# ) -# return -# -# for asset_path in asset_paths: -# asset_name = os.path.basename(asset_path) -# print(f"Uploading artifact '{asset_name}' to github release as asset") -# try: -# release.upload_asset(path=asset_path, name=asset_name, content_type="application/gzip") -# except GithubException as e: -# print(f"ERROR: Failed to upload asset {asset_name}. Error: {e}") -# except Exception as e: -# print(f"An unexpected error occurred during upload of {asset_name}: {e}") +# upload_assets_to_github_release uploads the release artifacts (downloaded notarized/signed staging artifacts) to +# the github release as assets. +def upload_assets_to_github_release(asset_paths, release_version: str): + if not GITHUB_TOKEN: + logger.info("ERROR: GITHUB_TOKEN environment variable not set.") + sys.exit(1) + + try: + g = Github(GITHUB_TOKEN) + repo = g.get_repo(GITHUB_REPO) + except GithubException as e: + logger.info(f"ERROR: Could not connect to GitHub or find repository '{GITHUB_REPO}', Error {e}.") + sys.exit(1) + + try: + release = repo.get_release(release_version) + except GithubException as e: + logger.debug( + f"ERROR: Could not find release with tag '{release_version}'. Please ensure release exists already. Error: {e}" + ) + sys.exit(2) + + for asset_path in asset_paths: + asset_name = os.path.basename(asset_path) + logger.info(f"Uploading artifact '{asset_name}' to github release as asset") + try: + release.upload_asset(path=asset_path, name=asset_name, content_type="application/gzip") + except GithubException as e: + logger.debug(f"ERROR: Failed to upload asset {asset_name}. Error: {e}") + sys.exit(2) + except Exception as e: + logger.debug(f"An unexpected error occurred during upload of {asset_name}: {e}") + sys.exit(2) if __name__ == "__main__": diff --git a/scripts/release/kubectl-mongodb/setup_tls.sh b/scripts/release/kubectl_mongodb/setup_tls.sh similarity index 100% rename from scripts/release/kubectl-mongodb/setup_tls.sh rename to scripts/release/kubectl_mongodb/setup_tls.sh diff --git a/scripts/release/kubectl-mongodb/sign.sh b/scripts/release/kubectl_mongodb/sign.sh similarity index 100% rename from scripts/release/kubectl-mongodb/sign.sh rename to scripts/release/kubectl_mongodb/sign.sh diff --git a/scripts/release/kubectl-mongodb/verify.sh b/scripts/release/kubectl_mongodb/verify.sh similarity index 100% rename from scripts/release/kubectl-mongodb/verify.sh rename to scripts/release/kubectl_mongodb/verify.sh From 0d64a4f28fa1790b321f18db9631bfc4c224f72c Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Fri, 22 Aug 2025 15:03:13 +0200 Subject: [PATCH 12/16] Fail if the artifacts are not found in staging bucket --- .../release/kubectl_mongodb/python/promote_kubectl_plugin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py b/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py index f061032db..32233f8f9 100644 --- a/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py +++ b/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py @@ -221,6 +221,10 @@ def download_artifacts_from_s3(release_version: str, commit_sha: str): logger.debug(f"ERROR: Failed to list or download from prefix '{s3_artifact_dir}'. S3 Client Error: {e}") return False + if download_count == 0: + logger.info(f"Couldn't download artifacts from staging S3 bucket {STAGING_S3_BUCKET_NAME}, please verify that artifacts are available under dir: {commit_sha}") + sys.exit(1) + logger.info("All the artifacts have been downloaded successfully.") return True From 3fc912b924ed0a2072a0caf3a59d20d163c8d7f4 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Wed, 3 Sep 2025 18:34:14 +0200 Subject: [PATCH 13/16] Moves constants to consts file --- .../python/build_kubectl_plugin.py | 7 +-- .../release/kubectl_mongodb/python/consts.py | 12 +++++ .../python/promote_kubectl_plugin.py | 53 +++++++++++-------- 3 files changed, 43 insertions(+), 29 deletions(-) create mode 100644 scripts/release/kubectl_mongodb/python/consts.py diff --git a/scripts/release/kubectl_mongodb/python/build_kubectl_plugin.py b/scripts/release/kubectl_mongodb/python/build_kubectl_plugin.py index b900ad96d..8c9d1358e 100755 --- a/scripts/release/kubectl_mongodb/python/build_kubectl_plugin.py +++ b/scripts/release/kubectl_mongodb/python/build_kubectl_plugin.py @@ -6,17 +6,13 @@ from botocore.exceptions import ClientError, NoCredentialsError, PartialCredentialsError from lib.base_logger import logger +from scripts.release.kubectl_mongodb.python.consts import * from scripts.release.build.build_info import ( load_build_info, ) -AWS_REGION = "eu-north-1" -KUBECTL_PLUGIN_BINARY_NAME = "kubectl-mongodb" S3_BUCKET_KUBECTL_PLUGIN_SUBPATH = KUBECTL_PLUGIN_BINARY_NAME -GORELEASER_DIST_DIR = "dist" - - def run_goreleaser(): try: command = ["goreleaser", "build", "--snapshot", "--clean", "--skip", "post-hooks"] @@ -149,4 +145,3 @@ def main(): if __name__ == "__main__": main() - \ No newline at end of file diff --git a/scripts/release/kubectl_mongodb/python/consts.py b/scripts/release/kubectl_mongodb/python/consts.py new file mode 100644 index 000000000..2b0e1446b --- /dev/null +++ b/scripts/release/kubectl_mongodb/python/consts.py @@ -0,0 +1,12 @@ +AWS_REGION = "eu-north-1" +KUBECTL_PLUGIN_BINARY_NAME = "kubectl-mongodb" + +STAGING_S3_BUCKET_NAME = "mongodb-kubernetes-dev" +RELEASE_S3_BUCKET_NAME = "mongodb-kubernetes-staging" + +GITHUB_REPO = "mongodb/mongodb-kubernetes" + +LOCAL_ARTIFACTS_DIR = "artifacts" +CHECKSUMS_PATH = f"{LOCAL_ARTIFACTS_DIR}/checksums.txt" + +GORELEASER_DIST_DIR = "dist" diff --git a/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py b/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py index 32233f8f9..7caf0d257 100644 --- a/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py +++ b/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py @@ -11,22 +11,12 @@ from github import Github, GithubException from lib.base_logger import logger +from scripts.release.kubectl_mongodb.python.consts import * GITHUB_TOKEN = os.environ.get("GH_TOKEN") -GITHUB_REPO = "mongodb/mongodb-kubernetes" -AWS_REGION = "eu-north-1" - -STAGING_S3_BUCKET_NAME = "mongodb-kubernetes-dev" -RELEASE_S3_BUCKET_NAME = "mongodb-kubernetes-staging" - -KUBECTL_PLUGIN_BINARY_NAME = "kubectl-mongodb" S3_BUCKET_KUBECTL_PLUGIN_SUBPATH = KUBECTL_PLUGIN_BINARY_NAME -LOCAL_ARTIFACTS_DIR = "artifacts" -CHECKSUMS_PATH = f"{LOCAL_ARTIFACTS_DIR}/checksums.txt" - - def main(): parser = argparse.ArgumentParser() parser.add_argument( @@ -51,6 +41,28 @@ def main(): upload_assets_to_github_release(artifacts, args.release_version) +# get_commit_from_tag gets the commit associated with a release tag, so that we can use that +# commit to pull the artifacts from staging bucket. +def get_commit_from_tag(tag: str) -> str: + try: + subprocess.run( + ["git", "fetch", "--tags"], + capture_output=True, + text=True, + check=True + ) + + result = subprocess.run( + ["git", "rev-parse", f"{tag}^{{commit}}"], # git rev-parse v1.1.1^{commit} + capture_output=True, + text=True, + check=True + ) + return result.stdout.strip() + + except subprocess.CalledProcessError as e: + logger.info(f"Failed to get commit for tag: {tag}, err: {e.stderr.strip()}") + sys.exit(1) # generate_checksums generates checksums for the artifacts that we are going to upload to github release as assets. # It's formatted: checksum artifact_name @@ -151,20 +163,15 @@ def sign_and_verify_artifacts(): sys.exit(1) -def artifacts_source_dir_s3(commit_sha: str): - return f"{S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/" - - -def artifacts_dest_dir_s3(release_verion: str): - return f"{S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{release_verion}/dist/" - - def s3_artifacts_path_to_local_path(release_version: str, commit_sha: str): + s3_common_path = f"{S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist" return { - f"{S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_darwin_amd64_v1/": f"kubectl-mongodb_{release_version}_darwin_amd64", - f"{S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_darwin_arm64/": f"kubectl-mongodb_{release_version}_darwin_arm64", - f"{S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_linux_amd64_v1/": f"kubectl-mongodb_{release_version}_linux_amd64", - f"{S3_BUCKET_KUBECTL_PLUGIN_SUBPATH}/{commit_sha}/dist/kubectl-mongodb_linux_arm64/": f"kubectl-mongodb_{release_version}_linux_arm64", + f"{s3_common_path}/kubectl-mongodb_darwin_amd64_v1/": f"kubectl-mongodb_{release_version}_darwin_amd64", + f"{s3_common_path}/kubectl-mongodb_darwin_arm64/": f"kubectl-mongodb_{release_version}_darwin_arm64", + f"{s3_common_path}/kubectl-mongodb_linux_amd64_v1/": f"kubectl-mongodb_{release_version}_linux_amd64", + f"{s3_common_path}/kubectl-mongodb_linux_arm64/": f"kubectl-mongodb_{release_version}_linux_arm64", + f"{s3_common_path}/kubectl-mongodb_linux_ppc64le/": f"kubectl-mongodb_{release_version}_linux_ppc64le", + f"{s3_common_path}/kubectl-mongodb_linux_s390x/": f"kubectl-mongodb_{release_version}_linux_s390x", } From eff67fef22a47677cc3b8efd105267fb49faa65c Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Tue, 21 Oct 2025 15:43:07 +0200 Subject: [PATCH 14/16] Refactor, rebase and test --- .evergreen-functions.yml | 26 ++-------------- .evergreen-release.yml | 1 + .evergreen.yml | 7 ----- .../release/kubectl_mongodb/python/consts.py | 3 +- .../python/promote_kubectl_plugin.py | 31 ++++++++++--------- 5 files changed, 20 insertions(+), 48 deletions(-) diff --git a/.evergreen-functions.yml b/.evergreen-functions.yml index 57dfef605..22c5b8e6f 100644 --- a/.evergreen-functions.yml +++ b/.evergreen-functions.yml @@ -504,22 +504,6 @@ functions: # publish_helm_chart packages and publishes the MCK helm chart to the OCI container registry publish_helm_chart: - command: subprocess.exec - params: - include_expansions_in_env: - - github_commit - - GRS_USERNAME - - GRS_PASSWORD - - PKCS11_URI - - ARTIFACTORY_URL - - ARTIFACTORY_PASSWORD - - SIGNING_IMAGE_URI - - macos_notary_keyid - - macos_notary_secret - - workdir - - triggered_by_git_tag - env: - MACOS_NOTARY_KEY: ${macos_notary_keyid} - MACOS_NOTARY_SECRET: ${macos_notary_secret} working_dir: src/github.com/mongodb/mongodb-kubernetes binary: scripts/release/publish_helm_chart.sh @@ -527,7 +511,7 @@ functions: - command: subprocess.exec params: working_dir: src/github.com/mongodb/mongodb-kubernetes - binary: scripts/dev/run_python.sh scripts/release/kubectl-mongodb/python/build_kubectl_plugin.py + binary: scripts/dev/run_python.sh scripts/release/kubectl_mongodb/python/build_kubectl_plugin.py build_and_push_appdb_database: - command: subprocess.exec @@ -926,10 +910,4 @@ functions: GOROOT: "/opt/golang/go1.24" MACOS_NOTARY_KEY: ${macos_notary_keyid} MACOS_NOTARY_SECRET: ${macos_notary_secret} - # shell.exec EVG Task doesn't have add_to_path, so we need to explicitly add the path export below. - script: | - set -Eeu pipefail - export GORELEASER_CURRENT_TAG=${OPERATOR_VERSION|*triggered_by_git_tag} - export PATH=$GOROOT/bin:$PATH - export GITHUB_TOKEN=${generated_token} - ${workdir}/goreleaser release --clean + script: scripts/dev/run_python.sh scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py diff --git a/.evergreen-release.yml b/.evergreen-release.yml index 592adf44e..6453a4cb1 100644 --- a/.evergreen-release.yml +++ b/.evergreen-release.yml @@ -121,6 +121,7 @@ tasks: - func: clone - func: install_goreleaser - func: install_macos_notarization_service + - func: python_venv - func: release_kubectl_mongodb_plugin - name: create_chart_release_pr diff --git a/.evergreen.yml b/.evergreen.yml index df456f2f5..f99cc4c41 100644 --- a/.evergreen.yml +++ b/.evergreen.yml @@ -1700,13 +1700,6 @@ buildvariants: tasks: - name: generate_perf_tasks_30_thread - - name: kubectl_plugin_release - display_name: kubectl_plugin_release - run_on: - - ubuntu2204-small - tasks: - - name: release_kubectl_plugin - ### Prerequisites for E2E test suite - name: init_test_run diff --git a/scripts/release/kubectl_mongodb/python/consts.py b/scripts/release/kubectl_mongodb/python/consts.py index 2b0e1446b..173e0c9ff 100644 --- a/scripts/release/kubectl_mongodb/python/consts.py +++ b/scripts/release/kubectl_mongodb/python/consts.py @@ -1,8 +1,7 @@ AWS_REGION = "eu-north-1" KUBECTL_PLUGIN_BINARY_NAME = "kubectl-mongodb" -STAGING_S3_BUCKET_NAME = "mongodb-kubernetes-dev" -RELEASE_S3_BUCKET_NAME = "mongodb-kubernetes-staging" +STAGING_S3_BUCKET_NAME = "mongodb-kubernetes-staging" GITHUB_REPO = "mongodb/mongodb-kubernetes" diff --git a/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py b/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py index 7caf0d257..c970ac7af 100644 --- a/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py +++ b/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py @@ -12,24 +12,25 @@ from lib.base_logger import logger from scripts.release.kubectl_mongodb.python.consts import * +from scripts.release.build.build_info import ( + load_build_info, +) GITHUB_TOKEN = os.environ.get("GH_TOKEN") S3_BUCKET_KUBECTL_PLUGIN_SUBPATH = KUBECTL_PLUGIN_BINARY_NAME def main(): - parser = argparse.ArgumentParser() - parser.add_argument( - "--release_version", - required=True, - help="product release version, ideally should match with github tag/release.", - ) - parser.add_argument("--staging_commit", required=True, help="staging commit that we want to promote to release.") + release_version = os.environ.get("OPERATOR_VERSION") + + # figure out release and staging buckets using build_scenario + build_scenario = os.environ.get("BUILD_SCENARIO") + kubectl_plugin_build_info = load_build_info(build_scenario).binaries[KUBECTL_PLUGIN_BINARY_NAME] + release_scenario_bucket_name = kubectl_plugin_build_info.s3_store - args = parser.parse_args() - download_artifacts_from_s3(args.release_version, args.staging_commit) + download_artifacts_from_s3(release_version, get_commit_from_tag(release_version)) - notarize_artifacts(args.release_version) + notarize_artifacts(release_version) sign_and_verify_artifacts() @@ -37,9 +38,9 @@ def main(): artifacts = generate_checksums(artifacts_tar) - promote_artifacts(artifacts, args.release_version) + promote_artifacts(artifacts, release_version, release_scenario_bucket_name) - upload_assets_to_github_release(artifacts, args.release_version) + upload_assets_to_github_release(artifacts, release_version) # get_commit_from_tag gets the commit associated with a release tag, so that we can use that # commit to pull the artifacts from staging bucket. @@ -53,7 +54,7 @@ def get_commit_from_tag(tag: str) -> str: ) result = subprocess.run( - ["git", "rev-parse", f"{tag}^{{commit}}"], # git rev-parse v1.1.1^{commit} + ["git", "rev-parse", "--short", f"{tag}^{{commit}}"], # git rev-parse v1.1.1^{commit} capture_output=True, text=True, check=True @@ -91,7 +92,7 @@ def generate_checksums(artifacts: list[str]): # promote_artifacts promotes (copies) the downloaded staging artifacts to release S3 bucket. -def promote_artifacts(artifacts: list[str], release_version: str): +def promote_artifacts(artifacts: list[str], release_version: str, release_scenario_bucket_name: str): s3_client = boto3.client("s3", region_name=AWS_REGION) for file in artifacts: if not os.path.isfile(file) or not file.endswith((".tar.gz", ".txt")): @@ -102,7 +103,7 @@ def promote_artifacts(artifacts: list[str], release_version: str): s3_key = os.path.join(S3_BUCKET_KUBECTL_PLUGIN_SUBPATH, release_version, file_name) try: - s3_client.upload_file(file, RELEASE_S3_BUCKET_NAME, s3_key) + s3_client.upload_file(file, release_scenario_bucket_name, s3_key) except ClientError as e: logger.debug(f"failed to upload the file {file}: {e}") sys.exit(1) From e78f07af311e542614055a6d1dbe6748b8276da7 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Tue, 21 Oct 2025 15:49:48 +0200 Subject: [PATCH 15/16] Add comment, run precommit --- .../python/build_kubectl_plugin.py | 4 +++- .../python/promote_kubectl_plugin.py | 22 ++++++++++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/scripts/release/kubectl_mongodb/python/build_kubectl_plugin.py b/scripts/release/kubectl_mongodb/python/build_kubectl_plugin.py index 8c9d1358e..bf63ea875 100755 --- a/scripts/release/kubectl_mongodb/python/build_kubectl_plugin.py +++ b/scripts/release/kubectl_mongodb/python/build_kubectl_plugin.py @@ -6,13 +6,14 @@ from botocore.exceptions import ClientError, NoCredentialsError, PartialCredentialsError from lib.base_logger import logger -from scripts.release.kubectl_mongodb.python.consts import * from scripts.release.build.build_info import ( load_build_info, ) +from scripts.release.kubectl_mongodb.python.consts import * S3_BUCKET_KUBECTL_PLUGIN_SUBPATH = KUBECTL_PLUGIN_BINARY_NAME + def run_goreleaser(): try: command = ["goreleaser", "build", "--snapshot", "--clean", "--skip", "post-hooks"] @@ -143,5 +144,6 @@ def main(): download_plugin_for_tests_image(kubectl_plugin_build_info.s3_store, version) + if __name__ == "__main__": main() diff --git a/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py b/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py index c970ac7af..664026d7c 100644 --- a/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py +++ b/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py @@ -11,15 +11,16 @@ from github import Github, GithubException from lib.base_logger import logger -from scripts.release.kubectl_mongodb.python.consts import * from scripts.release.build.build_info import ( load_build_info, ) +from scripts.release.kubectl_mongodb.python.consts import * GITHUB_TOKEN = os.environ.get("GH_TOKEN") S3_BUCKET_KUBECTL_PLUGIN_SUBPATH = KUBECTL_PLUGIN_BINARY_NAME + def main(): release_version = os.environ.get("OPERATOR_VERSION") @@ -42,22 +43,20 @@ def main(): upload_assets_to_github_release(artifacts, release_version) + # get_commit_from_tag gets the commit associated with a release tag, so that we can use that # commit to pull the artifacts from staging bucket. def get_commit_from_tag(tag: str) -> str: try: - subprocess.run( - ["git", "fetch", "--tags"], - capture_output=True, - text=True, - check=True - ) + subprocess.run(["git", "fetch", "--tags"], capture_output=True, text=True, check=True) result = subprocess.run( - ["git", "rev-parse", "--short", f"{tag}^{{commit}}"], # git rev-parse v1.1.1^{commit} + # using --short because that's how staging version is figured out for staging build scenario + # https://github.com/mongodb/mongodb-kubernetes/blob/1.5.0/scripts/dev/contexts/evg-private-context#L137 + ["git", "rev-parse", "--short", f"{tag}^{{commit}}"], # git rev-parse v1.1.1^{commit} capture_output=True, text=True, - check=True + check=True, ) return result.stdout.strip() @@ -65,6 +64,7 @@ def get_commit_from_tag(tag: str) -> str: logger.info(f"Failed to get commit for tag: {tag}, err: {e.stderr.strip()}") sys.exit(1) + # generate_checksums generates checksums for the artifacts that we are going to upload to github release as assets. # It's formatted: checksum artifact_name def generate_checksums(artifacts: list[str]): @@ -230,7 +230,9 @@ def download_artifacts_from_s3(release_version: str, commit_sha: str): return False if download_count == 0: - logger.info(f"Couldn't download artifacts from staging S3 bucket {STAGING_S3_BUCKET_NAME}, please verify that artifacts are available under dir: {commit_sha}") + logger.info( + f"Couldn't download artifacts from staging S3 bucket {STAGING_S3_BUCKET_NAME}, please verify that artifacts are available under dir: {commit_sha}" + ) sys.exit(1) logger.info("All the artifacts have been downloaded successfully.") From cc6a3b79f335d5c5b07ba620881bc64c3fd6f9f8 Mon Sep 17 00:00:00 2001 From: Vivek Singh Date: Tue, 21 Oct 2025 18:09:39 +0200 Subject: [PATCH 16/16] Address review comments --- .evergreen-functions.yml | 4 +++- .../release/kubectl_mongodb/python/consts.py | 5 ++-- .../python/promote_kubectl_plugin.py | 24 +++++++++++-------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/.evergreen-functions.yml b/.evergreen-functions.yml index 22c5b8e6f..e459c3955 100644 --- a/.evergreen-functions.yml +++ b/.evergreen-functions.yml @@ -504,6 +504,7 @@ functions: # publish_helm_chart packages and publishes the MCK helm chart to the OCI container registry publish_helm_chart: - command: subprocess.exec + params: working_dir: src/github.com/mongodb/mongodb-kubernetes binary: scripts/release/publish_helm_chart.sh @@ -887,7 +888,7 @@ functions: release_kubectl_mongodb_plugin: - command: github.generate_token params: - expansion_name: generated_token + expansion_name: GH_TOKEN - command: shell.exec type: setup params: @@ -910,4 +911,5 @@ functions: GOROOT: "/opt/golang/go1.24" MACOS_NOTARY_KEY: ${macos_notary_keyid} MACOS_NOTARY_SECRET: ${macos_notary_secret} + GH_TOKEN: ${GH_TOKEN} script: scripts/dev/run_python.sh scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py diff --git a/scripts/release/kubectl_mongodb/python/consts.py b/scripts/release/kubectl_mongodb/python/consts.py index 173e0c9ff..3e3efbcac 100644 --- a/scripts/release/kubectl_mongodb/python/consts.py +++ b/scripts/release/kubectl_mongodb/python/consts.py @@ -1,11 +1,12 @@ AWS_REGION = "eu-north-1" KUBECTL_PLUGIN_BINARY_NAME = "kubectl-mongodb" -STAGING_S3_BUCKET_NAME = "mongodb-kubernetes-staging" - GITHUB_REPO = "mongodb/mongodb-kubernetes" LOCAL_ARTIFACTS_DIR = "artifacts" CHECKSUMS_PATH = f"{LOCAL_ARTIFACTS_DIR}/checksums.txt" GORELEASER_DIST_DIR = "dist" + +BUILD_SCENARIO_RELEASE = "release" +BUILD_SCENARIO_STAGING = "staging" diff --git a/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py b/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py index 664026d7c..73aacd239 100644 --- a/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py +++ b/scripts/release/kubectl_mongodb/python/promote_kubectl_plugin.py @@ -24,12 +24,13 @@ def main(): release_version = os.environ.get("OPERATOR_VERSION") - # figure out release and staging buckets using build_scenario - build_scenario = os.environ.get("BUILD_SCENARIO") - kubectl_plugin_build_info = load_build_info(build_scenario).binaries[KUBECTL_PLUGIN_BINARY_NAME] - release_scenario_bucket_name = kubectl_plugin_build_info.s3_store + kubectl_plugin_release_info = load_build_info(BUILD_SCENARIO_RELEASE).binaries[KUBECTL_PLUGIN_BINARY_NAME] + release_scenario_bucket_name = kubectl_plugin_release_info.s3_store - download_artifacts_from_s3(release_version, get_commit_from_tag(release_version)) + kubectl_plugin_staging_info = load_build_info(BUILD_SCENARIO_STAGING).binaries[KUBECTL_PLUGIN_BINARY_NAME] + staging_scenario_bucket_name = kubectl_plugin_staging_info.s3_store + + download_artifacts_from_s3(release_version, get_commit_from_tag(release_version), staging_scenario_bucket_name) notarize_artifacts(release_version) @@ -104,6 +105,9 @@ def promote_artifacts(artifacts: list[str], release_version: str, release_scenar try: s3_client.upload_file(file, release_scenario_bucket_name, s3_key) + logger.debug( + f"Plugin file {file} was promoted to release bucket {release_scenario_bucket_name}/{s3_key} successfully" + ) except ClientError as e: logger.debug(f"failed to upload the file {file}: {e}") sys.exit(1) @@ -179,8 +183,8 @@ def s3_artifacts_path_to_local_path(release_version: str, commit_sha: str): # download_artifacts_from_s3 downloads the staging artifacts (only that ones that we would later promote) from S3 and puts # them in the local dir LOCAL_ARTIFACTS_DIR. # ToDo: if the artifacts are not present at correct location, this is going to fail silently, we should instead fail this -def download_artifacts_from_s3(release_version: str, commit_sha: str): - logger.info(f"Starting download of artifacts from staging S3 bucket: {STAGING_S3_BUCKET_NAME}") +def download_artifacts_from_s3(release_version: str, commit_sha: str, staging_s3_bucket_name: str): + logger.info(f"Starting download of artifacts from staging S3 bucket: {staging_s3_bucket_name}") try: s3_client = boto3.client("s3", region_name=AWS_REGION) @@ -200,7 +204,7 @@ def download_artifacts_from_s3(release_version: str, commit_sha: str): for s3_artifact_dir, local_subdir in artifacts_to_promote.items(): try: paginator = s3_client.get_paginator("list_objects_v2") - pages = paginator.paginate(Bucket=STAGING_S3_BUCKET_NAME, Prefix=s3_artifact_dir) + pages = paginator.paginate(Bucket=staging_s3_bucket_name, Prefix=s3_artifact_dir) for page in pages: # "Contents" corresponds to the directory in the S3 bucket if "Contents" not in page: @@ -222,7 +226,7 @@ def download_artifacts_from_s3(release_version: str, commit_sha: str): os.makedirs(os.path.dirname(final_local_path), exist_ok=True) logger.info(f"Downloading staging artifact {s3_key} to {final_local_path}") - s3_client.download_file(STAGING_S3_BUCKET_NAME, s3_key, final_local_path) + s3_client.download_file(staging_s3_bucket_name, s3_key, final_local_path) download_count += 1 except ClientError as e: @@ -231,7 +235,7 @@ def download_artifacts_from_s3(release_version: str, commit_sha: str): if download_count == 0: logger.info( - f"Couldn't download artifacts from staging S3 bucket {STAGING_S3_BUCKET_NAME}, please verify that artifacts are available under dir: {commit_sha}" + f"Couldn't download artifacts from staging S3 bucket {staging_s3_bucket_name}, please verify that artifacts are available under dir: {commit_sha}" ) sys.exit(1)