def format_help(self): telemetry.set_command_details( command=self.prog[3:], extension_name=self.command_source.extension_name if self.command_source else None) telemetry.set_success(summary='show help') super(AzCliCommandParser, self).format_help()
def format_help(self): is_group = self.is_group() telemetry.set_command_details(command=self.prog[3:]) telemetry.set_success(summary='show help') _help.show_help(self.prog.split()[1:], self._actions[-1] if is_group else self, is_group) self.exit()
def format_help(self): is_group = self.is_group() telemetry.set_command_details(command=self.prog[3:]) telemetry.set_success(summary='show help') _help.show_help(self.prog.split()[1:], self._actions[-1] if is_group else self, is_group) self.exit()
def format_help(self): is_group = self.is_group() telemetry.set_command_details( command=self.prog[3:], extension_name=self.command_source.extension_name if self.command_source else None) telemetry.set_success(summary='show help') _help.show_help(self.prog.split()[1:], self._actions[-1] if is_group else self, is_group) self.exit()
def format_help(self): extension_version = None try: if self.command_source: extension_version = get_extension(self.command_source.extension_name).version except Exception: # pylint: disable=broad-except pass telemetry.set_command_details( command=self.prog[3:], extension_name=self.command_source.extension_name if self.command_source else None, extension_version=extension_version) telemetry.set_success(summary='show help') super(AzCliCommandParser, self).format_help()
def format_help(self): extension_version = None try: if self.command_source: extension_version = get_extension(self.command_source.extension_name).version except Exception: # pylint: disable=broad-except pass telemetry.set_command_details( command=self.prog[3:], extension_name=self.command_source.extension_name if self.command_source else None, extension_version=extension_version) telemetry.set_success(summary='show help') super(AzCliCommandParser, self).format_help()
def execute(self, unexpanded_argv): # pylint: disable=too-many-statements self.refresh_request_id() argv = Application._expand_file_prefixed_files(unexpanded_argv) command_table = self.configuration.get_command_table(argv) self.raise_event(self.COMMAND_TABLE_LOADED, command_table=command_table) self.parser.load_command_table(command_table) self.raise_event(self.COMMAND_PARSER_LOADED, parser=self.parser) if not argv: enable_autocomplete(self.parser) az_subparser = self.parser.subparsers[tuple()] _help.show_welcome(az_subparser) # TODO: Question, is this needed? telemetry.set_command_details('az') telemetry.set_success(summary='welcome') return None if argv[0].lower() == 'help': argv[0] = '--help' # Rudimentary parsing to get the command nouns = [] for i, current in enumerate(argv): try: if current[0] == '-': break except IndexError: pass argv[i] = current.lower() nouns.append(argv[i]) command = ' '.join(nouns) if argv[-1] in ('--help', '-h') or command in command_table: self.configuration.load_params(command) self.raise_event(self.COMMAND_TABLE_PARAMS_LOADED, command_table=command_table) self.parser.load_command_table(command_table) if self.session['completer_active']: enable_autocomplete(self.parser) self.raise_event(self.COMMAND_PARSER_PARSING, argv=argv) args = self.parser.parse_args(argv) self.raise_event(self.COMMAND_PARSER_PARSED, command=args.command, args=args) results = [] for expanded_arg in _explode_list_args(args): self.session['command'] = expanded_arg.command try: _validate_arguments(expanded_arg) except CLIError: raise except: # pylint: disable=bare-except err = sys.exc_info()[1] getattr(expanded_arg, '_parser', self.parser).validation_error(str(err)) # Consider - we are using any args that start with an underscore (_) as 'private' # arguments and remove them from the arguments that we pass to the actual function. # This does not feel quite right. params = dict([(key, value) for key, value in expanded_arg.__dict__.items() if not key.startswith('_')]) params.pop('subcommand', None) params.pop('func', None) params.pop('command', None) telemetry.set_command_details(expanded_arg.command, self.configuration.output_format, [p for p in unexpanded_argv if p.startswith('-')]) result = expanded_arg.func(params) result = todict(result) results.append(result) if len(results) == 1: results = results[0] event_data = {'result': results} self.raise_event(self.TRANSFORM_RESULT, event_data=event_data) self.raise_event(self.FILTER_RESULT, event_data=event_data) return CommandResultItem(event_data['result'], table_transformer=command_table[args.command].table_transformer, is_query_active=self.session['query_active'])
def execute(self, args): from knack.events import ( EVENT_INVOKER_PRE_CMD_TBL_CREATE, EVENT_INVOKER_POST_CMD_TBL_CREATE, EVENT_INVOKER_CMD_TBL_LOADED, EVENT_INVOKER_PRE_PARSE_ARGS, EVENT_INVOKER_POST_PARSE_ARGS, EVENT_INVOKER_TRANSFORM_RESULT, EVENT_INVOKER_FILTER_RESULT) from knack.util import CommandResultItem, todict from azure.cli.core.commands.events import EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE # TODO: Can't simply be invoked as an event because args are transformed args = _pre_command_table_create(self.cli_ctx, args) self.cli_ctx.raise_event(EVENT_INVOKER_PRE_CMD_TBL_CREATE, args=args) self.commands_loader.load_command_table(args) self.cli_ctx.raise_event( EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE, load_cmd_tbl_func=self.commands_loader.load_command_table, args=args) command = self._rudimentary_get_command(args) telemetry.set_raw_command_name(command) try: self.commands_loader.command_table = { command: self.commands_loader.command_table[command] } except KeyError: # Trim down the command table to reduce the number of subparsers required to optimize the performance. # # When given a command table like this: # # network application-gateway create # network application-gateway delete # network list-usages # storage account create # storage account list # # input: az # output: network application-gateway create # storage account create # # input: az network # output: network application-gateway create # network list-usages cmd_table = {} group_names = set() for cmd_name, cmd in self.commands_loader.command_table.items(): if command and not cmd_name.startswith(command): continue cmd_stub = cmd_name[len(command):].strip() group_name = cmd_stub.split(' ', 1)[0] if group_name not in group_names: cmd_table[cmd_name] = cmd group_names.add(group_name) self.commands_loader.command_table = cmd_table self.commands_loader.command_table = self.commands_loader.command_table # update with the truncated table self.commands_loader.command_name = command self.commands_loader.load_arguments(command) self.cli_ctx.raise_event(EVENT_INVOKER_POST_CMD_TBL_CREATE, cmd_tbl=self.commands_loader.command_table) self.parser.cli_ctx = self.cli_ctx self.parser.load_command_table(self.commands_loader.command_table) self.cli_ctx.raise_event(EVENT_INVOKER_CMD_TBL_LOADED, cmd_tbl=self.commands_loader.command_table, parser=self.parser) if not args: self.cli_ctx.completion.enable_autocomplete(self.parser) subparser = self.parser.subparsers[tuple()] self.help.show_welcome(subparser) # TODO: No event in base with which to target telemetry.set_command_details('az') telemetry.set_success(summary='welcome') return None if args[0].lower() == 'help': args[0] = '--help' self.cli_ctx.completion.enable_autocomplete(self.parser) self.cli_ctx.raise_event(EVENT_INVOKER_PRE_PARSE_ARGS, args=args) parsed_args = self.parser.parse_args(args) self.cli_ctx.raise_event(EVENT_INVOKER_POST_PARSE_ARGS, command=parsed_args.command, args=parsed_args) # TODO: This fundamentally alters the way Knack.invocation works here. Cannot be customized # with an event. Would need to be customized via inheritance. results = [] for expanded_arg in _explode_list_args(parsed_args): cmd = expanded_arg.func if hasattr(expanded_arg, 'cmd'): expanded_arg.cmd = cmd self.cli_ctx.data['command'] = expanded_arg.command self._validation(expanded_arg) params = self._filter_params(expanded_arg) command_source = self.commands_loader.command_table[ command].command_source extension_version = None try: if command_source: extension_version = get_extension( command_source.extension_name).version except Exception: # pylint: disable=broad-except pass telemetry.set_command_details( self.cli_ctx.data['command'], self.data['output'], [(p.split('=', 1)[0] if p.startswith('--') else p[:2]) for p in args if (p.startswith('-') and len(p) > 1)], extension_name=command_source.extension_name if command_source else None, extension_version=extension_version) if command_source: self.data[ 'command_extension_name'] = command_source.extension_name try: result = cmd(params) if cmd.supports_no_wait and getattr(expanded_arg, 'no_wait', False): result = None elif cmd.no_wait_param and getattr(expanded_arg, cmd.no_wait_param, False): result = None # TODO: Not sure how to make this actually work with the TRANSFORM event... transform_op = cmd.command_kwargs.get('transform', None) if transform_op: result = transform_op(result) if _is_poller(result): result = LongRunningOperation( self.cli_ctx, 'Starting {}'.format(cmd.name))(result) elif _is_paged(result): result = list(result) result = todict(result) event_data = {'result': result} self.cli_ctx.raise_event(EVENT_INVOKER_TRANSFORM_RESULT, event_data=event_data) self.cli_ctx.raise_event(EVENT_INVOKER_FILTER_RESULT, event_data=event_data) result = event_data['result'] results.append(result) except Exception as ex: # pylint: disable=broad-except if cmd.exception_handler: cmd.exception_handler(ex) return None else: six.reraise(*sys.exc_info()) if results and len(results) == 1: results = results[0] return CommandResultItem( results, table_transformer=self.commands_loader.command_table[ parsed_args.command].table_transformer, is_query_active=self.data['query_active'])
def execute(self, unexpanded_argv): # pylint: disable=too-many-statements argv = Application._expand_file_prefixed_files(unexpanded_argv) command_table = self.configuration.get_command_table(argv) self.raise_event(self.COMMAND_TABLE_LOADED, command_table=command_table) self.parser.load_command_table(command_table) self.raise_event(self.COMMAND_PARSER_LOADED, parser=self.parser) if not argv: enable_autocomplete(self.parser) az_subparser = self.parser.subparsers[tuple()] _help.show_welcome(az_subparser) # TODO: Question, is this needed? telemetry.set_command_details('az') telemetry.set_success(summary='welcome') return None if argv[0].lower() == 'help': argv[0] = '--help' # Rudimentary parsing to get the command nouns = [] for i, current in enumerate(argv): try: if current[0] == '-': break except IndexError: pass argv[i] = current.lower() nouns.append(argv[i]) command = ' '.join(nouns) if argv[-1] in ('--help', '-h') or command in command_table: self.configuration.load_params(command) self.raise_event(self.COMMAND_TABLE_PARAMS_LOADED, command_table=command_table) self.parser.load_command_table(command_table) if self.session['completer_active']: enable_autocomplete(self.parser) self.raise_event(self.COMMAND_PARSER_PARSING, argv=argv) args = self.parser.parse_args(argv) self.raise_event(self.COMMAND_PARSER_PARSED, command=args.command, args=args) results = [] for expanded_arg in _explode_list_args(args): self.session['command'] = expanded_arg.command try: _validate_arguments(expanded_arg) except CLIError: raise except: # pylint: disable=bare-except err = sys.exc_info()[1] getattr(expanded_arg, '_parser', self.parser).validation_error(str(err)) # Consider - we are using any args that start with an underscore (_) as 'private' # arguments and remove them from the arguments that we pass to the actual function. # This does not feel quite right. params = dict([(key, value) for key, value in expanded_arg.__dict__.items() if not key.startswith('_')]) params.pop('subcommand', None) params.pop('func', None) params.pop('command', None) telemetry.set_command_details(expanded_arg.command, self.configuration.output_format, [p for p in unexpanded_argv if p.startswith('-')]) result = expanded_arg.func(params) result = todict(result) results.append(result) if len(results) == 1: results = results[0] event_data = {'result': results} self.raise_event(self.TRANSFORM_RESULT, event_data=event_data) self.raise_event(self.FILTER_RESULT, event_data=event_data) return CommandResultItem(event_data['result'], table_transformer=command_table[args.command].table_transformer, is_query_active=self.session['query_active'])
def _check_value(self, action, value): # pylint: disable=too-many-statements, too-many-locals # Override to customize the error message when a argument is not among the available choices # converted value must be one of the choices (if specified) if action.choices is not None and value not in action.choices: # pylint: disable=too-many-nested-blocks # self.cli_ctx is None when self.prog is beyond 'az', such as 'az iot'. # use cli_ctx from cli_help which is not lost. cli_ctx = self.cli_ctx or (self.cli_help.cli_ctx if self.cli_help else None) caused_by_extension_not_installed = False command_name_inferred = self.prog error_msg = None if not self.command_source: candidates = difflib.get_close_matches(value, action.choices, cutoff=0.7) if candidates: # use the most likely candidate to replace the misspelled command args = self.prog.split() + self._raw_arguments args_inferred = [item if item != value else candidates[0] for item in args] command_name_inferred = ' '.join(args_inferred).split('-')[0] use_dynamic_install = self._get_extension_use_dynamic_install_config() if use_dynamic_install != 'no' and not candidates: # Check if the command is from an extension from azure.cli.core.util import roughly_parse_command cmd_list = self.prog.split() + self._raw_arguments command_str = roughly_parse_command(cmd_list[1:]) ext_name = self._search_in_extension_commands(command_str) if ext_name: caused_by_extension_not_installed = True telemetry.set_command_details(command_str, parameters=AzCliCommandInvoker._extract_parameter_names(cmd_list), # pylint: disable=protected-access extension_name=ext_name) run_after_extension_installed = self._get_extension_run_after_dynamic_install_config() if use_dynamic_install == 'yes_without_prompt': logger.warning('The command requires the extension %s. ' 'It will be installed first.', ext_name) go_on = True else: from knack.prompting import prompt_y_n, NoTTYException prompt_msg = 'The command requires the extension {}. ' \ 'Do you want to install it now?'.format(ext_name) if run_after_extension_installed: prompt_msg = '{} The command will continue to run after the extension is installed.' \ .format(prompt_msg) NO_PROMPT_CONFIG_MSG = "Run 'az config set extension.use_dynamic_install=" \ "yes_without_prompt' to allow installing extensions without prompt." try: go_on = prompt_y_n(prompt_msg, default='y') if go_on: logger.warning(NO_PROMPT_CONFIG_MSG) except NoTTYException: logger.warning("The command requires the extension %s.\n " "Unable to prompt for extension install confirmation as no tty " "available. %s", ext_name, NO_PROMPT_CONFIG_MSG) go_on = False if go_on: from azure.cli.core.extension.operations import add_extension add_extension(cli_ctx=cli_ctx, extension_name=ext_name, upgrade=True) if run_after_extension_installed: import subprocess import platform exit_code = subprocess.call(cmd_list, shell=platform.system() == 'Windows') error_msg = ("Extension {} dynamically installed and commands will be " "rerun automatically.").format(ext_name) telemetry.set_user_fault(error_msg) self.exit(exit_code) else: with CommandLoggerContext(logger): error_msg = 'Extension {} installed. Please rerun your command.'.format(ext_name) logger.error(error_msg) telemetry.set_user_fault(error_msg) self.exit(2) else: error_msg = "The command requires the latest version of extension {ext_name}. " \ "To install, run 'az extension add --upgrade -n {ext_name}'.".format(ext_name=ext_name) if not error_msg: # parser has no `command_source`, value is part of command itself error_msg = "'{value}' is misspelled or not recognized by the system.".format(value=value) az_error = CommandNotFoundError(error_msg) else: # `command_source` indicates command values have been parsed, value is an argument parameter = action.option_strings[0] if action.option_strings else action.dest error_msg = "{prog}: '{value}' is not a valid value for '{param}'.".format( prog=self.prog, value=value, param=parameter) candidates = difflib.get_close_matches(value, action.choices, cutoff=0.7) az_error = InvalidArgumentValueError(error_msg) command_arguments = self._get_failure_recovery_arguments(action) if candidates: az_error.set_recommendation("Did you mean '{}' ?".format(candidates[0])) # recommend a command for user recommender = CommandRecommender(*command_arguments, error_msg, cli_ctx) recommender.set_help_examples(self.get_examples(command_name_inferred)) recommended_command = recommender.recommend_a_command() if recommended_command: az_error.set_recommendation("Try this: '{}'".format(recommended_command)) # remind user to check extensions if we can not find a command to recommend if isinstance(az_error, CommandNotFoundError) \ and not az_error.recommendations and self.prog == 'az' \ and use_dynamic_install == 'no': az_error.set_recommendation(EXTENSION_REFERENCE) az_error.set_recommendation(OVERVIEW_REFERENCE.format(command=self.prog)) if not caused_by_extension_not_installed: az_error.print_error() az_error.send_telemetry() self.exit(2)
def execute(self, args): from knack.events import (EVENT_INVOKER_PRE_CMD_TBL_CREATE, EVENT_INVOKER_POST_CMD_TBL_CREATE, EVENT_INVOKER_CMD_TBL_LOADED, EVENT_INVOKER_PRE_PARSE_ARGS, EVENT_INVOKER_POST_PARSE_ARGS, EVENT_INVOKER_TRANSFORM_RESULT, EVENT_INVOKER_FILTER_RESULT) from knack.util import CommandResultItem, todict from azure.cli.core.commands.events import EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE # TODO: Can't simply be invoked as an event because args are transformed args = _pre_command_table_create(self.cli_ctx, args) self.cli_ctx.raise_event(EVENT_INVOKER_PRE_CMD_TBL_CREATE, args=args) self.commands_loader.load_command_table(args) self.cli_ctx.raise_event(EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE, load_cmd_tbl_func=self.commands_loader.load_command_table, args=args) command = self._rudimentary_get_command(args) telemetry.set_raw_command_name(command) try: self.commands_loader.command_table = {command: self.commands_loader.command_table[command]} except KeyError: # Trim down the command table to reduce the number of subparsers required to optimize the performance. # # When given a command table like this: # # network application-gateway create # network application-gateway delete # network list-usages # storage account create # storage account list # # input: az # output: network application-gateway create # storage account create # # input: az network # output: network application-gateway create # network list-usages cmd_table = {} group_names = set() for cmd_name, cmd in self.commands_loader.command_table.items(): if command and not cmd_name.startswith(command): continue cmd_stub = cmd_name[len(command):].strip() group_name = cmd_stub.split(' ', 1)[0] if group_name not in group_names: cmd_table[cmd_name] = cmd group_names.add(group_name) self.commands_loader.command_table = cmd_table self.commands_loader.command_table = self.commands_loader.command_table # update with the truncated table self.commands_loader.command_name = command self.commands_loader.load_arguments(command) self.cli_ctx.raise_event(EVENT_INVOKER_POST_CMD_TBL_CREATE, commands_loader=self.commands_loader) self.parser.cli_ctx = self.cli_ctx self.parser.load_command_table(self.commands_loader) self.cli_ctx.raise_event(EVENT_INVOKER_CMD_TBL_LOADED, cmd_tbl=self.commands_loader.command_table, parser=self.parser) if not args: self.parser.enable_autocomplete() subparser = self.parser.subparsers[tuple()] self.help.show_welcome(subparser) # TODO: No event in base with which to target telemetry.set_command_details('az') telemetry.set_success(summary='welcome') return None if args[0].lower() == 'help': args[0] = '--help' self.parser.enable_autocomplete() self.cli_ctx.raise_event(EVENT_INVOKER_PRE_PARSE_ARGS, args=args) parsed_args = self.parser.parse_args(args) self.cli_ctx.raise_event(EVENT_INVOKER_POST_PARSE_ARGS, command=parsed_args.command, args=parsed_args) # TODO: This fundamentally alters the way Knack.invocation works here. Cannot be customized # with an event. Would need to be customized via inheritance. results = [] for expanded_arg in _explode_list_args(parsed_args): cmd = expanded_arg.func if hasattr(expanded_arg, 'cmd'): expanded_arg.cmd = cmd self.cli_ctx.data['command'] = expanded_arg.command self._validation(expanded_arg) params = self._filter_params(expanded_arg) command_source = self.commands_loader.command_table[command].command_source extension_version = None extension_name = None try: if isinstance(command_source, ExtensionCommandSource): extension_name = command_source.extension_name extension_version = get_extension(command_source.extension_name).version except Exception: # pylint: disable=broad-except pass telemetry.set_command_details(self.cli_ctx.data['command'], self.data['output'], [(p.split('=', 1)[0] if p.startswith('--') else p[:2]) for p in args if (p.startswith('-') and len(p) > 1)], extension_name=extension_name, extension_version=extension_version) if extension_name: self.data['command_extension_name'] = extension_name deprecations = [] + getattr(expanded_arg, '_argument_deprecations', []) if cmd.deprecate_info: deprecations.append(cmd.deprecate_info) # search for implicit deprecation path_comps = cmd.name.split()[:-1] implicit_deprecate_info = None while path_comps and not implicit_deprecate_info: implicit_deprecate_info = resolve_deprecate_info(self.cli_ctx, ' '.join(path_comps)) del path_comps[-1] if implicit_deprecate_info: deprecate_kwargs = implicit_deprecate_info.__dict__.copy() deprecate_kwargs['object_type'] = 'command' del deprecate_kwargs['_get_tag'] del deprecate_kwargs['_get_message'] deprecations.append(ImplicitDeprecated(**deprecate_kwargs)) for d in deprecations: logger.warning(d.message) try: result = cmd(params) if cmd.supports_no_wait and getattr(expanded_arg, 'no_wait', False): result = None elif cmd.no_wait_param and getattr(expanded_arg, cmd.no_wait_param, False): result = None transform_op = cmd.command_kwargs.get('transform', None) if transform_op: result = transform_op(result) if _is_poller(result): result = LongRunningOperation(self.cli_ctx, 'Starting {}'.format(cmd.name))(result) elif _is_paged(result): result = list(result) result = todict(result, AzCliCommandInvoker.remove_additional_prop_layer) event_data = {'result': result} self.cli_ctx.raise_event(EVENT_INVOKER_TRANSFORM_RESULT, event_data=event_data) result = event_data['result'] results.append(result) except Exception as ex: # pylint: disable=broad-except if cmd.exception_handler: cmd.exception_handler(ex) return None else: six.reraise(*sys.exc_info()) if results and len(results) == 1: results = results[0] event_data = {'result': results} self.cli_ctx.raise_event(EVENT_INVOKER_FILTER_RESULT, event_data=event_data) return CommandResultItem( event_data['result'], table_transformer=self.commands_loader.command_table[parsed_args.command].table_transformer, is_query_active=self.data['query_active'])
def execute(self, args): from knack.events import (EVENT_INVOKER_PRE_CMD_TBL_CREATE, EVENT_INVOKER_POST_CMD_TBL_CREATE, EVENT_INVOKER_CMD_TBL_LOADED, EVENT_INVOKER_PRE_PARSE_ARGS, EVENT_INVOKER_POST_PARSE_ARGS, EVENT_INVOKER_FILTER_RESULT) from azure.cli.core.commands.events import EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE # TODO: Can't simply be invoked as an event because args are transformed args = _pre_command_table_create(self.cli_ctx, args) self.cli_ctx.raise_event(EVENT_INVOKER_PRE_CMD_TBL_CREATE, args=args) self.commands_loader.load_command_table(args) self.cli_ctx.raise_event(EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE, load_cmd_tbl_func=self.commands_loader.load_command_table, args=args) command = self._rudimentary_get_command(args) self.cli_ctx.invocation.data['command_string'] = command telemetry.set_raw_command_name(command) try: self.commands_loader.command_table = {command: self.commands_loader.command_table[command]} except KeyError: # Trim down the command table to reduce the number of subparsers required to optimize the performance. # # When given a command table like this: # # network application-gateway create # network application-gateway delete # network list-usages # storage account create # storage account list # # input: az # output: network application-gateway create # storage account create # # input: az network # output: network application-gateway create # network list-usages cmd_table = {} group_names = set() for cmd_name, cmd in self.commands_loader.command_table.items(): if command and not cmd_name.startswith(command): continue cmd_stub = cmd_name[len(command):].strip() group_name = cmd_stub.split(' ', 1)[0] if group_name not in group_names: cmd_table[cmd_name] = cmd group_names.add(group_name) self.commands_loader.command_table = cmd_table self.commands_loader.command_table = self.commands_loader.command_table # update with the truncated table self.commands_loader.command_name = command self.commands_loader.load_arguments(command) self.cli_ctx.raise_event(EVENT_INVOKER_POST_CMD_TBL_CREATE, commands_loader=self.commands_loader) self.parser.cli_ctx = self.cli_ctx self.parser.load_command_table(self.commands_loader) self.cli_ctx.raise_event(EVENT_INVOKER_CMD_TBL_LOADED, cmd_tbl=self.commands_loader.command_table, parser=self.parser) arg_check = [a for a in args if a not in ['--debug', '--verbose']] if not arg_check: self.parser.enable_autocomplete() subparser = self.parser.subparsers[tuple()] self.help.show_welcome(subparser) # TODO: No event in base with which to target telemetry.set_command_details('az') telemetry.set_success(summary='welcome') return CommandResultItem(None, exit_code=0) if args[0].lower() == 'help': args[0] = '--help' self.parser.enable_autocomplete() self.cli_ctx.raise_event(EVENT_INVOKER_PRE_PARSE_ARGS, args=args) parsed_args = self.parser.parse_args(args) self.cli_ctx.raise_event(EVENT_INVOKER_POST_PARSE_ARGS, command=parsed_args.command, args=parsed_args) # TODO: This fundamentally alters the way Knack.invocation works here. Cannot be customized # with an event. Would need to be customized via inheritance. cmd = parsed_args.func self.cli_ctx.data['command'] = parsed_args.command self.cli_ctx.data['safe_params'] = AzCliCommandInvoker._extract_parameter_names(args) command_source = self.commands_loader.command_table[command].command_source extension_version = None extension_name = None try: if isinstance(command_source, ExtensionCommandSource): extension_name = command_source.extension_name extension_version = get_extension(command_source.extension_name).version except Exception: # pylint: disable=broad-except pass telemetry.set_command_details(self.cli_ctx.data['command'], self.data['output'], self.cli_ctx.data['safe_params'], extension_name=extension_name, extension_version=extension_version) if extension_name: self.data['command_extension_name'] = extension_name self.resolve_warnings(cmd, parsed_args) self.resolve_confirmation(cmd, parsed_args) jobs = [] for expanded_arg in _explode_list_args(parsed_args): cmd_copy = copy.copy(cmd) cmd_copy.cli_ctx = copy.copy(cmd.cli_ctx) cmd_copy.cli_ctx.data = copy.deepcopy(cmd.cli_ctx.data) expanded_arg.cmd = expanded_arg._cmd = cmd_copy if hasattr(expanded_arg, '_subscription'): cmd_copy.cli_ctx.data['subscription_id'] = expanded_arg._subscription # pylint: disable=protected-access self._validation(expanded_arg) jobs.append((expanded_arg, cmd_copy)) ids = getattr(parsed_args, '_ids', None) or [None] * len(jobs) if self.cli_ctx.config.getboolean('core', 'disable_concurrent_ids', False) or len(ids) < 2: results, exceptions = self._run_jobs_serially(jobs, ids) else: results, exceptions = self._run_jobs_concurrently(jobs, ids) # handle exceptions if len(exceptions) == 1 and not results: ex, id_arg = exceptions[0] raise ex elif exceptions: for exception, id_arg in exceptions: logger.warning('%s: "%s"', id_arg, str(exception)) if not results: return CommandResultItem(None, exit_code=1, error=CLIError('Encountered more than one exception.')) logger.warning('Encountered more than one exception.') if results and len(results) == 1: results = results[0] event_data = {'result': results} self.cli_ctx.raise_event(EVENT_INVOKER_FILTER_RESULT, event_data=event_data) return CommandResultItem( event_data['result'], table_transformer=self.commands_loader.command_table[parsed_args.command].table_transformer, is_query_active=self.data['query_active'])
def execute(self, args): from knack.events import (EVENT_INVOKER_PRE_CMD_TBL_CREATE, EVENT_INVOKER_POST_CMD_TBL_CREATE, EVENT_INVOKER_CMD_TBL_LOADED, EVENT_INVOKER_PRE_PARSE_ARGS, EVENT_INVOKER_POST_PARSE_ARGS, EVENT_INVOKER_FILTER_RESULT) from azure.cli.core.commands.events import EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE # TODO: Can't simply be invoked as an event because args are transformed args = _pre_command_table_create(self.cli_ctx, args) self.cli_ctx.raise_event(EVENT_INVOKER_PRE_CMD_TBL_CREATE, args=args) self.commands_loader.load_command_table(args) self.cli_ctx.raise_event( EVENT_INVOKER_PRE_CMD_TBL_TRUNCATE, load_cmd_tbl_func=self.commands_loader.load_command_table, args=args) command = self._rudimentary_get_command(args) self.cli_ctx.invocation.data['command_string'] = command telemetry.set_raw_command_name(command) try: self.commands_loader.command_table = { command: self.commands_loader.command_table[command] } except KeyError: # Trim down the command table to reduce the number of subparsers required to optimize the performance. # # When given a command table like this: # # network application-gateway create # network application-gateway delete # network list-usages # storage account create # storage account list # # input: az # output: network application-gateway create # storage account create # # input: az network # output: network application-gateway create # network list-usages cmd_table = {} group_names = set() for cmd_name, cmd in self.commands_loader.command_table.items(): if command and not cmd_name.startswith(command): continue cmd_stub = cmd_name[len(command):].strip() group_name = cmd_stub.split(' ', 1)[0] if group_name not in group_names: cmd_table[cmd_name] = cmd group_names.add(group_name) self.commands_loader.command_table = cmd_table self.commands_loader.command_table = self.commands_loader.command_table # update with the truncated table self.commands_loader.command_name = command self.commands_loader.load_arguments(command) self.cli_ctx.raise_event(EVENT_INVOKER_POST_CMD_TBL_CREATE, commands_loader=self.commands_loader) self.parser.cli_ctx = self.cli_ctx self.parser.load_command_table(self.commands_loader) self.cli_ctx.raise_event(EVENT_INVOKER_CMD_TBL_LOADED, cmd_tbl=self.commands_loader.command_table, parser=self.parser) arg_check = [a for a in args if a not in ['--debug', '--verbose']] if not arg_check: self.parser.enable_autocomplete() subparser = self.parser.subparsers[tuple()] self.help.show_welcome(subparser) # TODO: No event in base with which to target telemetry.set_command_details('az') telemetry.set_success(summary='welcome') return CommandResultItem(None, exit_code=0) if args[0].lower() == 'help': args[0] = '--help' self.parser.enable_autocomplete() self.cli_ctx.raise_event(EVENT_INVOKER_PRE_PARSE_ARGS, args=args) parsed_args = self.parser.parse_args(args) self.cli_ctx.raise_event(EVENT_INVOKER_POST_PARSE_ARGS, command=parsed_args.command, args=parsed_args) # TODO: This fundamentally alters the way Knack.invocation works here. Cannot be customized # with an event. Would need to be customized via inheritance. cmd = parsed_args.func self.cli_ctx.data['command'] = parsed_args.command self.cli_ctx.data[ 'safe_params'] = AzCliCommandInvoker._extract_parameter_names(args) command_source = self.commands_loader.command_table[ command].command_source extension_version = None extension_name = None try: if isinstance(command_source, ExtensionCommandSource): extension_name = command_source.extension_name extension_version = get_extension( command_source.extension_name).version except Exception: # pylint: disable=broad-except pass telemetry.set_command_details(self.cli_ctx.data['command'], self.data['output'], self.cli_ctx.data['safe_params'], extension_name=extension_name, extension_version=extension_version) if extension_name: self.data['command_extension_name'] = extension_name self.resolve_warnings(cmd, parsed_args) self.resolve_confirmation(cmd, parsed_args) jobs = [] for expanded_arg in _explode_list_args(parsed_args): cmd_copy = copy.copy(cmd) cmd_copy.cli_ctx = copy.copy(cmd.cli_ctx) cmd_copy.cli_ctx.data = copy.deepcopy(cmd.cli_ctx.data) expanded_arg.cmd = cmd_copy if hasattr(expanded_arg, '_subscription'): cmd_copy.cli_ctx.data[ 'subscription_id'] = expanded_arg._subscription # pylint: disable=protected-access self._validation(expanded_arg) jobs.append((expanded_arg, cmd_copy)) ids = getattr(parsed_args, '_ids', None) or [None] * len(jobs) if self.cli_ctx.config.getboolean('core', 'disable_concurrent_ids', False) or len(ids) < 2: results, exceptions = self._run_jobs_serially(jobs, ids) else: results, exceptions = self._run_jobs_concurrently(jobs, ids) # handle exceptions if len(exceptions) == 1 and not results: ex, id_arg = exceptions[0] raise ex elif exceptions: for exception, id_arg in exceptions: logger.warning('%s: "%s"', id_arg, str(exception)) if not results: return CommandResultItem( None, exit_code=1, error=CLIError('Encountered more than one exception.')) else: logger.warning('Encountered more than one exception.') if results and len(results) == 1: results = results[0] event_data = {'result': results} self.cli_ctx.raise_event(EVENT_INVOKER_FILTER_RESULT, event_data=event_data) return CommandResultItem( event_data['result'], table_transformer=self.commands_loader.command_table[ parsed_args.command].table_transformer, is_query_active=self.data['query_active'])
def _check_value_in_extensions(cli_ctx, parser, args, no_prompt): # pylint: disable=too-many-statements, too-many-locals """Check if the command args can be found in extension commands. Exit command if the error is caused by an extension not installed. Otherwise return. """ # Check if the command is from an extension from azure.cli.core.util import roughly_parse_command from azure.cli.core.azclierror import NoTTYError exit_code = 2 command_str = roughly_parse_command(args[1:]) allow_prefix_match = args[-1] == '-h' or args[-1] == '--help' ext_name = _search_in_extension_commands(cli_ctx, command_str, allow_prefix_match=allow_prefix_match) # ext_name is a list if the input command matches the prefix of one or more extension commands, # for instance: `az blueprint` when running `az blueprint -h` # ext_name is a str if the input command matches a complete command of an extension, # for instance: `az blueprint create` if isinstance(ext_name, list): if len(ext_name) > 1: from knack.prompting import prompt_choice_list, NoTTYException prompt_msg = "The command requires the latest version of one of the following " \ "extensions. You need to pick one to install:" try: choice_idx = prompt_choice_list(prompt_msg, ext_name) ext_name = ext_name[choice_idx] no_prompt = True except NoTTYException: tty_err_msg = "{}{}\nUnable to prompt for selection as no tty available. Please update or " \ "install the extension with 'az extension add --upgrade -n <extension-name>'." \ .format(prompt_msg, ext_name) az_error = NoTTYError(tty_err_msg) az_error.print_error() az_error.send_telemetry() parser.exit(exit_code) else: ext_name = ext_name[0] if not ext_name: return # If a valid command has parser error, it may be caused by CLI running on a profile that is # not 'latest' and the command is not supported in that profile. If this command exists in an extension, # CLI will try to download the extension and rerun the command. But the parser will fail again and try to # install the extension and rerun the command infinitely. So we need to check if the latest version of the # extension is already installed and return if yes as the error is not caused by extension not installed. from azure.cli.core.extension import get_extension, ExtensionNotInstalledException from azure.cli.core.extension._resolve import resolve_from_index, NoExtensionCandidatesError try: ext = get_extension(ext_name) except ExtensionNotInstalledException: pass else: try: resolve_from_index(ext_name, cur_version=ext.version, cli_ctx=cli_ctx) except NoExtensionCandidatesError: return telemetry.set_command_details(command_str, parameters=AzCliCommandInvoker._extract_parameter_names(args), # pylint: disable=protected-access extension_name=ext_name) run_after_extension_installed = _get_extension_run_after_dynamic_install_config(cli_ctx) prompt_info = "" if no_prompt: logger.warning('The command requires the extension %s. It will be installed first.', ext_name) install_ext = True else: # yes_prompt from knack.prompting import prompt_y_n, NoTTYException prompt_msg = 'The command requires the extension {}. Do you want to install it now?'.format(ext_name) if run_after_extension_installed: prompt_msg = '{} The command will continue to run after the extension is installed.' \ .format(prompt_msg) NO_PROMPT_CONFIG_MSG = "Run 'az config set extension.use_dynamic_install=" \ "yes_without_prompt' to allow installing extensions without prompt." try: install_ext = prompt_y_n(prompt_msg, default='y') if install_ext: prompt_info = " with prompt" logger.warning(NO_PROMPT_CONFIG_MSG) except NoTTYException: tty_err_msg = "The command requires the extension {}. " \ "Unable to prompt for extension install confirmation as no tty " \ "available. {}".format(ext_name, NO_PROMPT_CONFIG_MSG) az_error = NoTTYError(tty_err_msg) az_error.print_error() az_error.send_telemetry() parser.exit(exit_code) print_error = True if install_ext: from azure.cli.core.extension.operations import add_extension add_extension(cli_ctx=cli_ctx, extension_name=ext_name, upgrade=True) if run_after_extension_installed: import subprocess import platform exit_code = subprocess.call(args, shell=platform.system() == 'Windows') # In this case, error msg is for telemetry recording purpose only. # From UX perspective, the command will rerun in subprocess. Whether it succeeds or fails, # mesages will be shown from the subprocess and this process should not print more message to # interrupt that. print_error = False error_msg = ("Extension {} dynamically installed{} and commands will be " "rerun automatically.").format(ext_name, prompt_info) else: error_msg = 'Extension {} installed{}. Please rerun your command.' \ .format(ext_name, prompt_info) else: error_msg = "The command requires the latest version of extension {ext_name}. " \ "To install, run 'az extension add --upgrade -n {ext_name}'.".format( ext_name=ext_name) az_error = CommandNotFoundError(error_msg) if print_error: az_error.print_error() az_error.send_telemetry() parser.exit(exit_code)
def _check_value(self, action, value): # pylint: disable=too-many-statements, too-many-locals # Override to customize the error message when a argument is not among the available choices # converted value must be one of the choices (if specified) if action.choices is not None and value not in action.choices: # pylint: disable=too-many-nested-blocks caused_by_extension_not_installed = False if not self.command_source: candidates = difflib.get_close_matches(value, action.choices, cutoff=0.7) error_msg = None # self.cli_ctx is None when self.prog is beyond 'az', such as 'az iot'. # use cli_ctx from cli_help which is not lost. cli_ctx = self.cli_ctx or (self.cli_help.cli_ctx if self.cli_help else None) use_dynamic_install = self._get_extension_use_dynamic_install_config( ) if use_dynamic_install != 'no' and not candidates: # Check if the command is from an extension from azure.cli.core.util import roughly_parse_command cmd_list = self.prog.split() + self._raw_arguments command_str = roughly_parse_command(cmd_list[1:]) ext_name = self._search_in_extension_commands(command_str) if ext_name: caused_by_extension_not_installed = True telemetry.set_command_details( command_str, parameters=AzCliCommandInvoker. _extract_parameter_names(cmd_list), # pylint: disable=protected-access extension_name=ext_name) run_after_extension_installed = cli_ctx.config.getboolean( 'extension', 'run_after_dynamic_install', False) if cli_ctx else False if use_dynamic_install == 'yes_without_prompt': logger.warning( 'The command requires the extension %s. ' 'It will be installed first.', ext_name) go_on = True else: from knack.prompting import prompt_y_n, NoTTYException prompt_msg = 'The command requires the extension {}. ' \ 'Do you want to install it now?'.format(ext_name) if run_after_extension_installed: prompt_msg = '{} The command will continue to run after the extension is installed.' \ .format(prompt_msg) NO_PROMPT_CONFIG_MSG = "Run 'az config set extension.use_dynamic_install=" \ "yes_without_prompt' to allow installing extensions without prompt." try: go_on = prompt_y_n(prompt_msg, default='y') if go_on: logger.warning(NO_PROMPT_CONFIG_MSG) except NoTTYException: logger.warning( "The command requires the extension %s.\n " "Unable to prompt for extension install confirmation as no tty " "available. %s", ext_name, NO_PROMPT_CONFIG_MSG) go_on = False if go_on: from azure.cli.core.extension.operations import add_extension add_extension(cli_ctx=cli_ctx, extension_name=ext_name) if run_after_extension_installed: import subprocess import platform exit_code = subprocess.call( cmd_list, shell=platform.system() == 'Windows') telemetry.set_user_fault( "Extension {} dynamically installed and commands will be " "rerun automatically.".format(ext_name)) self.exit(exit_code) else: error_msg = 'Extension {} installed. Please rerun your command.'.format( ext_name) else: error_msg = "The command requires the extension {ext_name}. " \ "To install, run 'az extension add -n {ext_name}'.".format(ext_name=ext_name) if not error_msg: # parser has no `command_source`, value is part of command itself error_msg = "{prog}: '{value}' is not in the '{prog}' command group. See '{prog} --help'." \ .format(prog=self.prog, value=value) if use_dynamic_install.lower() == 'no': extensions_link = 'https://docs.microsoft.com/en-us/cli/azure/azure-cli-extensions-overview' error_msg = ( "{msg} " "If the command is from an extension, " "please make sure the corresponding extension is installed. " "To learn more about extensions, please visit " "{extensions_link}").format( msg=error_msg, extensions_link=extensions_link) else: # `command_source` indicates command values have been parsed, value is an argument parameter = action.option_strings[ 0] if action.option_strings else action.dest error_msg = "{prog}: '{value}' is not a valid value for '{param}'. See '{prog} --help'.".format( prog=self.prog, value=value, param=parameter) candidates = difflib.get_close_matches(value, action.choices, cutoff=0.7) telemetry.set_user_fault(error_msg) with CommandLoggerContext(logger): logger.error(error_msg) if not caused_by_extension_not_installed: if candidates: print_args = { 's': 's' if len(candidates) > 1 else '', 'verb': 'are' if len(candidates) > 1 else 'is', 'value': value } self._suggestion_msg.append( "\nThe most similar choice{s} to '{value}' {verb}:". format(**print_args)) self._suggestion_msg.append('\n'.join( ['\t' + candidate for candidate in candidates])) failure_recovery_recommendations = self._get_failure_recovery_recommendations( action) self._suggestion_msg.extend(failure_recovery_recommendations) self._print_suggestion_msg(sys.stderr) self.exit(2)