示例#1
0
 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()
示例#2
0
    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()
示例#3
0
    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()
示例#4
0
    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()
示例#5
0
文件: parser.py 项目: zooba/azure-cli
    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()
示例#6
0
    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()
示例#7
0
    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'])
示例#8
0
    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'])
示例#9
0
    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'])
示例#10
0
    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)
示例#11
0
    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'])
示例#12
0
    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'])
示例#13
0
    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'])
示例#14
0
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)
示例#15
0
    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)