Skip to content
Draft
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .azure-pipelines/templates/automation_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ steps:
displayName: "Use Python ${{ parameters.pythonVersion }}"
- template: ./azdev_setup.yml
parameters:
EnableCompactAAZ: true
EnableCompactAAZ: false
- bash: |
set -ev

Expand Down
2 changes: 0 additions & 2 deletions build_scripts/windows/scripts/build.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,6 @@ if %errorlevel% neq 0 goto ERROR


pushd %BUILDING_DIR%
%BUILDING_DIR%\python.exe -I %REPO_ROOT%\scripts\compact_aaz.py
if %errorlevel% neq 0 goto ERROR
%BUILDING_DIR%\python.exe -I %REPO_ROOT%\scripts\trim_sdk.py
if %errorlevel% neq 0 goto ERROR
popd
Expand Down
9 changes: 0 additions & 9 deletions scripts/live_test/CLITest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -577,9 +577,6 @@ stages:
# This helps detect issues in CI if a used SDK API version is deleted by the below script.
python $workDir/s/scripts/trim_sdk.py

# Compact aaz folders of modules
python $workDir/s/scripts/compact_aaz.py

az -v
az account set -s $(azure-cli-live-test-bami-sub-id)

Expand Down Expand Up @@ -1155,9 +1152,6 @@ stages:
# This helps detect issues in CI if a used SDK API version is deleted by the below script.
python $workDir/s/scripts/trim_sdk.py

# Compact aaz folders of modules
python $workDir/s/scripts/compact_aaz.py

az -v
az account set -s $(azure-cli-live-test-bami-sub-id)

Expand Down Expand Up @@ -1731,9 +1725,6 @@ stages:
# This helps detect issues in CI if a used SDK API version is deleted by the below script.
python $workDir/s/scripts/trim_sdk.py

# Compact aaz folders of modules
python $workDir/s/scripts/compact_aaz.py

az -v
az account set -s $(azure-cli-live-test-bami-sub-id)

Expand Down
3 changes: 2 additions & 1 deletion src/azure-cli-core/azure/cli/core/aaz/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from azure.core.polling.base_polling import LocationPolling, StatusCheckPolling
from abc import abstractmethod

from ._poller import AAZNoPolling, AAZBasePolling
from azure.cli.core.cloud import (CloudEndpointNotSetException, CloudSuffixNotSetException,
CloudNameEnum as _CloudNameEnum)

Expand Down Expand Up @@ -111,6 +110,7 @@ def send_request(self, request, stream=False, **kwargs): # pylint: disable=argu
def build_lro_polling(self, no_wait, initial_session, deserialization_callback, error_callback,
lro_options=None, path_format_arguments=None):
from azure.core.polling.base_polling import OperationResourcePolling
from ._poller import AAZNoPolling, AAZBasePolling
if no_wait == True: # noqa: E712, pylint: disable=singleton-comparison
polling = AAZNoPolling()
else:
Expand Down Expand Up @@ -233,6 +233,7 @@ def _build_per_call_policies(cls, ctx, **kwargs):
def build_lro_polling(self, no_wait, initial_session, deserialization_callback, error_callback,
lro_options=None, path_format_arguments=None):
from azure.mgmt.core.polling.arm_polling import AzureAsyncOperationPolling, BodyContentPolling
from ._poller import AAZNoPolling, AAZBasePolling
if no_wait == True: # noqa: E712, pylint: disable=singleton-comparison
polling = AAZNoPolling()
else:
Expand Down
102 changes: 94 additions & 8 deletions src/azure-cli-core/azure/cli/core/aaz/_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
from ._base import AAZUndefined, AAZBaseValue
from ._field_type import AAZObjectType
from ._paging import AAZPaged
from ._poller import AAZLROPoller
from ._command_ctx import AAZCommandCtx
from .exceptions import AAZUnknownFieldError, AAZUnregisteredArg
from .utils import get_aaz_profile_module_name
Expand Down Expand Up @@ -235,6 +234,7 @@ def processor(schema, result):
def build_lro_poller(self, executor, extract_result):
""" Build AAZLROPoller instance to support long running operation
"""
from ._poller import AAZLROPoller
polling_generator = executor()
if self.ctx.lro_no_wait:
# run until yield the first polling
Expand Down Expand Up @@ -389,20 +389,20 @@ def decorator(cls):

def load_aaz_command_table(loader, aaz_pkg_name, args):
""" This function is used in AzCommandsLoader.load_command_table.
It will load commands in module's aaz package.
It will load commands in module's aaz package using file-path based navigation.
"""
profile_pkg = _get_profile_pkg(aaz_pkg_name, loader.cli_ctx.cloud)

