Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2517e69
update
wangzelin007 Feb 7, 2024
bc3aed8
update
wangzelin007 Feb 7, 2024
f2b7bab
update
wangzelin007 Feb 7, 2024
87f519a
update
wangzelin007 Feb 7, 2024
8906e0b
update
wangzelin007 Feb 7, 2024
b6017d4
Update update_ext_cmd_tree.py
wangzelin007 Feb 7, 2024
2c3d7a3
Update update_ext_cmd_tree.py
wangzelin007 Feb 7, 2024
5de410e
update
wangzelin007 Feb 8, 2024
0c3bb06
Merge branch 'main' into release-scripts
wangzelin007 Feb 8, 2024
bfe5ed1
update
wangzelin007 Feb 8, 2024
8a844b7
Update sync_extensions.py
wangzelin007 Feb 8, 2024
29f00c6
Update util.py
wangzelin007 Feb 8, 2024
148eaf3
Merge branch 'main' into release-scripts
wangzelin007 Apr 8, 2024
e8c767a
Update util.py
wangzelin007 May 31, 2024
212f082
Update util.py
wangzelin007 May 31, 2024
08f3b0a
Create build_all_ext.sh
wangzelin007 May 31, 2024
bb4a4e2
Revert "Create build_all_ext.sh"
wangzelin007 Jun 3, 2024
218ca95
Update build_ext_cmd_tree.sh
wangzelin007 Oct 15, 2024
690b7ce
Update build_ext_cmd_tree.sh
wangzelin007 Oct 15, 2024
e8572a7
Update build_ext_cmd_tree.sh
wangzelin007 Oct 15, 2024
7447487
Update build_ext_cmd_tree.sh
wangzelin007 Oct 15, 2024
868a7b2
Update build_ext_cmd_tree.sh
wangzelin007 Oct 15, 2024
80a3c1c
Update build_ext_cmd_tree.sh
wangzelin007 Oct 16, 2024
6ac4465
Update build_ext_cmd_tree.sh
wangzelin007 Oct 17, 2024
9beaa14
Update build_ext_cmd_tree.sh
wangzelin007 Oct 17, 2024
8578db6
Merge branch 'main' into release-scripts
wangzelin007 Nov 18, 2024
ac63598
generate command tree for ml
wangzelin007 Dec 20, 2024
50d18b4
Merge branch 'main' into release-scripts
wangzelin007 Dec 20, 2024
6d0db41
Merge branch 'main' into release-scripts
wangzelin007 Dec 20, 2024
9c84141
generate command tree for ml
wangzelin007 Dec 23, 2024
eac3fb4
generate command tree for ml
wangzelin007 Dec 24, 2024
49d26d1
fix: add rdbms-connect and deploy-to-azure to cmd tree blocklist
wangzelin007 Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions scripts/ci/build_ext_cmd_tree.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ if [[ -z "$changed_content" ]]; then
exit 0
fi

pip install azure-cli-core azure-cli requests
pip install azure-storage-blob==1.5.0
echo "Listing Available Extensions:"
az extension list-available -otable

Expand All @@ -20,7 +18,8 @@ export AZURE_EXTENSION_INDEX_URL=https://raw.githubusercontent.com/Azure/azure-c
output=$(az extension list-available --query [].name -otsv)
# azure-cli-ml is replaced by ml
# disable alias which relies on Jinja2 2.10
blocklist=("azure-cli-ml" "alias")
# 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

Expand All @@ -40,4 +39,7 @@ for ext in $output; do
fi
done

pip install azure-cli-core azure-cli requests azure-storage-blob==1.5.0
# arcdata: ModuleNotFoundError: No module named 'regex._regex'
pip install regex
python $(cd $(dirname $0); pwd)/update_ext_cmd_tree.py $filter_exts
42 changes: 19 additions & 23 deletions scripts/ci/sync_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
# pylint: disable=line-too-long
# pylint: disable=broad-except

import json
import os
import re
import json
import subprocess

from util import run_az_cmd

DEFAULT_TARGET_INDEX_URL = os.getenv('AZURE_EXTENSION_TARGET_INDEX_URL')
STORAGE_ACCOUNT = os.getenv('AZURE_EXTENSION_TARGET_STORAGE_ACCOUNT')
STORAGE_CONTAINER = os.getenv('AZURE_EXTENSION_TARGET_STORAGE_CONTAINER')
Expand Down Expand Up @@ -48,7 +50,6 @@ def download_file(url, file_path):
if chunk: # ignore keep-alive new chunks
f.write(chunk)


