Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 8 additions & 0 deletions .azure-pipelines/templates/automation_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ parameters:
displayName: Job name
type: string
default: ''
- name: AzdevPackage
type: string
default: 'azdev @ git+https://github.com/aryasadeghi1/azure-cli-dev-tools.git@c429216bfdae9b196dcfa6689f4d3f15a7b7054e'
- name: DiffToolPackage
type: string
default: 'azure-cli-diff-tool @ git+https://github.com/aryasadeghi1/azure-cli-dev-tools.git@c429216bfdae9b196dcfa6689f4d3f15a7b7054e#subdirectory=azure-cli-diff-tool'

steps:
- task: UsePythonVersion@0
Expand All @@ -36,6 +42,8 @@ steps:
- template: ./azdev_setup.yml
parameters:
EnableCompactAAZ: true
AzdevPackage: ${{ parameters.AzdevPackage }}
DiffToolPackage: ${{ parameters.DiffToolPackage }}
- bash: |
set -ev

Expand Down
18 changes: 16 additions & 2 deletions .azure-pipelines/templates/azdev_setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ parameters:
- name: EnableCompactAAZ
type: boolean
default: false
# Temporary immutable candidates until the validated packages are released.
# Keep both requirements on the same commit; overrides may also be wheel paths.
- name: AzdevPackage
type: string
default: 'azdev @ git+https://github.com/aryasadeghi1/azure-cli-dev-tools.git@c429216bfdae9b196dcfa6689f4d3f15a7b7054e'
- name: DiffToolPackage
type: string
default: 'azure-cli-diff-tool @ git+https://github.com/aryasadeghi1/azure-cli-dev-tools.git@c429216bfdae9b196dcfa6689f4d3f15a7b7054e#subdirectory=azure-cli-diff-tool'
steps:
- bash: |
set -ev
Expand All @@ -16,8 +24,12 @@ steps:
chmod +x env/bin/activate
. env/bin/activate

python -m pip install -U pip "setuptools<81"
pip install --upgrade "azdev==0.2.13"
python -m pip install -U pip "setuptools>=78.1.1"
packages=("$AZDEV_PACKAGE")
if [ -n "$DIFF_TOOL_PACKAGE" ]; then
packages+=("$DIFF_TOOL_PACKAGE")
fi
python -m pip install --upgrade "${packages[@]}"
azdev --version

if [ -z "$CLI_EXT_REPO_PATH" ]; then
Expand All @@ -37,5 +49,7 @@ steps:
az --version
displayName: 'azdev setup'
env:
AZDEV_PACKAGE: ${{ parameters.AzdevPackage }}
DIFF_TOOL_PACKAGE: ${{ parameters.DiffToolPackage }}
CLI_REPO_PATH: ${{ parameters.CLIRepoPath }}
CLI_EXT_REPO_PATH: ${{ parameters.CLIExtensionRepoPath }}
4 changes: 2 additions & 2 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@

/src/azure-cli/ @Azure/act-codegen-extensibility-squad @Azure/act-quality-productivity-squad @Azure/act-platform-engineering-squad

/src/azure-cli-core/setup.py @Azure/act-codegen-extensibility-squad @Azure/act-quality-productivity-squad @Azure/act-platform-engineering-squad @Azure/act-identity-squad
/src/azure-cli/setup.py @Azure/act-identity-squad @Azure/act-codegen-extensibility-squad
/src/azure-cli-core/pyproject.toml @Azure/act-codegen-extensibility-squad @Azure/act-quality-productivity-squad @Azure/act-platform-engineering-squad @Azure/act-identity-squad
/src/azure-cli/pyproject.toml @Azure/act-identity-squad @Azure/act-codegen-extensibility-squad
/src/azure-cli/requirements.*.txt @Azure/act-codegen-extensibility-squad @Azure/act-quality-productivity-squad @Azure/act-platform-engineering-squad @Azure/act-identity-squad