command_table = {}
command_group_table = {}
if args is None:
arg_str = ''
fully_load = True
if args is None or os.environ.get(AAZ_PACKAGE_FULL_LOAD_ENV_NAME, 'False').lower() == 'true':
effective_args = None # fully load
else:
arg_str = ' '.join(args).lower() # Sometimes args may contain capital letters.
fully_load = os.environ.get(AAZ_PACKAGE_FULL_LOAD_ENV_NAME, 'False').lower() == 'true' # disable cut logic
effective_args = list(args)
if profile_pkg is not None:
_load_aaz_pkg(loader, profile_pkg, command_table, command_group_table, arg_str, fully_load)
base_path = os.path.dirname(profile_pkg.__file__)
_load_aaz_by_path(loader, base_path, profile_pkg.__name__, effective_args,
command_table, command_group_table)

for group_name, command_group in command_group_table.items():
loader.command_group_table[group_name] = command_group
Expand Down Expand Up @@ -439,6 +439,92 @@ def _wrapper(cls):
return _wrapper


def _try_import_module(relative_name, package):
"""Try to import a module by relative name, return None on failure."""
try:
return importlib.import_module(relative_name, package)
except (ModuleNotFoundError, ImportError):
# Comment the debug log because it may cause confusion
# commands from different modules may patch each other.
# logger.debug('Failed to import module %s relative to %s.', relative_name, package)
return None


def _register_from_module(loader, mod, command_table, command_group_table):
"""Scan a module's namespace for AAZCommand/AAZCommandGroup classes and register them."""
for value in mod.__dict__.values():
if not isinstance(value, type):
continue
if issubclass(value, AAZCommandGroup) and value.AZ_NAME:
command_group_table[value.AZ_NAME] = value(cli_ctx=loader.cli_ctx)
elif issubclass(value, AAZCommand) and value.AZ_NAME:
command_table[value.AZ_NAME] = value(loader=loader)


def _load_aaz_by_path(loader, base_path, base_module, args, command_table, command_group_table):
"""Recursively navigate the AAZ package tree guided by CLI args.

- args is None or empty → full recursive load of all commands under this directory.
- args has items → try to match first arg as a command file or sub-directory,
recurse with remaining args on match.
- args exhausted / no match → load current level's commands and sub-group headers.

:param base_path: Filesystem path of the current package directory.
:param base_module: Dotted module name of the current package.
:param args: Remaining CLI args (list of str), or None for full load.
"""
if not os.path.isdir(base_path):
return

if args is not None and args and not args[0].startswith('-'):
first_arg = args[0].lower().replace('-', '_')

# First arg matches a command file (e.g. "create" → "_create.py")
cmd_file = os.path.join(base_path, f"_{first_arg}.py")
if os.path.isfile(cmd_file):
mod = _try_import_module(f"._{first_arg}", base_module)
if mod:
_register_from_module(loader, mod, command_table, command_group_table)
return

# First arg matches a sub-directory (command group)
sub_dir = os.path.join(base_path, first_arg)
if os.path.isdir(sub_dir):
sub_module = f"{base_module}.{first_arg}"
mod = _try_import_module('.__cmd_group', sub_module)
if mod:
_register_from_module(loader, mod, command_table, command_group_table)
_load_aaz_by_path(loader, sub_dir, sub_module, args[1:], command_table, command_group_table)
return

# Load __cmd_group + all command files at this level
mod = _try_import_module('.__cmd_group', base_module)
if mod:
_register_from_module(loader, mod, command_table, command_group_table)

for entry in os.listdir(base_path):
entry_path = os.path.join(base_path, entry)

# Command files: _create.py, _list.py, etc.
if (entry.startswith('_') and not entry.startswith('__') and
entry.endswith('.py') and os.path.isfile(entry_path)):
mod = _try_import_module(f'.{entry[:-3]}', base_module)
if mod:
_register_from_module(loader, mod, command_table, command_group_table)

# Sub-directories
elif not entry.startswith('_') and os.path.isdir(entry_path):
sub_module = f"{base_module}.{entry}"
if not args:
# Full load → recurse into every sub-directory
_load_aaz_by_path(loader, entry_path, sub_module, None, command_table, command_group_table)
else:
# Args exhausted / not matched → only load sub-group headers for help listing
mod = _try_import_module('.__cmd_group', sub_module)
if mod:
_register_from_module(loader, mod, command_table, command_group_table)