def _sync_wheel(ext, updated_indexes, failed_urls, overwrite, temp_dir):
download_url = ext['downloadUrl']
whl_file = download_url.split('/')[-1]
Expand All @@ -62,27 +63,24 @@ def _sync_wheel(ext, updated_indexes, failed_urls, overwrite, temp_dir):
if not overwrite:
cmd = ['az', 'storage', 'blob', 'exists', '--container-name', f'{STORAGE_CONTAINER}', '--account-name',
f'{STORAGE_ACCOUNT}', '--name', f'{blob_name}', '--auth-mode', 'login']
result = subprocess.run(cmd, capture_output=True)
if result.stdout and json.loads(result.stdout)['exists']:
message = f"Checking if '{blob_name}' exists in the storage"
result = run_az_cmd(cmd, message=message, raise_error=True)
if json.loads(result.stdout)['exists']:
print("Skipping '{}' as it already exists...".format(whl_file))
return

cmd = ['az', 'storage', 'blob', 'upload', '--container-name', f'{STORAGE_CONTAINER}', '--account-name',
f'{STORAGE_ACCOUNT}', '--name', f'{blob_name}', '--file', f'{os.path.abspath(whl_path)}',
'--auth-mode', 'login', '--overwrite']
result = subprocess.run(cmd, capture_output=True)
if result.returncode != 0:
print(f"Failed to upload '{whl_file}' to the storage account")
raise
message = f"uploading '{blob_name}' to the storage"
run_az_cmd(cmd, message=message, raise_error=True)

cmd = ['az', 'storage', 'blob', 'url', '--container-name', f'{STORAGE_CONTAINER}', '--account-name',
f'{STORAGE_ACCOUNT}', '--name', f'{blob_name}', '--auth-mode', 'login']
result = subprocess.run(cmd, capture_output=True)
print(result)
if result.stdout and result.returncode == 0:
url = json.loads(result.stdout)
else:
print("Failed to get the URL for '{}'".format(whl_file))
raise
message = f"Getting the URL for '{blob_name}'"
result = run_az_cmd(cmd, message=message, raise_error=True)
url = json.loads(result.stdout)

updated_index = ext
updated_index['downloadUrl'] = url
updated_indexes.append(updated_index)
Expand Down Expand Up @@ -157,10 +155,9 @@ def main():
cmd = ['az', 'storage', 'blob', 'upload', '--container-name', f'{STORAGE_CONTAINER}', '--account-name',
f'{STORAGE_ACCOUNT}', '--name', f'{backup_index_name}',
'--file', f'{os.path.abspath(target_index_path)}', '--auth-mode', 'login', '--overwrite']
result = subprocess.run(cmd, capture_output=True)
if result.returncode != 0:
print(f"Failed to upload '{target_index_path}' to the storage account")
raise
message = f"Uploading '{backup_index_name}' to the storage"
run_az_cmd(cmd, message=message, raise_error=True)

# start with an empty index.json to sync all extensions
initial_index = {"extensions": {}, "formatVersion": "1"}
open(target_index_path, 'w').write(json.dumps(initial_index, indent=4, sort_keys=True))
Expand All @@ -185,10 +182,9 @@ def main():
cmd = ['az', 'storage', 'blob', 'upload', '--container-name', f'{STORAGE_CONTAINER}', '--account-name',
f'{STORAGE_ACCOUNT}', '--name', f'{index_name}', '--file', f'{os.path.abspath(target_index_path)}',
'--auth-mode', 'login', '--overwrite']
result = subprocess.run(cmd, capture_output=True)
if result.returncode != 0:
print(f"Failed to upload '{target_index_path}' to the storage account")
raise
message = f"Uploading '{index_name}' to the storage"
run_az_cmd(cmd, message=message, raise_error=True)

print("\nSync finished.")
if updated_indexes:
print("New extensions available in:")
Expand Down
59 changes: 48 additions & 11 deletions scripts/ci/update_ext_cmd_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
import filecmp
import json
import os
import subprocess
import sys
import subprocess