/src/azure-cli/azure/cli/command_modules/acr/ @Azure/act-observability-squad
Expand Down
6 changes: 5 additions & 1 deletion .github/actions/env-setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ runs:
python-version: "3.12"
- name: Install azdev
shell: bash
# Temporary immutable candidates; neither package needs a PyPI release.
env:
AZDEV_PACKAGE: 'azdev @ git+https://github.com/aryasadeghi1/azure-cli-dev-tools.git@c429216bfdae9b196dcfa6689f4d3f15a7b7054e'
DIFF_TOOL_PACKAGE: 'azure-cli-diff-tool @ git+https://github.com/aryasadeghi1/azure-cli-dev-tools.git@c429216bfdae9b196dcfa6689f4d3f15a7b7054e#subdirectory=azure-cli-diff-tool'
run: |
python -m pip install --upgrade pip
set -ev
python -m venv env
chmod +x env/bin/activate
source ./env/bin/activate
pip install azdev
python -m pip install "$AZDEV_PACKAGE" "$DIFF_TOOL_PACKAGE"
azdev --version
cd ../
azdev setup -c azure-cli --debug
Expand Down
6 changes: 3 additions & 3 deletions azure-cli.pyproj
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
<Compile Include="azure-cli-core\azure\cli\core\__init__.py" />
<Compile Include="azure-cli-core\azure\cli\__init__.py" />
<Compile Include="azure-cli-core\azure\__init__.py" />
<Compile Include="azure-cli-core\setup.py" />
<Content Include="azure-cli-core\pyproject.toml" />
<Compile Include="azure-cli-testsdk\azure\cli\testsdk\base.py" />
<Compile Include="azure-cli-testsdk\azure\cli\testsdk\checkers.py" />
<Compile Include="azure-cli-testsdk\azure\cli\testsdk\decorators.py" />
Expand All @@ -84,11 +84,11 @@
<Compile Include="azure-cli-testsdk\azure\cli\testsdk\preparers.py" />
<Compile Include="azure-cli-testsdk\azure\cli\testsdk\utilities.py" />
<Compile Include="azure-cli-testsdk\azure\cli\testsdk\__init__.py" />
<Compile Include="azure-cli-testsdk\setup.py" />
<Content Include="azure-cli-testsdk\pyproject.toml" />
<Compile Include="azure-cli\azure\cli\__init__.py" />
<Compile Include="azure-cli\azure\cli\__main__.py" />
<Compile Include="azure-cli\azure_bdist_wheel.py" />
<Compile Include="azure-cli\setup.py" />
<Content Include="azure-cli\pyproject.toml" />
<Compile Include="command_modules\azure-cli-acr\azure\cli\command_modules\acr\commands.py" />
<Compile Include="command_modules\azure-cli-acr\azure\cli\command_modules\acr\credential.py" />
<Compile Include="command_modules\azure-cli-acr\azure\cli\command_modules\acr\custom.py" />
Expand Down
9 changes: 4 additions & 5 deletions azure-cli2017.pyproj
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
<Compile Include="azure-cli-core\azure\cli\core\_session.py" />
<Compile Include="azure-cli-core\azure\cli\core\telemetry.py" />
<Compile Include="azure-cli-core\azure\cli\core\__init__.py" />
<Compile Include="azure-cli-core\setup.py" />
<Content Include="azure-cli-core\pyproject.toml" />
<Compile Include="azure-cli-telemetry\azure\cli\telemetry\components\records_collection.py" />
<Compile Include="azure-cli-telemetry\azure\cli\telemetry\components\records_collection.py" />
<Compile Include="azure-cli-telemetry\azure\cli\telemetry\components\telemetry_client.py" />
Expand Down Expand Up @@ -118,8 +118,7 @@
<Compile Include="azure-cli-telemetry\azure\cli\__init__.py" />
<Compile Include="azure-cli-telemetry\azure\__init__.py" />
<Compile Include="azure-cli-telemetry\azure\__init__.py" />
<Compile Include="azure-cli-telemetry\setup.py" />
<Compile Include="azure-cli-telemetry\setup.py" />
<Content Include="azure-cli-telemetry\pyproject.toml" />
<Compile Include="azure-cli-testsdk\azure\cli\testsdk\base.py" />
<Compile Include="azure-cli-testsdk\azure\cli\testsdk\checkers.py" />
<Compile Include="azure-cli-testsdk\azure\cli\testsdk\decorators.py" />
Expand All @@ -129,7 +128,7 @@
<Compile Include="azure-cli-testsdk\azure\cli\testsdk\reverse_dependency.py" />
<Compile Include="azure-cli-testsdk\azure\cli\testsdk\utilities.py" />
<Compile Include="azure-cli-testsdk\azure\cli\testsdk\__init__.py" />
<Compile Include="azure-cli-testsdk\setup.py" />
<Content Include="azure-cli-testsdk\pyproject.toml" />
<Compile Include="azure-cli\azure\cli\command_modules\acr\build.py" />
<Compile Include="azure-cli\azure\cli\command_modules\acr\check_health.py" />
<Compile Include="azure-cli\azure\cli\command_modules\acr\commands.py" />
Expand Down Expand Up @@ -963,7 +962,7 @@
<Compile Include="azure-cli\azure\cli\__init__.py" />
<Compile Include="azure-cli\azure\cli\__main__.py" />
<Compile Include="azure-cli\azure\__init__.py" />
<Compile Include="azure-cli\setup.py" />
<Content Include="azure-cli\pyproject.toml" />
</ItemGroup>
<ItemGroup>
<Folder Include="azure-cli-core\" />
Expand Down
26 changes: 24 additions & 2 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,28 @@ jobs:
pythonVersion: '$(python.version)'
module: 'azure-cli-core'

