Conversation
️✔️Azure CLI Extensions Breaking Change Test
|
|
Hi @wangzelin007, |
|
CI |
|
I would recommend we postpone this PR until we are back from holiday. |
|
This reverts commit 08f3b0a.
There was a problem hiding this comment.
Pull request overview
Updates CI/release helper scripts used to sync/publish Azure CLI extensions and generate/upload the extension command tree, aligning with #7273 (UTF-8 command tree session + idempotent blob uploads) and adding additional build-time adjustments.
Changes:
- Introduce
run_az_cmdwrapper and use it foraz storage bloboperations (including--overwrite) in sync/upload flows. - Update command-tree generation to use UTF-8 session encoding and adjust command-tree build blocklist / dependencies.
- Add runtime package inspection/upgrade logic in
update_ext_cmd_tree.pyfor themlextension path.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| scripts/ci/util.py | Adds run_az_cmd wrapper and switches to subprocess.check_output usage. |
| scripts/ci/update_ext_cmd_tree.py | Uses UTF-8 session for cmd tree, switches upload/url calls to run_az_cmd, and adds package upgrade helpers/logic. |
| scripts/ci/sync_extensions.py | Replaces direct subprocess.run calls with run_az_cmd for storage operations. |
| scripts/ci/build_ext_cmd_tree.sh | Adjusts blocklist and changes when dependencies are installed (plus adds regex install). |
Comments suppressed due to low confidence (1)
scripts/ci/build_ext_cmd_tree.sh:35
build_ext_cmd_tree.shnow runsaz extension list-available/az extension addbefore installingazure-cli/azure-cli-core. This makes the script dependent onazalready being present on the agent image; previously it bootstrapped those dependencies itself. Consider either restoring the early install (or adding a fast preflight check foraz) to avoid failures on images without Azure CLI preinstalled.
echo "Listing Available Extensions:"
az extension list-available -otable
# turn off telemetry as it crowds output
export AZURE_CORE_COLLECT_TELEMETRY=False
# use index.json in master branch
export AZURE_EXTENSION_INDEX_URL=https://raw.githubusercontent.com/Azure/azure-cli-extensions/master/src/index.json
output=$(az extension list-available --query [].name -otsv)
# azure-cli-ml is replaced by ml
# disable alias which relies on Jinja2 2.10
# disable rdbms-connect and deploy-to-azure which cause cmd tree build failures
blocklist=("azure-cli-ml" "alias" "rdbms-connect" "deploy-to-azure")
rm -f ~/.azure/extCmdTreeToUpload.json
filter_exts=""
for ext in $output; do
ext=${ext%$'\r'} # Remove a trailing newline when running on Windows.
if [[ " ${blocklist[@]} " =~ " ${ext} " ]]; then
continue
fi
filter_exts="${filter_exts} ${ext}"
echo "Adding extension:" $ext
az extension add --upgrade -n $ext
if [ $? != 0 ]
|
|
||
| if message: |
There was a problem hiding this comment.
run_az_cmd prints twice when message is True: it prints the default "Running: ..." line and then prints the boolean value (True) because the subsequent if message: branch also runs. Consider using elif message is not False: (or checking for isinstance(message, str)) so True triggers only the default message and never prints the literal boolean.
| if message: | |
| elif isinstance(message, str): |
| try: | ||
| result = subprocess.run(cmd, capture_output=True, check=True) | ||
| return result | ||
| except subprocess.CalledProcessError as ex: | ||
| error_message = ex.stderr if ex.stderr else str(ex) | ||
| if raise_error: | ||
| raise Exception(f"Failed to run command: {cmd}\nError: {error_message}") from ex |
There was a problem hiding this comment.
On failure, error_message = ex.stderr will usually be bytes (because subprocess.run is not using text=True). This makes the raised Exception message hard to read (e.g., b'...'). Consider running with text=True (or decoding ex.stderr) so error output is readable and consistent.
| """Execute a shell command and return the output.""" | ||
| try: | ||
| result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) | ||
| if result.returncode == 0: | ||
| return result.stdout.strip() | ||
| else: | ||
| return f"Error: {result.stderr.strip()}" | ||
| except Exception as e: | ||
| return f"Exception: {str(e)}" |
There was a problem hiding this comment.
The new execute_command helper swallows failures by returning strings like "Error: ..." / "Exception: ...", but callers (e.g., upgrade_package) don't check these and the script continues. For CI tooling, it would be safer to raise on non-zero return codes (or return (rc, stdout, stderr) and explicitly fail) so command-tree generation doesn’t silently proceed with a broken environment.
| """Execute a shell command and return the output.""" | |
| try: | |
| result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) | |
| if result.returncode == 0: | |
| return result.stdout.strip() | |
| else: | |
| return f"Error: {result.stderr.strip()}" | |
| except Exception as e: | |
| return f"Exception: {str(e)}" | |
| """Execute a shell command and return the output. | |
| Raises a RuntimeError if the command exits with a non-zero status. | |
| """ | |
| try: | |
| result = subprocess.run( | |
| command, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE, | |
| text=True, | |
| check=True, | |
| ) | |
| return result.stdout.strip() | |
| except subprocess.CalledProcessError as e: | |
| # Raise an explicit error so callers cannot accidentally ignore failures. | |
| stderr = (e.stderr or "").strip() | |
| raise RuntimeError( | |
| f"Command '{' '.join(command)}' failed with exit code {e.returncode}: {stderr}" | |
| ) from e |
| def get_package_version(package_name): | ||
| """Get the current version of a Python package.""" | ||
| command = ["pip", "show", package_name] | ||
| output = execute_command(command) | ||
| if "Version:" in output: | ||
| for line in output.splitlines(): | ||
| if line.startswith("Version:"): | ||
| version = line.split(":")[1].strip() | ||
| print(f"{package_name} current version: {version}") | ||
|
|
||
|
|
||
| def upgrade_package(package_name): | ||
| """Upgrade a Python package to the latest version.""" | ||
| command = ["pip", "install", "--upgrade", package_name] | ||
| print(f"{command}") | ||
| return execute_command(command) |
There was a problem hiding this comment.
get_package_version / upgrade_package invoke pip directly (e.g., ["pip", "install", ...]). In CI this can target a different interpreter than the one running this script. Prefer using sys.executable -m pip to ensure packages are queried/installed into the same environment used to import azure.cli.*.
| if ext_name == 'ml': | ||
| get_package_version("azure-storage-blob") | ||
| upgrade_package("azure-storage-blob") | ||
| get_package_version("azure-storage-blob") | ||
| get_package_version("rpds") | ||
| upgrade_package("rpds") | ||
| get_package_version("rpds") | ||
| get_package_version("rpds-py") | ||
| upgrade_package("rpds-py") | ||
| get_package_version("rpds-py") |
There was a problem hiding this comment.
update_cmd_tree now conditionally upgrades packages at runtime when ext_name == 'ml' (azure-storage-blob, rpds, rpds-py). This is a significant behavior change that can make command-tree builds non-reproducible and can introduce unexpected dependency changes mid-run. If this is required, consider moving these pins/upgrades into the CI environment setup (e.g., the shell script) and documenting why only ml needs it.
| if ext_name == 'ml': | |
| get_package_version("azure-storage-blob") | |
| upgrade_package("azure-storage-blob") | |
| get_package_version("azure-storage-blob") | |
| get_package_version("rpds") | |
| upgrade_package("rpds") | |
| get_package_version("rpds") | |
| get_package_version("rpds-py") | |
| upgrade_package("rpds-py") | |
| get_package_version("rpds-py") |
| def update_cmd_tree(ext_name): | ||
| print(f"Processing {ext_name}") | ||
| if ext_name == 'ml': | ||
| get_package_version("azure-storage-blob") | ||
| upgrade_package("azure-storage-blob") | ||
| get_package_version("azure-storage-blob") | ||
| get_package_version("rpds") | ||
| upgrade_package("rpds") | ||
| get_package_version("rpds") | ||
| get_package_version("rpds-py") | ||
| upgrade_package("rpds-py") | ||
| get_package_version("rpds-py") |
There was a problem hiding this comment.
This PR is described as updating scripts per #7273 (encoding + --overwrite), but it also adds runtime package upgrades for ml and new helper functions. If these additions are intentional, it would help to update the PR description (or link the specific issue) so reviewers understand the extra scope and rationale.
Modify the code according to #7273
This checklist is used to make sure that common guidelines for a pull request are followed.
Related command
General Guidelines
azdev style <YOUR_EXT>locally? (pip install azdevrequired)python scripts/ci/test_index.py -qlocally? (pip install wheel==0.30.0required)For new extensions:
About Extension Publish
There is a pipeline to automatically build, upload and publish extension wheels.
Once your pull request is merged into main branch, a new pull request will be created to update
src/index.jsonautomatically.You only need to update the version information in file setup.py and historical information in file HISTORY.rst in your PR but do not modify
src/index.json.