from azure.cli.core import get_default_cli
from azure.cli.core._session import Session
from azure.cli.core.commands import _load_extension_command_loader
from azure.cli.core.extension import get_extension_modname, get_extension_path
from sync_extensions import download_file
from util import run_az_cmd

STORAGE_ACCOUNT = os.getenv('AZURE_EXTENSION_CMD_TREE_STORAGE_ACCOUNT')
STORAGE_CONTAINER = os.getenv('AZURE_EXTENSION_CMD_TREE_STORAGE_CONTAINER')
Expand All @@ -22,6 +24,36 @@
file_name = 'extCmdTreeToUpload.json'


def execute_command(command):
"""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)}"
Comment on lines +28 to +36
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
"""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

Copilot uses AI. Check for mistakes.


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)
Comment on lines +39 to +54
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.*.

Copilot uses AI. Check for mistakes.


def merge(data, key, value):
if isinstance(value, str):
if key in data:
Expand All @@ -35,6 +67,16 @@ def merge(data, key, value):

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")
Comment on lines +70 to +79
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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")

Copilot uses AI. Check for mistakes.
Comment on lines 68 to +79
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.

ext_dir = get_extension_path(ext_name)
ext_mod = get_extension_modname(ext_name, ext_dir=ext_dir)
Expand Down Expand Up @@ -83,19 +125,14 @@ def upload_cmd_tree():
cmd = ['az', 'storage', 'blob', 'upload', '--container-name', f'{STORAGE_CONTAINER}', '--account-name',
f'{STORAGE_ACCOUNT}', '--name', f'{blob_file_name}', '--file', f'{file_path}', '--auth-mode', 'login',
'--overwrite']
result = subprocess.run(cmd, capture_output=True)
if result.returncode != 0:
print(f"Failed to upload '{blob_file_name}' to the storage account")
print(result)
message = f"Uploading '{blob_file_name}' to the storage"
run_az_cmd(cmd, message=message, raise_error=True)

cmd = ['az', 'storage', 'blob', 'url', '--container-name', f'{STORAGE_CONTAINER}', '--account-name',
f'{STORAGE_ACCOUNT}', '--name', f'{blob_file_name}', '--auth-mode', 'login']
result = subprocess.run(cmd, capture_output=True)
if result.stdout and result.returncode == 0:
url = json.loads(result.stdout)
else:
print(f"Failed to get the URL for '{blob_file_name}'")
raise
message = f"Getting the URL for '{blob_file_name}'"
result = run_az_cmd(cmd, message=message, raise_error=True)
url = json.loads(result.stdout)

download_file_path = os.path.expanduser(os.path.join('~', '.azure', downloaded_file_name))
download_file(url, download_file_path)
Expand Down
32 changes: 27 additions & 5 deletions scripts/ci/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import json
import logging
import os
import re
import shlex
import json
import subprocess
import zipfile

from subprocess import check_output

logger = logging.getLogger(__name__)

# copy from wheel==0.30.0
Expand Down Expand Up @@ -136,7 +135,7 @@ def diff_code(start, end):

# If running in Travis CI, only run tests for edited extensions
commit_range = os.environ.get('TRAVIS_COMMIT_RANGE')
if commit_range and not check_output(
if commit_range and not subprocess.check_output(
['git', '--no-pager', 'diff', '--name-only', commit_range, '--', src_d_full]):
continue

Expand All @@ -154,7 +153,7 @@ def diff_code(start, end):
else:
cmd = cmd_tpl.format(start=start, end=end,
code_dir=src_d_full)
if not check_output(shlex.split(cmd)):
if not subprocess.check_output(shlex.split(cmd)):
continue

diff_ref.append((pkg_name, src_d_full))
Expand All @@ -163,3 +162,26 @@ def diff_code(start, end):
f'end: {end}, '
f'diff_ref: {diff_ref}.')
return diff_ref


def run_az_cmd(cmd, message=False, raise_error=True):
"""
:param cmd: The entire command line to run.
:param message: A custom message to print, or True (bool) to use a default.
:param raise_error: Whether to raise an exception if the command fails.
"""
# use default message if custom not provided
if message is True:
print(f'Running: {cmd}')

if message:
Comment on lines +176 to +177
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
if message:
elif isinstance(message, str):

Copilot uses AI. Check for mistakes.
print(f'{message}')

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
Comment on lines +180 to +186
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
return ex
Loading