- job: TestPackagingMetadata
displayName: Test Packaging Metadata
timeoutInMinutes: 10
pool:
name: ${{ variables.ubuntu_pool }}
strategy:
matrix:
Python310:
python.version: '3.10'
Python314:
python.version: '3.14'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(python.version)'
- bash: |
set -e
python -m pip install docutils requests packaging tomli
python -B -m unittest discover -s tools/automation/tests -t tools -p 'test_*.py' -v
python -B scripts/ci/check_package_versions.py
displayName: Validate source metadata and packaging automation

- job: TestTelemetry
displayName: Unit Test for Telemetry
timeoutInMinutes: 10
Expand Down Expand Up @@ -577,7 +599,7 @@ jobs:
displayName: 'Use Python $(python.version)'
inputs:
versionSpec: '$(python.version)'
- bash: pip install --upgrade pip wheel build "setuptools<81"
- bash: pip install --upgrade pip wheel build "setuptools>=78.1.1"
displayName: 'Install pip and wheel'
- bash: ./scripts/ci/test_profile_integration.sh
displayName: 'Run Integration Test against Profiles'
Expand All @@ -598,7 +620,7 @@ jobs:
displayName: 'Use Python $(python.version)'
inputs:
versionSpec: '$(python.version)'
- bash: pip install --upgrade pip wheel build "setuptools<81"
- bash: pip install --upgrade pip wheel build "setuptools>=78.1.1"
displayName: 'Install pip and wheel setuptools'
- bash: ./scripts/ci/test_extensions.sh
displayName: 'Load extensions'
Expand Down
6 changes: 2 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# basic
# Cap setuptools<81: newer setuptools breaks the CLI's setup.py-based builds
# (81 removes setup.py --dry-run and changes distutils command signatures;
# 82 removes pkg_resources). Pin to the last 80.x line.
setuptools>=65.5.1,<81
# Security floor for setuptools; CLI packages use the PEP 517 build backend.
setuptools>=78.1.1
pip>=9.0.1
120 changes: 67 additions & 53 deletions scripts/ci/automation_full_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import json
import logging
import os
from pathlib import Path
import subprocess
import sys
import time
Expand Down Expand Up @@ -423,60 +424,73 @@ def build_pipeline_result():
return pipeline_result