def _load_aaz_pkg(loader, pkg, parent_command_table, command_group_table, arg_str, fully_load):
""" Load aaz commands and aaz command groups under a package folder.
"""
Expand Down
3 changes: 0 additions & 3 deletions src/azure-cli-core/azure/cli/core/aaz/_poller.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
from azure.core.polling.base_polling import LROBasePolling
from azure.core.tracing.common import with_current_context
from azure.core.tracing.decorator import distributed_trace
# import requests in main thread to resolve import deadlock between threads in python
# reference https://github.com/psf/requests/issues/2925 and https://github.com/Azure/azure-cli/issues/26272
import requests # pylint: disable=unused-import

_LOGGER = logging.getLogger(__name__)

Expand Down
10 changes: 10 additions & 0 deletions src/azure-cli-core/azure/cli/core/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,16 @@ def load_command_table(self, command_loader):
_argument_validators=argument_validators,
_parser=command_parser)

# Ensure subparsers are created for all registered command groups, even
# those that have no commands in the (possibly truncated) command table.
# Without this, empty command groups would not appear in help output.
for group_name in grp_tbl:
# _get_subparser creates intermediate subparsers for path[:1] through
# path[:len-1]. Append a sentinel so the full group path is treated
# as an intermediate level and its subparser is created.
path = group_name.split() + ['_placeholder']
self._get_subparser(path, grp_tbl)

def validation_error(self, message):
az_error = ValidationError(message)
az_error.print_error()
Expand Down
10 changes: 10 additions & 0 deletions src/azure-cli/azure/cli/command_modules/network/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ def load_command_table(self, args):
aaz_pkg_name=aaz.__name__,
args=args
)
try:
from . import operations
except ImportError:
operations = None
if operations:
load_aaz_command_table(
loader=self,
aaz_pkg_name=operations.__name__,
args=args
)
load_command_table(self, args)
return self.command_table

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# pylint: disable=inconsistent-return-statements
@Completer
def subnet_completion_list(cmd, prefix, namespace, **kwargs): # pylint: disable=unused-argument
from .aaz.latest.network.vnet.subnet import List
from .aaz.latest.network.vnet.subnet._list import List
if namespace.resource_group_name and namespace.virtual_network_name:
rg = namespace.resource_group_name
vnet = namespace.virtual_network_name
Expand All @@ -27,7 +27,7 @@ def get_lb_subresource_completion_list(prop):
# pylint: disable=inconsistent-return-statements
@Completer
def completer(cmd, prefix, namespace, **kwargs): # pylint: disable=unused-argument
from .aaz.latest.network.lb import Show
from .aaz.latest.network.lb._show import Show
try:
lb_name = namespace.load_balancer_name
except AttributeError:
Expand All @@ -46,7 +46,7 @@ def get_ag_subresource_completion_list(prop):
# pylint: disable=inconsistent-return-statements
@Completer
def completer(cmd, prefix, namespace, **kwargs): # pylint: disable=unused-argument
from .aaz.latest.network.application_gateway import Show
from .aaz.latest.network.application_gateway._show import Show
try:
ag_name = namespace.application_gateway_name
except AttributeError:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ def get_network_watcher_from_location(remove=False, watcher_name='watcher_name',
rg_name='watcher_rg'):
def _validator(cmd, namespace):
from azure.mgmt.core.tools import parse_resource_id
from .aaz.latest.network.watcher import List
from .aaz.latest.network.watcher._list import List

location = namespace.location
watcher_list = List(cli_ctx=cmd.cli_ctx)(command_args={})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._list_service_aliases import *
from ._list_service_tags import *
from ._list_usages import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._create import *
from ._delete import *
from ._health import *
from ._health_on_demand import *
from ._list import *
from ._show import *
from ._start import *
from ._stop import *
from ._update import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._create import *
from ._delete import *
from ._list import *
from ._show import *
from ._update import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._create import *
from ._delete import *
from ._list import *
from ._show import *
from ._update import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._add import *
from ._list import *
from ._remove import *
from ._show import *
from ._update import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._create import *
from ._delete import *
from ._list import *
from ._show import *
from ._update import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._create import *
from ._delete import *
from ._list import *
from ._show import *
from ._update import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._create import *
from ._delete import *
from ._list import *
from ._show import *
from ._update import *
from ._wait import *
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,3 @@

# pylint: skip-file
# flake8: noqa

from .__cmd_group import *
from ._create import *
from ._delete import *
from ._list import *
from ._show import *
from ._update import *
from ._wait import *
Loading
Loading