Esempio n. 1
0
    def error(self, message):
        # Get a recommended command from the CommandRecommender
        command_arguments = self._get_failure_recovery_arguments()
        cli_ctx = self.cli_ctx or (self.cli_help.cli_ctx if self.cli_help else None)
        recommender = CommandRecommender(*command_arguments, message, cli_ctx)
        recommender.set_help_examples(self.get_examples(self.prog))
        recommendation = recommender.recommend_a_command()

        az_error = AzCLIError(AzCLIErrorType.ArgumentParseError, message, command=self.prog)
        if '--query' in message:
            from azure.cli.core.util import QUERY_REFERENCE
            az_error.set_recommendation(QUERY_REFERENCE)
        elif recommendation:
            az_error.set_recommendation("Try this: '{}'".format(recommendation))
            az_error.set_recommendation(OVERVIEW_REFERENCE.format(command=self.prog))
        az_error.print_error()
        az_error.send_telemetry()

        # For ai-did-you-mean-this
        failure_recovery_recommendations = self._get_failure_recovery_recommendations()
        self._suggestion_msg.extend(failure_recovery_recommendations)
        self._print_suggestion_msg(sys.stderr)
        self.exit(2)
Esempio n. 2
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 = AzCLIError(AzCLIErrorType.CommandNotFoundError, error_msg, command=self.prog)

            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 = AzCLIError(AzCLIErrorType.ArgumentParseError, error_msg, command=self.prog)

            command_arguments = self._get_failure_recovery_arguments(action)
            if candidates:
                az_error.set_recommendation("Did you mean '{}' ?".format(candidates[0]))

            # recommand 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 az_error.error_type == AzCLIErrorType.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))

            az_error.print_error()
            az_error.send_telemetry()

            if not caused_by_extension_not_installed:
                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)
Esempio n. 3
0
 def validation_error(self, message):
     az_error = AzCLIError(AzCLIErrorType.ValidationError, message, command=self.prog)
     az_error.print_error()
     az_error.send_telemetry()
     self.exit(2)
Esempio n. 4
0
def handle_exception(ex):  # pylint: disable=too-many-return-statements, too-many-statements
    # For error code, follow guidelines at https://docs.python.org/2/library/sys.html#sys.exit,
    from jmespath.exceptions import JMESPathTypeError
    from msrestazure.azure_exceptions import CloudError
    from msrest.exceptions import HttpOperationError, ValidationError, ClientRequestError
    from azure.cli.core.azlogging import CommandLoggerContext
    from azure.common import AzureException
    from azure.core.exceptions import AzureError
    from requests.exceptions import SSLError
    import traceback

    logger.debug(
        "azure.cli.core.util.handle_exception is called with an exception:")
    # Print the traceback and exception message
    logger.debug(traceback.format_exc())

    with CommandLoggerContext(logger):
        error_msg = getattr(ex, 'message', str(ex))
        exit_code = 1

        if isinstance(ex, AzCLIError):
            az_error = ex

        elif isinstance(ex, JMESPathTypeError):
            error_msg = "Invalid jmespath query supplied for `--query`: {}".format(
                error_msg)
            az_error = AzCLIError(AzCLIErrorType.ArgumentParseError, error_msg)
            az_error.set_recommendation(QUERY_REFERENCE)

        elif isinstance(ex, ValidationError):
            az_error = AzCLIError(AzCLIErrorType.ValidationError, error_msg)

        # TODO: Fine-grained analysis to decide whether they are ValidationErrors
        elif isinstance(ex, (CLIError, CloudError, AzureError)):
            try:
                error_msg = ex.args[0]
                for detail in ex.args[0].error.details:
                    error_msg += ('\n' + detail)
            except Exception:  # pylint: disable=broad-except
                pass
            az_error = AzCLIError(AzCLIErrorType.ValidationError, error_msg)
            exit_code = ex.args[1] if len(ex.args) >= 2 else 1

        # TODO: Fine-grained analysis
        elif isinstance(ex, AzureException):
            az_error = AzCLIError(AzCLIErrorType.ServiceError, error_msg)
            exit_code = ex.args[1] if len(ex.args) >= 2 else 1

        # TODO: Fine-grained analysis
        elif isinstance(ex, (ClientRequestError, SSLError)):
            az_error = AzCLIError(AzCLIErrorType.ClientError, error_msg)
            if 'SSLError' in error_msg:
                az_error.set_recommendation(SSLERROR_TEMPLATE)

        # TODO: Fine-grained analysis
        elif isinstance(ex, HttpOperationError):
            try:
                response = json.loads(ex.response.text)
                if isinstance(response, str):
                    error = response
                else:
                    error = response['error']

                # ARM should use ODATA v4. So should try this first.
                # http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793091
                if isinstance(error, dict):
                    code = "{} - ".format(error.get('code', 'Unknown Code'))
                    message = error.get('message', ex)
                    error_msg = "code: {}, {}".format(code, message)
                else:
                    error_msg = error

            except (ValueError, KeyError):
                pass

            az_error = AzCLIError(AzCLIErrorType.ServiceError, error_msg)

        elif isinstance(ex, KeyboardInterrupt):
            error_msg = 'Keyboard interrupt is captured.'
            az_error = AzCLIError(AzCLIErrorType.ManualInterrupt, error_msg)

        else:
            error_msg = "The command failed with an unexpected error. Here is the traceback:"
            az_error = AzCLIError(AzCLIErrorType.UnexpectedError, error_msg)
            az_error.set_raw_exception(ex)
            az_error.set_recommendation(
                "To open an issue, please run: 'az feedback'")

        az_error.print_error()
        az_error.send_telemetry()

        return exit_code