def get_pipeline_result(test_result_fp, pipeline_result):
tree = ET.parse(test_result_fp)
root = tree.getroot()
for testsuite in root:
for testcase in testsuite:
# ['azure', 'cli', 'command_modules', 'network', 'tests', 'latest', 'test_network_commands', 'NetworkNicScenarioTest']
# ['src', 'azure-cli', 'azure', 'cli', 'command_modules', 'network', 'tests', 'hybrid_2018_03_01', 'test_dns_commands', 'DnsZoneImportTest']
# ['src', 'azure-cli-core', 'azure', 'cli', 'core', 'tests', 'test_aaz_arg', 'TestAAZArg']
# ['src', 'azure-cli-telemetry', 'azure', 'cli', 'telemetry', 'tests', 'test_records_collection', 'TestRecordsCollection']
class_name = testcase.attrib['classname'].split('.')
# classname="azure.cli.command_modules.network.tests"
if class_name[2] == 'command_modules':
module = class_name[3]
# classname="azure.cli.core.tests"
# classname="azure.cli.telemetry.tests"
elif class_name[2] in ['core', 'telemetry']:
module = class_name[2]
# classname="src.azure-cli.azure.cli.command_modules.network.tests"
elif class_name[4] == 'command_modules':
module = class_name[5]
# classname="src.azure-cli-core.azure.cli.core.tests"
# classname="src.azure-cli-telemetry.azure.cli.telemetry.tests"
elif class_name[1] in ['azure-cli-core', 'azure-cli-telemetry']:
module = class_name[4]
else:
logger.error(f'unexpected class name: {class_name}')
module = 'unknown'
failures = testcase.findall('failure')
if failures:
# logger.info(f"failed testcase attributes: {testcase.attrib}")
state = "Failed"
test_case = testcase.attrib['name']
line = testcase.attrib['file'] + ':' + testcase.attrib['line']
# only get first failure
for failure in failures:
message = failure.attrib['message'].replace('\n', '<br>').replace(' ', '&nbsp;')
break
for i in pipeline_result[unique_job_name]['Details'][0]['Details'][0]['Details'][0]['Details']:
if i['Module'] == module:
i['Status'] = 'Failed'
# GitHub has a comment length limit of 65535, we must ensure that the length is less than 65535.
# The azure cli bot will also add extra html characters.
# So the number of characters cannot be accurately calculated.
# Using indent=4 is just a rough estimate.
if len(json.dumps(pipeline_result, indent=4)) + len(message) > 65535:
message = 'The error message is too long, please check the pipeline log for details.'
i['Content'] = build_markdown_content(state, test_case, message, line, i['Content'])
break
else:
for i in pipeline_result[unique_job_name]['Details'][0]['Details'][0]['Details'][0]['Details']:
if i['Module'] == module:
i['Status'] = 'Succeeded' if i['Status'] != 'Failed' else 'Failed'
break
def _module_from_test_path(value):
# Accept dotted JUnit classnames and source paths, with or without a src prefix.
parts = value.replace('\\', '.').replace('/', '.').split('.')
for index in range(len(parts) - 2):
if parts[index:index + 2] == ['azure', 'cli']:
module = parts[index + 2]
if module == 'command_modules' and index + 3 < len(parts):
return parts[index + 3]
if module in ('core', 'telemetry'):
return module
for package in ('azure-cli-core', 'azure-cli-telemetry'):
if package in parts:
return package.removeprefix('azure-cli-')
return None


def _test_file_modules():
# A pyproject root can shorten classnames to test_foo.TestClass. Resolve
# those from source instead of hard-coding filenames or guessing a module.
root = Path(working_directory) if working_directory else Path(__file__).resolve().parents[2]
modules = {}
for path in root.glob('src/azure-cli*/azure/cli/**/test_*.py'):
if 'tests' not in path.parts:
continue
module = _module_from_test_path(path.as_posix())
if module:
modules.setdefault(path.stem, set()).add(module)
return modules


