From 91de8aa034183d42b91d33e568b195050d86c7af Mon Sep 17 00:00:00 2001 From: Vipul Ajmera Date: Fri, 11 Sep 2026 10:55:28 +0530 Subject: [PATCH 1/9] Create update_pkg_wheel_name_mapping.py --- .../update_pkg_wheel_name_mapping.py | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 gha-script/upload-scripts/update_pkg_wheel_name_mapping.py diff --git a/gha-script/upload-scripts/update_pkg_wheel_name_mapping.py b/gha-script/upload-scripts/update_pkg_wheel_name_mapping.py new file mode 100644 index 0000000000..13ac330cf4 --- /dev/null +++ b/gha-script/upload-scripts/update_pkg_wheel_name_mapping.py @@ -0,0 +1,131 @@ +""" +update_pkg_wheel_name_mapping.py + +Checks and updates the pkg_wheel_name_mapping.json file in IBM Cloud Object Storage (COS) +bucket 'ose-power-artifacts-production'. + +If WHEEL_NAME is present and differs from PACKAGE_NAME: +1. Downloads pkg_wheel_name_mapping.json from IBM COS. +2. Checks if mapping[PACKAGE_NAME] == WHEEL_NAME. +3. If not present or different, updates it and uploads it back to COS. +""" + +import json +import os +import sys +import requests + +COS_ENDPOINT = "https://s3.us.cloud-object-storage.appdomain.cloud" +COS_BUCKET = "ose-power-artifacts-production" +MAPPING_FILE_KEY = "pkg_wheel_name_mapping.json" +IAM_TOKEN_URL = "https://iam.cloud.ibm.com/identity/token" + + +def get_iam_token(api_key: str) -> str: + payload = { + "grant_type": "urn:ibm:params:oauth:grant-type:apikey", + "apikey": api_key, + } + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Accept": "application/json", + } + response = requests.post(IAM_TOKEN_URL, data=payload, headers=headers, timeout=30) + response.raise_for_status() + token_data = response.json() + if "access_token" not in token_data: + raise RuntimeError(f"Failed to obtain IAM token: {token_data}") + return token_data["access_token"] + + +def get_wheel_mapping(token: str) -> dict: + url = f"{COS_ENDPOINT}/{COS_BUCKET}/{MAPPING_FILE_KEY}" + headers = { + "Authorization": f"Bearer {token}", + } + response = requests.get(url, headers=headers, timeout=30) + if response.status_code == 200: + try: + return response.json() + except Exception as e: + print(f"Warning: Failed to parse existing pkg_wheel_name_mapping.json: {e}. Starting fresh.") + return {} + elif response.status_code == 404: + print("pkg_wheel_name_mapping.json not found in COS. Initializing new mapping.") + return {} + else: + print(f"Warning: GET {url} returned status code {response.status_code}: {response.text}") + return {} + + +def upload_wheel_mapping(token: str, mapping_data: dict) -> None: + url = f"{COS_ENDPOINT}/{COS_BUCKET}/{MAPPING_FILE_KEY}" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json", + } + content = json.dumps(mapping_data, indent=2) + response = requests.put(url, data=content.encode("utf-8"), headers=headers, timeout=30) + if response.status_code in (200, 204) and "" not in response.text: + print("Successfully uploaded updated pkg_wheel_name_mapping.json to COS.") + else: + raise RuntimeError(f"Failed to upload pkg_wheel_name_mapping.json. Status: {response.status_code}, Response: {response.text}") + + +def main() -> None: + package_name = os.environ.get("PACKAGE_NAME", "").strip().lower() + wheel_name = os.environ.get("WHEEL_NAME", "").strip().lower() + api_key = os.environ.get("GHA_CURRENCY_SERVICE_ID_API_KEY", "").strip() + + print(f"Package Name: {package_name}") + print(f"Wheel Name: {wheel_name}") + + if not package_name: + print("PACKAGE_NAME is empty. Skipping wheel mapping update.") + return + + if not wheel_name: + print("WHEEL_NAME is not set or empty. Skipping wheel mapping update.") + return + + if wheel_name == package_name: + print(f"WHEEL_NAME '{wheel_name}' matches PACKAGE_NAME '{package_name}'. No mapping update needed.") + return + + if not api_key: + print("Warning: GHA_CURRENCY_SERVICE_ID_API_KEY not set. Cannot update pkg_wheel_name_mapping.json in COS.") + return + + print(f"Detected difference: wheel_name '{wheel_name}' vs package_name '{package_name}'") + print("Fetching IAM access token...") + token = get_iam_token(api_key) + + print("Fetching current pkg_wheel_name_mapping.json from COS...") + raw_mapping = get_wheel_mapping(token) + # Ensure existing mapping keys and values are normalized to lowercase + mapping = {k.strip().lower(): v.strip().lower() for k, v in raw_mapping.items()} + + current_val = mapping.get(package_name) + if current_val == wheel_name: + print(f"Mapping '{package_name}': '{wheel_name}' already exists and is up to date in COS.") + print("\n--- Current pkg_wheel_name_mapping.json ---") + print(json.dumps(mapping, indent=2)) + print("----------------------------------\n") + return + + print(f"Updating mapping: '{package_name}': '{wheel_name}' (was: '{current_val}')") + mapping[package_name] = wheel_name + + sorted_mapping = {k: mapping[k] for k in sorted(mapping.keys())} + + print("\n--- Updated pkg_wheel_name_mapping.json ---") + print(json.dumps(sorted_mapping, indent=2)) + print("----------------------------------\n") + + print("Uploading updated pkg_wheel_name_mapping.json to COS...") + upload_wheel_mapping(token, sorted_mapping) + print("Wheel mapping synchronization completed successfully.") + + +if __name__ == "__main__": + main() From 503d05bac46b534bfeb498dedb086e9361224698 Mon Sep 17 00:00:00 2001 From: Vipul Ajmera Date: Fri, 11 Sep 2026 10:56:07 +0530 Subject: [PATCH 2/9] Update read_buildinfo.sh --- gha-script/read_buildinfo.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gha-script/read_buildinfo.sh b/gha-script/read_buildinfo.sh index 3e7e156cbd..dab018e87f 100755 --- a/gha-script/read_buildinfo.sh +++ b/gha-script/read_buildinfo.sh @@ -26,6 +26,10 @@ config_file='build_info.json' if [ -f $config_file ]; then jsonObj=$config_file build_script=$(jq .build_script $jsonObj) + wheel_name="" + if $(jq 'has("wheel_name")' $jsonObj); then + wheel_name=$(jq -r .wheel_name $jsonObj) + fi if $(jq 'has("use_non_root_user")' $jsonObj); then nonRootBuild=$(jq .use_non_root_user $jsonObj) @@ -61,6 +65,11 @@ if [ -f $config_file ]; then build_script=$(jq -c "$version_block.build_script" $config_file) fi + # version-specific wheel_name + if [[ $(jq -r "$version_block.wheel_name" $config_file) != "null" ]]; then + wheel_name=$(jq -r "$version_block.wheel_name" $config_file) + fi + # version-specific base_docker_image if [[ $(jq -r "$version_block.base_docker_image" $config_file) != "null" ]]; then basename=$(jq -r "$version_block.base_docker_image" $config_file) @@ -299,6 +308,7 @@ echo "export NON_ROOT_BUILD=\"$nonRootBuild\"" >> $CUR_DIR/var echo "export TESTED_ON=\"$tested_on\"" >> $CUR_DIR/variable.sh echo "export AUDITWHEEL_EXCLUDE=\"$AUDITWHEEL_EXCLUDE\"" >> $CUR_DIR/variable.sh echo "export SKIP_PYTHON_VERSIONS=\"$SKIP_PYTHON_VERSIONS\"" >> $CUR_DIR/variable.sh +echo "export WHEEL_NAME=\"$wheel_name\"" >> $CUR_DIR/variable.sh # Full array - kept for any downstream consumer that still needs it echo "export BUILD_SCRIPTS_JSON='$BUILD_SCRIPTS_JSON'" >> $CUR_DIR/variable.sh # Per-UBI-major named exports - empty string when that UBI version has no script From 5f58fd07611a77b9166c6aa15968659e2a62c8f0 Mon Sep 17 00:00:00 2001 From: Vipul Ajmera Date: Fri, 11 Sep 2026 11:00:06 +0530 Subject: [PATCH 3/9] Update currency-build.yaml --- .github/workflows/currency-build.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/currency-build.yaml b/.github/workflows/currency-build.yaml index f666bde149..55b4f48ac6 100644 --- a/.github/workflows/currency-build.yaml +++ b/.github/workflows/currency-build.yaml @@ -124,6 +124,13 @@ jobs: echo "__EOF__" } >> $GITHUB_OUTPUT + - name: Update wheel mapping in COS + env: + GHA_CURRENCY_SERVICE_ID_API_KEY: ${{ secrets.GHA_CURRENCY_SERVICE_ID_API_KEY }} + run: | + source variable.sh + python3 ./gha-script/upload-scripts/update_pkg_wheel_name_mapping.py + - name: Create scanner-env.sh run: | mkdir package-cache From 00fb5a763c93686f9d47d3ff30b0728675c19846 Mon Sep 17 00:00:00 2001 From: Vipul Ajmera Date: Fri, 11 Sep 2026 11:15:44 +0530 Subject: [PATCH 4/9] Create check_wheel_version.py --- gha-script/check_wheel_version.py | 169 ++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 gha-script/check_wheel_version.py diff --git a/gha-script/check_wheel_version.py b/gha-script/check_wheel_version.py new file mode 100644 index 0000000000..27329a8d8f --- /dev/null +++ b/gha-script/check_wheel_version.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 + +""" +check_wheel_version.py +Validates a built wheel's version before post-processing. + +Two checks: + 1. VERSION MATCH — wheel base version must correspond to GITHUB_PACKAGE_VERSION. + 2. CLEAN VERSION — wheel must not contain a git-injected local identifier. + +Usage: + python check_wheel_version.py + +Requirements: pip install packaging + +Exit codes: + 0 both checks passed + 1 one or both checks failed +""" + +import re +import sys + +from packaging.specifiers import SpecifierSet +from packaging.utils import InvalidWheelFilename, parse_wheel_filename +from packaging.version import InvalidVersion, Version + +# --------------------------------------------------------------------------- +# Compiled patterns +# --------------------------------------------------------------------------- + +# Detects a git-hash local segment (6+ hex chars, optional leading 'g'). +# Matches: g1892993bc 56be3b5e g6909efdd6 +# No match: cpu cuda118 rocm6 cpu.ppc64le +_GIT_HASH_RE = re.compile(r"(?:^|\.)g?[0-9a-f]{6,}(?:\.|$)", re.IGNORECASE) + +# Strips operator separators — e.g. pkg==1.2.3 pkg@1.2.3 +_RE_OPERATOR = re.compile(r"^[^@=\s]+(?:@|==)(\S+)") + +# Strips common textual version prefixes — e.g. v1.2 release-v1.2 rel_1_2 n7 (ffmpeg) +# To add a new prefix pattern, extend this alternation. +_RE_PREFIX = re.compile(r"^(?:release-v|release[-_]|rel_|version_|v|n(?=\d))", re.IGNORECASE) + +# Strips a leading package-name segment — e.g. azure-mgmt-batch_18.0.0 jaxlib-v0.4.7 cares-1_19_1 +# To add a new package-name pattern, extend this regex. +_RE_PKG_PREFIX = re.compile(r"^[a-zA-Z][\w-]*?[-_]v?(\d[^\n]*)") + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _is_dirty_local(local: str) -> bool: + """Return True if *local* contains a setuptools-scm git-hash segment.""" + return bool(_GIT_HASH_RE.search(local)) + + +def _normalize_version(github_package_version: str) -> str: + """Return a bare version number from any common tagging convention. + + Strips (in order): + 1. Operator separators pkg==1.2.3 pkg@1.2.3 + 2. Textual prefixes v release-v rel_ version_ n (ffmpeg) + 3. Package-name prefix azure-mgmt-batch_18.0.0 jaxlib-v0.4.7 + 4. Underscore → dot 1_4_39 → 1.4.39 + + Returns the input unchanged when no rule matches — the caller can detect + this (norm == github_package_version) and hint that a new regex rule may + be needed. + """ + s = github_package_version.strip() + m = _RE_OPERATOR.match(s) + if m: + s = m.group(1) + s = _RE_PREFIX.sub("", s) + m = _RE_PKG_PREFIX.match(s) + if m: + s = m.group(1) + return s.replace("_", ".") + + +def version_matches(wheel_ver: Version, norm: str) -> bool: + """Return True if *wheel_ver* corresponds to the already-normalised *norm*. + + Tier 1 — SpecifierSet: handles PEP 440 versions including dev/rc/post/local + on the correct base (e.g. 1.14.0.dev0 matches norm '1.14.0'). + + Tier 2 — Exact equality fallback: for numeric strings that are not valid + PEP 440 (e.g. date-based '20220401'). + """ + # Tier 1 + try: + spec = SpecifierSet(f"=={Version(norm).base_version}.*", prereleases=True) + return str(wheel_ver) in spec + except InvalidVersion: + pass + + # Tier 2 + base = wheel_ver.base_version + return base == norm or base.replace(".", "_") == norm.replace(".", "_") + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def main() -> None: + if len(sys.argv) != 3: + print("Usage: check_wheel_version.py ") + sys.exit(1) + + wheel_filename = sys.argv[1] + github_package_version = sys.argv[2].strip() + + try: + _, ver, _, _ = parse_wheel_filename(wheel_filename) + except InvalidWheelFilename as exc: + print(f"ERROR: Cannot parse wheel filename: {exc}") + sys.exit(1) + + sep = "=" * 60 + print(f"\n{sep}") + print("Wheel Version Validation") + print(sep) + print(f" Wheel : {wheel_filename}") + print(f" Expected : {github_package_version}") + print(f" Got : {ver} (base: {ver.base_version})") + print(sep) + + failed = False + norm = _normalize_version(github_package_version) + + # Check 1: version match + if version_matches(ver, norm): + print(f" PASS [1] Version match ({ver.base_version} matches {github_package_version})") + else: + print(" FAIL [1] Version mismatch!") + print(f" Expected : {github_package_version}") + print(f" Normalised: {norm}") + print(f" Got : {ver.base_version}") + if norm == github_package_version: + # _normalize_version made no change — likely an unrecognised tag format. + print(f" _normalize_version did not recognise the format of '{github_package_version}'.") + print(" If this is a new tag convention, add a rule to") + print(" _RE_PREFIX or _RE_PKG_PREFIX in check_wheel_version.py.") + else: + print(" The build script produced a different version than requested.") + print(f" Ensure it checks out / pins {github_package_version}") + failed = True + + # Check 2: no git-injected local identifier. + # Intentional variant labels (+cpu, +cuda118, +rocm6) are allowed. + if ver.local is None: + print(" PASS [2] Clean version (no local identifier)") + elif _is_dirty_local(str(ver.local)): + print(f" FAIL [2] Git-injected local identifier: +{ver.local}") + print(f" Got : {ver}") + print(f" Expected : {ver.base_version} (no git hash)") + print(" setuptools-scm injected a commit hash because the") + print(" source tree was not at a clean tagged commit.") + print(" Ensure the build script checks out the exact release tag.") + failed = True + else: + print(f" PASS [2] Intentional local variant (+{ver.local})") + + print() + sys.exit(1 if failed else 0) + + +if __name__ == "__main__": + main() From e45d8245abe6f7853925fad9eb7e78ae1d7a2af8 Mon Sep 17 00:00:00 2001 From: Vipul Ajmera Date: Fri, 11 Sep 2026 11:16:19 +0530 Subject: [PATCH 5/9] Update build_wheels.py --- gha-script/build_wheels.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gha-script/build_wheels.py b/gha-script/build_wheels.py index 4dc87fb8a7..45f4da3d3d 100644 --- a/gha-script/build_wheels.py +++ b/gha-script/build_wheels.py @@ -6,7 +6,7 @@ import docker import json -def trigger_build_wheel(wrapper_file, python_version, image_name, file_name, version, post_process_file): +def trigger_build_wheel(wrapper_file, python_version, image_name, file_name, version, post_process_file, check_wheel_version_file): # Docker client setup client = docker.DockerClient(base_url='unix://var/run/docker.sock') @@ -41,7 +41,7 @@ def trigger_build_wheel(wrapper_file, python_version, image_name, file_name, ver command = [ "bash", "-c", - f"{setup}cd /home/tester/ && {run_script} {python_version} {file_name} {version} {post_process_file}" + f"{setup}cd /home/tester/ && {run_script} {python_version} {file_name} {version} {post_process_file} {check_wheel_version_file}" ] # Run container @@ -99,4 +99,4 @@ def trigger_build_wheel(wrapper_file, python_version, image_name, file_name, ver if __name__=="__main__": print("Inside python program") - trigger_build_wheel(sys.argv[1],sys.argv[2],sys.argv[3],sys.argv[4],sys.argv[5],sys.argv[6]) + trigger_build_wheel(sys.argv[1],sys.argv[2],sys.argv[3],sys.argv[4],sys.argv[5],sys.argv[6],sys.argv[7]) From 38a7307577e3678eeba5850c9af04ee72aaf01d3 Mon Sep 17 00:00:00 2001 From: Vipul Ajmera Date: Fri, 11 Sep 2026 11:16:52 +0530 Subject: [PATCH 6/9] Update build_wheels.sh --- gha-script/build_wheels.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gha-script/build_wheels.sh b/gha-script/build_wheels.sh index 8761b8ae6b..0269792383 100644 --- a/gha-script/build_wheels.sh +++ b/gha-script/build_wheels.sh @@ -97,7 +97,10 @@ WHEEL_SCRIPT=gha-script/create_wheel_wrapper.sh # path to post_process_wheel script (suffix addition, license addition, metadata addition) POST_PROCESS_SCRIPT_PATH=gha-script/post_process_wheel.py -python3 gha-script/build_wheels.py "$WHEEL_SCRIPT" "$PYTHON_VERSION" "$docker_image" "$PKG_DIR_PATH$BUILD_SCRIPT" "$VERSION" "$POST_PROCESS_SCRIPT_PATH" 2>&1 | tee wheel_build_log +# path to check_wheel_version script (version match + clean version validation) +CHECK_WHEEL_VERSION_SCRIPT=gha-script/check_wheel_version.py + +python3 gha-script/build_wheels.py "$WHEEL_SCRIPT" "$PYTHON_VERSION" "$docker_image" "$PKG_DIR_PATH$BUILD_SCRIPT" "$VERSION" "$POST_PROCESS_SCRIPT_PATH" "$CHECK_WHEEL_VERSION_SCRIPT" 2>&1 | tee wheel_build_log wheel_status=${PIPESTATUS[0]} log_size=$(stat -c %s wheel_build_log) From 1da0761b48cf5c1a6762e40ad913eca5ed18c6ff Mon Sep 17 00:00:00 2001 From: Vipul Ajmera Date: Fri, 11 Sep 2026 11:19:27 +0530 Subject: [PATCH 7/9] Update create_wheel_wrapper.sh --- gha-script/create_wheel_wrapper.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/gha-script/create_wheel_wrapper.sh b/gha-script/create_wheel_wrapper.sh index 71365976cc..3213cc8118 100644 --- a/gha-script/create_wheel_wrapper.sh +++ b/gha-script/create_wheel_wrapper.sh @@ -5,6 +5,7 @@ PYTHON_VERSION=$1 BUILD_SCRIPT_PATH=${2:-""} EXTRA_ARGS=${3:-""} POST_PROCESS_SCRIPT_PATH=${4:-"post_process_wheel.py"} +CHECK_WHEEL_VERSION_SCRIPT=${5:-"check_wheel_version.py"} CURRENT_DIR=$(pwd) # install git - required by generate_sha() for all Python versions and UBI versions @@ -360,6 +361,23 @@ fi cd "$CURRENT_DIR" wheel_final=(*.whl) +# --------------------------------------------------------------------------- +# check_wheel_version: validates the built wheel version against +# GITHUB_PACKAGE_VERSION before any post-processing takes place. +# +# Two checks (both must pass): +# 1. VERSION MATCH — wheel base version must correspond to GITHUB_PACKAGE_VERSION +# Catches: build script checked out the wrong tag, e.g. requested +# v1.14.0 but the package self-reported 1.15.0.dev0. +# 2. CLEAN VERSION — wheel must NOT have a PEP 440 local identifier (+...) +# Catches: setuptools-scm / build system injected a git hash or build +# date because the source tree was not at a clean tagged commit, +# e.g. 0.0.30+56be3b5e.d20250607 or 0.1.3.dev0+ga7d8bbf.d20250607 +# --------------------------------------------------------------------------- +if [ -n "$EXTRA_ARGS" ]; then + python "$CHECK_WHEEL_VERSION_SCRIPT" "${wheel_final[0]}" "$EXTRA_ARGS" +fi + # --------------------------------------------------------------------------- # run_cve_scan: runs generalized_wheel_scanner.py on the built wheel. # From cf4e31c67e0b8a932cb2008f9e292bc24b1f7729 Mon Sep 17 00:00:00 2001 From: Vipul Ajmera Date: Wed, 23 Sep 2026 17:42:39 +0530 Subject: [PATCH 8/9] Update update_pkg_wheel_name_mapping.py --- .../update_pkg_wheel_name_mapping.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/gha-script/upload-scripts/update_pkg_wheel_name_mapping.py b/gha-script/upload-scripts/update_pkg_wheel_name_mapping.py index 13ac330cf4..e8b2751620 100644 --- a/gha-script/upload-scripts/update_pkg_wheel_name_mapping.py +++ b/gha-script/upload-scripts/update_pkg_wheel_name_mapping.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 """ update_pkg_wheel_name_mapping.py @@ -31,11 +32,15 @@ def get_iam_token(api_key: str) -> str: "Accept": "application/json", } response = requests.post(IAM_TOKEN_URL, data=payload, headers=headers, timeout=30) - response.raise_for_status() - token_data = response.json() - if "access_token" not in token_data: - raise RuntimeError(f"Failed to obtain IAM token: {token_data}") - return token_data["access_token"] + + if response.status_code != 200: + error_msg = response.json().get("errorMessage", "Authentication failed") + raise RuntimeError(f"IAM Token request failed [{response.status_code}]: {error_msg}") + + token = response.json().get("access_token") + if not token: + raise RuntimeError("Failed to obtain IAM access token.") + return token def get_wheel_mapping(token: str) -> dict: From 021931de3a9cfe2fe30840df496836c9946e2ec6 Mon Sep 17 00:00:00 2001 From: Vipul Ajmera Date: Wed, 23 Sep 2026 17:47:01 +0530 Subject: [PATCH 9/9] Update update_pkg_wheel_name_mapping.py --- gha-script/upload-scripts/update_pkg_wheel_name_mapping.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/gha-script/upload-scripts/update_pkg_wheel_name_mapping.py b/gha-script/upload-scripts/update_pkg_wheel_name_mapping.py index e8b2751620..0671f4d619 100644 --- a/gha-script/upload-scripts/update_pkg_wheel_name_mapping.py +++ b/gha-script/upload-scripts/update_pkg_wheel_name_mapping.py @@ -71,10 +71,8 @@ def upload_wheel_mapping(token: str, mapping_data: dict) -> None: } content = json.dumps(mapping_data, indent=2) response = requests.put(url, data=content.encode("utf-8"), headers=headers, timeout=30) - if response.status_code in (200, 204) and "" not in response.text: - print("Successfully uploaded updated pkg_wheel_name_mapping.json to COS.") - else: - raise RuntimeError(f"Failed to upload pkg_wheel_name_mapping.json. Status: {response.status_code}, Response: {response.text}") + response.raise_for_status() + print("Successfully uploaded updated pkg_wheel_name_mapping.json to COS.") def main() -> None: