diff --git a/src/azure-cli-core/azure/cli/core/__init__.py b/src/azure-cli-core/azure/cli/core/__init__.py index 8e2e1b42fe1..b57f5a79ab4 100644 --- a/src/azure-cli-core/azure/cli/core/__init__.py +++ b/src/azure-cli-core/azure/cli/core/__init__.py @@ -18,6 +18,7 @@ from knack.introspection import extract_args_from_signature, extract_full_summary_from_signature from knack.log import get_logger from knack.preview import PreviewItem +from knack.experimental import ExperimentalItem from knack.util import CLIError from knack.arguments import ArgumentsContext, CaseInsensitiveList # pylint: disable=unused-import @@ -160,7 +161,7 @@ def _update_command_table_from_modules(args): # Changing this error message requires updating CI script that checks for failed # module loading. import azure.cli.core.telemetry as telemetry - logger.error("Error loading command module '%s'", mod) + logger.error("Error loading command module '%s': %s", mod, ex) telemetry.set_exception(exception=ex, fault_type='module-load-error-' + mod, summary='Error loading module: {}'.format(mod)) logger.debug(traceback.format_exc()) @@ -450,6 +451,11 @@ def command_group(self, group_name, command_type=None, **kwargs): target=group_name, object_type='command group' ) + if kwargs.get('is_experimental', False): + kwargs['experimental_info'] = ExperimentalItem( + target=group_name, + object_type='command group' + ) return self._command_group_cls(self, group_name, **kwargs) def argument_context(self, scope, **kwargs): diff --git a/src/azure-cli-core/azure/cli/core/_help.py b/src/azure-cli-core/azure/cli/core/_help.py index 3b960021b0a..c5814460e76 100644 --- a/src/azure-cli-core/azure/cli/core/_help.py +++ b/src/azure-cli-core/azure/cli/core/_help.py @@ -290,6 +290,7 @@ def __init__(self, help_ctx, delimiters, parser): 'name_source': [action.metavar or action.dest], 'deprecate_info': getattr(action, 'deprecate_info', None), 'preview_info': getattr(action, 'preview_info', None), + 'experimental_info': getattr(action, 'experimental_info', None), 'description': action.help, 'choices': action.choices, 'required': False, diff --git a/src/azure-cli-core/azure/cli/core/commands/__init__.py b/src/azure-cli-core/azure/cli/core/commands/__init__.py index 8cb3ebb1be3..0e240dbbebf 100644 --- a/src/azure-cli-core/azure/cli/core/commands/__init__.py +++ b/src/azure-cli-core/azure/cli/core/commands/__init__.py @@ -30,10 +30,11 @@ import azure.cli.core.telemetry as telemetry from knack.arguments import CLICommandArgument -from knack.commands import CLICommand, CommandGroup +from knack.commands import CLICommand, CommandGroup, PREVIEW_EXPERIMENTAL_CONFLICT_ERROR from knack.deprecation import ImplicitDeprecated, resolve_deprecate_info from knack.invocation import CommandInvoker from knack.preview import ImplicitPreviewItem, PreviewItem, resolve_preview_info +from knack.experimental import ImplicitExperimentalItem, ExperimentalItem, resolve_experimental_info from knack.log import get_logger from knack.util import CLIError, CommandResultItem, todict from knack.events import EVENT_INVOKER_TRANSFORM_RESULT @@ -722,11 +723,31 @@ def _resolve_preview_and_deprecation_warnings(self, cmd, parsed_args): del preview_kwargs['_get_message'] previews.append(ImplicitPreviewItem(**preview_kwargs)) + experimentals = [] + getattr(parsed_args, '_argument_experimentals', []) + if cmd.experimental_info: + experimentals.append(cmd.experimental_info) + else: + # search for implicit command experimental status + path_comps = cmd.name.split()[:-1] + implicit_experimental_info = None + while path_comps and not implicit_experimental_info: + implicit_experimental_info = resolve_experimental_info(self.cli_ctx, ' '.join(path_comps)) + del path_comps[-1] + + if implicit_experimental_info: + experimental_kwargs = implicit_experimental_info.__dict__.copy() + experimental_kwargs['object_type'] = 'command' + del experimental_kwargs['_get_tag'] + del experimental_kwargs['_get_message'] + experimentals.append(ImplicitExperimentalItem(**experimental_kwargs)) + if not self.cli_ctx.only_show_errors: for d in deprecations: print(d.message, file=sys.stderr) for p in previews: print(p.message, file=sys.stderr) + for e in experimentals: + print(e.message, file=sys.stderr) def _resolve_extension_override_warning(self, cmd): # pylint: disable=no-self-use if isinstance(cmd.command_source, ExtensionCommandSource) and cmd.command_source.overrides_command: @@ -1147,11 +1168,8 @@ def custom_command(self, name, method_name=None, **kwargs): def _command(self, name, method_name, custom_command=False, **kwargs): self._check_stale() merged_kwargs = self._flatten_kwargs(kwargs, get_command_type_kwarg(custom_command)) - # don't inherit deprecation or preview info from command group - merged_kwargs['deprecate_info'] = kwargs.get('deprecate_info', None) - merged_kwargs['preview_info'] = None - if kwargs.get('is_preview', False): - merged_kwargs['preview_info'] = PreviewItem(self.command_loader.cli_ctx, object_type='command') + self._apply_tags(merged_kwargs, kwargs, name) + operations_tmpl = merged_kwargs['operations_tmpl'] command_name = '{} {}'.format(self.group_name, name) if self.group_name else name self.command_loader._cli_command(command_name, # pylint: disable=protected-access @@ -1191,11 +1209,7 @@ def generic_update_command(self, name, getter_name='get', getter_type=None, self._check_stale() merged_kwargs = self._flatten_kwargs(kwargs, get_command_type_kwarg()) merged_kwargs_custom = self._flatten_kwargs(kwargs, get_command_type_kwarg(custom_command=True)) - # don't inherit deprecation or preview info from command group - merged_kwargs['deprecate_info'] = kwargs.get('deprecate_info', None) - merged_kwargs['preview_info'] = None - if kwargs.get('is_preview', False): - merged_kwargs['preview_info'] = PreviewItem(self.command_loader.cli_ctx, object_type='command') + self._apply_tags(merged_kwargs, kwargs, name) getter_op = self._resolve_operation(merged_kwargs, getter_name, getter_type) setter_op = self._resolve_operation(merged_kwargs, setter_name, setter_type) @@ -1226,11 +1240,7 @@ def _wait_command(self, name, getter_name='get', getter_type=None, custom_comman from azure.cli.core.commands.arm import _cli_wait_command self._check_stale() merged_kwargs = self._flatten_kwargs(kwargs, get_command_type_kwarg(custom_command)) - # don't inherit deprecation or preview info from command group - merged_kwargs['deprecate_info'] = kwargs.get('deprecate_info', None) - merged_kwargs['preview_info'] = None - if kwargs.get('is_preview', False): - merged_kwargs['preview_info'] = PreviewItem(self.command_loader.cli_ctx, object_type='command') + self._apply_tags(merged_kwargs, kwargs, name) if getter_type: merged_kwargs = _merge_kwargs(getter_type.settings, merged_kwargs, CLI_COMMAND_KWARGS) @@ -1248,11 +1258,7 @@ def _show_command(self, name, getter_name='get', getter_type=None, custom_comman from azure.cli.core.commands.arm import _cli_show_command self._check_stale() merged_kwargs = self._flatten_kwargs(kwargs, get_command_type_kwarg(custom_command)) - # don't inherit deprecation or preview info from command group - merged_kwargs['deprecate_info'] = kwargs.get('deprecate_info', None) - merged_kwargs['preview_info'] = None - if kwargs.get('is_preview', False): - merged_kwargs['preview_info'] = PreviewItem(self.command_loader.cli_ctx, object_type='command') + self._apply_tags(merged_kwargs, kwargs, name) if getter_type: merged_kwargs = _merge_kwargs(getter_type.settings, merged_kwargs, CLI_COMMAND_KWARGS) @@ -1260,6 +1266,22 @@ def _show_command(self, name, getter_name='get', getter_type=None, custom_comman _cli_show_command(self.command_loader, '{} {}'.format(self.group_name, name), getter_op=getter_op, custom_command=custom_command, **merged_kwargs) + def _apply_tags(self, merged_kwargs, kwargs, command_name): + # don't inherit deprecation or preview info from command group + merged_kwargs['deprecate_info'] = kwargs.get('deprecate_info', None) + + # transform is_preview and is_experimental to StatusTags + merged_kwargs['preview_info'] = None + merged_kwargs['experimental_info'] = None + is_preview = kwargs.get('is_preview', False) + is_experimental = kwargs.get('is_experimental', False) + if is_preview and is_experimental: + raise CLIError(PREVIEW_EXPERIMENTAL_CONFLICT_ERROR.format("command", self.group_name + " " + command_name)) + if is_preview: + merged_kwargs['preview_info'] = PreviewItem(self.command_loader.cli_ctx,object_type='command') + if is_experimental: + merged_kwargs['experimental_info'] = ExperimentalItem(self.command_loader.cli_ctx,object_type='command') + def register_cache_arguments(cli_ctx): from knack import events diff --git a/src/azure-cli-core/azure/cli/core/commands/arm.py b/src/azure-cli-core/azure/cli/core/commands/arm.py index 59ffd823b48..336d7a9b304 100644 --- a/src/azure-cli-core/azure/cli/core/commands/arm.py +++ b/src/azure-cli-core/azure/cli/core/commands/arm.py @@ -224,6 +224,7 @@ def add_ids_arguments(_, **kwargs): # pylint: disable=unused-argument 'dest': 'ids' if id_arg else '_ids', 'deprecate_info': deprecate_info, 'is_preview': id_arg.settings.get('is_preview', None) if id_arg else None, + 'is_experimental': id_arg.settings.get('is_experimental', None) if id_arg else None, 'nargs': '+', 'arg_group': group_name } diff --git a/src/azure-cli-core/azure/cli/core/commands/constants.py b/src/azure-cli-core/azure/cli/core/commands/constants.py index 19c6cedb4be..eac0ef2f409 100644 --- a/src/azure-cli-core/azure/cli/core/commands/constants.py +++ b/src/azure-cli-core/azure/cli/core/commands/constants.py @@ -8,7 +8,8 @@ CLI_COMMON_KWARGS = ['min_api', 'max_api', 'resource_type', 'operation_group', - 'custom_command_type', 'command_type', 'is_preview', 'preview_info'] + 'custom_command_type', 'command_type', 'is_preview', 'preview_info', + 'is_experimental', 'experimental_info'] CLI_COMMAND_KWARGS = ['transform', 'table_transformer', 'confirmation', 'exception_handler', 'client_factory', 'operations_tmpl', 'no_wait_param', 'supports_no_wait', 'validator', diff --git a/src/azure-cli-core/azure/cli/core/parser.py b/src/azure-cli-core/azure/cli/core/parser.py index c93530d373b..83615ab86b5 100644 --- a/src/azure-cli-core/azure/cli/core/parser.py +++ b/src/azure-cli-core/azure/cli/core/parser.py @@ -119,6 +119,7 @@ def load_command_table(self, command_loader): param.completer = arg.completer param.deprecate_info = arg.deprecate_info param.preview_info = arg.preview_info + param.experimental_info = arg.experimental_info command_parser.set_defaults( func=metadata, command=command_name,