def get_pipeline_result(test_result_fp, pipeline_result):
root = ET.parse(test_result_fp).getroot()
details = pipeline_result[unique_job_name]['Details'][0]['Details'][0]['Details'][0]['Details']
test_modules = None
for testcase in root.iter('testcase'):
classname = testcase.get('classname', '')
filename = testcase.get('file', '')
module = _module_from_test_path(classname) or _module_from_test_path(filename)
if module is None:
if test_modules is None:
test_modules = _test_file_modules()
test_file = Path(filename.replace('\\', '/')).stem if filename else next(
(part for part in classname.split('.') if part.startswith('test_')), '')
matches = test_modules.get(test_file, set())
module = next(iter(matches)) if len(matches) == 1 else 'unknown'
if module == 'unknown':
logger.warning('Unable to identify test module: classname=%r file=%r', classname, filename)
result = next((item for item in details if item['Module'] == module), None)
if result is None:
result = {'Module': module, 'Status': 'Running', 'Content': ''}
details.append(result)
failures = testcase.findall('failure') + testcase.findall('error')
if failures:
result['Status'] = 'Failed'
test_case = testcase.get('name', classname)
line = filename or classname
if testcase.get('line'):
line += ':' + testcase.get('line')
failure = failures[0]
message = (failure.get('message') or failure.text or 'See pipeline log for details.')
message = message.replace('\n', '<br>').replace(' ', '&nbsp;')
# Leave room for the extra HTML added by the GitHub comment bot.
if len(json.dumps(pipeline_result, indent=4)) + len(message) > 65535:
message = 'The error message is too long, please check the pipeline log for details.'
result['Content'] = build_markdown_content('Failed', test_case, message, line, result['Content'])
elif result['Status'] != 'Failed':
result['Status'] = 'Succeeded'
return pipeline_result


Expand Down
10 changes: 6 additions & 4 deletions scripts/ci/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,15 @@ title 'Build Azure CLI and its command modules'
# provisioning the frontend is easy to miss. Install it here if it is absent rather
# than failing partway through the build.
python -c 'import build' 2>/dev/null || python -m pip install --disable-pip-version-check -q build
python -c 'import tomllib' 2>/dev/null || python -m pip install --disable-pip-version-check -q 'tomli>=2.0.1'
python "$script_dir/check_package_versions.py"

for setup_file in $(find src -name 'setup.py'); do
pushd $(dirname ${setup_file}) >/dev/null
for pyproject_file in $(find src -name 'pyproject.toml'); do
pushd $(dirname ${pyproject_file}) >/dev/null
echo "Building module at $(pwd) ..."
# --no-isolation builds against the environment prepared by the caller rather than
# provisioning a fresh one, which keeps the caller's setuptools pin in force and
# avoids requiring outbound network access inside the packaging test containers.
# provisioning a fresh one. The caller must provide the build requirements;
# this avoids fetching them inside the packaging test containers.
python -m build --wheel --no-isolation --outdir $output_dir
python -m build --sdist --no-isolation --outdir $sdist_dir
popd >/dev/null
Expand Down
32 changes: 32 additions & 0 deletions scripts/ci/check_package_versions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

"""Validate source versions before building release or nightly distributions."""

from pathlib import Path
import sys

ROOT = Path(__file__).resolve().parents[2]
PACKAGES = ('azure-cli', 'azure-cli-core', 'azure-cli-telemetry', 'azure-cli-testsdk')
sys.path.insert(0, str(ROOT / 'tools'))

from automation.utilities.packaging import get_package_version, tomllib # noqa: E402


def main():
versions = {}
for name in PACKAGES:
versions[name] = get_package_version(ROOT / 'src' / name)
with (ROOT / 'src/azure-cli/pyproject.toml').open('rb') as stream:
dependencies = tomllib.load(stream)['project']['dependencies']
expected = 'azure-cli-core==' + versions['azure-cli-core']
if expected not in dependencies or versions['azure-cli'] != versions['azure-cli-core']:
raise ValueError('CLI and core versions must match, including the dependency: ' + expected)
for name, version in versions.items():
print('{}: {}'.format(name, version))


if __name__ == '__main__':
main()
Loading
Loading