Beispiel #1
0
def HandleError(exc, command_path, known_error_handler=None):
  """Handles an error that occurs during command execution.

  It calls ConvertKnownError to convert exceptions to known types before
  processing. If it is a known type, it is printed nicely as as error. If not,
  it is raised as a crash.

  Args:
    exc: Exception, The original exception that occurred.
    command_path: str, The name of the command that failed (for error
      reporting).
    known_error_handler: f(exc): A function to process known errors.
  """
  known_exc, print_error = ConvertKnownError(exc)
  if known_exc:
    msg = u'({0}) {1}'.format(
        console_attr.EncodeForConsole(command_path),
        console_attr.EncodeForConsole(known_exc))
    log.debug(msg, exc_info=sys.exc_info())
    if print_error:
      log.error(msg)
    # Uncaught errors will be handled in gcloud_main.
    if known_error_handler:
      known_error_handler(exc)
    if properties.VALUES.core.print_handled_tracebacks.GetBool():
      raise
    _Exit(known_exc)
  else:
    # Make sure any uncaught exceptions still make it into the log file.
    log.debug(console_attr.EncodeForConsole(exc), exc_info=sys.exc_info())
    raise
 def Parse(value):
     if fn(value):
         return value
     encoded_value = console_attr.EncodeForConsole(value)
     formatted_err = u'Bad value [{0}]: {1}'.format(encoded_value,
                                                    description)
     raise ArgumentTypeError(formatted_err)
Beispiel #3
0
def _ReportCrash(err):
    """Report the anonymous crash information to the Error Reporting service.

  Args:
    err: Exception, the error that caused the crash.
  """
    stacktrace = traceback.format_exc(err)
    stacktrace = error_reporting_util.RemovePrivateInformationFromTraceback(
        stacktrace)
    command = properties.VALUES.metrics.command_name.Get()
    cid = metrics.GetCIDIfMetricsEnabled()

    client = _GetReportingClient()
    reporter = util.ErrorReporting(client)
    try:
        reporter.ReportEvent(error_message=stacktrace,
                             service=CRASH_SERVICE,
                             version=config.CLOUD_SDK_VERSION,
                             project=CRASH_PROJECT,
                             request_url=command,
                             user=cid)
    except apitools_exceptions.HttpError as http_err:
        log.file_only_logger.error(
            'Unable to report crash stacktrace:\n{0}'.format(
                console_attr.EncodeForConsole(http_err)))
Beispiel #4
0
    def error(self, message):
        """Overrides argparse.ArgumentParser's .error(message) method.

    Specifically, it avoids reprinting the program name and the string "error:".

    Args:
      message: str, The error message to print.
    """
        _, error, _ = sys.exc_info()
        parser = self
        if isinstance(error, parser_errors.ArgumentError):
            # This associates the error with the correct parser.
            parser = error.parser or self
        parser.ReportErrorMetrics(error, message)

        # No need to output help/usage text if we are in completion mode. However,
        # we do need to populate group/command level choices. These choices are not
        # loaded when there is a parser error since we do lazy loading.
        if '_ARGCOMPLETE' in os.environ:
            # pylint:disable=protected-access
            if self._calliope_command._sub_parser:
                self._calliope_command.LoadAllSubElements()
        else:
            message = console_attr.EncodeForConsole(message)
            log.error(u'({prog}) {message}'.format(prog=self.prog,
                                                   message=message))
            # multi-line message means hints already added, no need for usage.
            # pylint:disable=protected-access
            if '\n' not in message:
                argparse._sys.stderr.write(self._calliope_command.GetUsage())

        self.exit(2)
Beispiel #5
0
    def _HandleAllErrors(self, exc, command_path_string, specified_arg_names):
        """Handle all errors.

    Args:
      exc: Exception, The exception that was raised.
      command_path_string: str, The '.' separated command path.
      specified_arg_names: [str], The specified arg named scrubbed for metrics.

    Raises:
      exc or a core.exceptions variant that does not produce a stack trace.
    """
        if session_capturer.SessionCapturer.capturer:
            session_capturer.SessionCapturer.capturer.CaptureException(exc)
        known_exc, print_error = exceptions.ConvertKnownError(exc)
        error_extra_info = {'error_code': getattr(exc, 'exit_code', 1)}
        if isinstance(exc, exceptions.HttpException):
            error_extra_info['http_status_code'] = exc.payload.status_code

        metrics.Commands(command_path_string,
                         config.CLOUD_SDK_VERSION,
                         specified_arg_names,
                         error=exc.__class__,
                         error_extra_info=error_extra_info)
        metrics.Error(command_path_string,
                      exc.__class__,
                      specified_arg_names,
                      error_extra_info=error_extra_info)

        if known_exc:
            msg = u'({0}) {1}'.format(
                console_attr.EncodeForConsole(command_path_string),
                console_attr.EncodeForConsole(known_exc))
            log.debug(msg, exc_info=sys.exc_info())
            if print_error:
                log.error(msg)
            # Uncaught errors will be handled in gcloud_main.
            if self.__known_error_handler:
                self.__known_error_handler(exc)
            if properties.VALUES.core.print_handled_tracebacks.GetBool():
                raise
            self._Exit(known_exc)
        else:
            # Make sure any uncaught exceptions still make it into the log file.
            log.debug(console_attr.EncodeForConsole(exc),
                      exc_info=sys.exc_info())
            raise
Beispiel #6
0
  def error(self, message=None, context=None):
    """Overrides argparse.ArgumentParser's .error(message) method.

    Specifically, it avoids reprinting the program name and the string "error:".

    Args:
      message: str, The error message to print.
      context: _ErrorContext, A previous intercepted error context to reproduce.
    """
    if context:
      # Reproduce a previous call to this method from the info in context.
      message = context.message
      parser = context.parser
      error = context.error
      if error:
        # argparse calls this method as the result of an exception that can be
        # checked in sys.exc_info()[1].  A side effect of this try-except is to
        # set sys.exc_info()[1] to context.error from the original call that was
        # saved below in self._error_context.  This value might be checked later
        # in the execution (the test harness in particular checks it).
        try:
          raise error  # pylint: disable=raising-bad-type
        except type(error):
          pass
      else:
        error = sys.exc_info()[1]
    else:
      error = sys.exc_info()[1]
      parser = self
      if '_ARGCOMPLETE' not in os.environ and (
          self._probe_error or
          'too few arguments' in message or
          'Invalid choice' in message):
        # Save this context for later. We may be able to decuce a better error
        # message. For instance, argparse might complain about an invalid
        # command choice 'flag-value' for '--unknown-flag flag-value', but
        # with a little finagling in parse_known_args() we can verify that
        # '--unknown-flag' is in fact an unknwon flag and error out on that.
        self._error_context = _ErrorContext(message, parser, error)
        return
    parser.ReportErrorMetrics(error, message)

    # No need to output help/usage text if we are in completion mode. However,
    # we do need to populate group/command level choices. These choices are not
    # loaded when there is a parser error since we do lazy loading.
    if '_ARGCOMPLETE' in os.environ:
      # pylint:disable=protected-access
      if self._calliope_command._sub_parser:
        self._calliope_command.LoadAllSubElements()
    else:
      message = console_attr.EncodeForConsole(message)
      log.error(u'({prog}) {message}'.format(prog=self.prog, message=message))
      # multi-line message means hints already added, no need for usage.
      # pylint:disable=protected-access
      if '\n' not in message:
        argparse._sys.stderr.write(self._calliope_command.GetUsage())

    self.exit(2)
Beispiel #7
0
    def __init__(self, args, invalid_arg):
        self.invalid_arg = invalid_arg
        cmd = os.path.basename(args[0])
        if cmd.endswith('.py'):
            cmd = cmd[:-3]
        args = [cmd] + args[1:]

        super(InvalidCharacterInArgException, self).__init__(
            u'Failed to read command line argument [{0}] because it does '
            u'not appear to be valid 7-bit ASCII.\n\n'
            u'{1}'.format(console_attr.EncodeForConsole(self.invalid_arg),
                          _FormatNonAsciiMarkerString(args)))
Beispiel #8
0
 def Parse(value):
   """Validates and returns a custom object from an argument string value."""
   try:
     parsed_value = parser(value) if parser else value
   except ArgumentTypeError:
     pass
   else:
     if fn(parsed_value):
       return parsed_value
   encoded_value = console_attr.EncodeForConsole(value)
   formatted_err = u'Bad value [{0}]: {1}'.format(encoded_value, description)
   raise ArgumentTypeError(formatted_err)
Beispiel #9
0
    def Print(self, *msg):
        """Writes the given message to the output stream, and adds a newline.

    This method has the same output behavior as the builtin print method but
    respects the configured verbosity.

    Args:
      *msg: str, The messages to print.
    """

        from googlecloudsdk.core.console import console_attr  # pylint: disable=g-import-not-at-top, avoid import loop
        msg = (console_attr.EncodeForConsole(x, escape=False) for x in msg)
        message = u' '.join(msg)
        self.write(message + u'\n')
def HandleGcloudCrash(err):
    """Checks if installation error occurred, then proceeds with Error Reporting.

  Args:
    err: Exception err.
  """
    err_string = console_attr.EncodeForConsole(err)
    log.file_only_logger.exception('BEGIN CRASH STACKTRACE')
    if _IsInstallationCorruption(err):
        _PrintInstallationAction(err, err_string)
    else:
        log.error(u'gcloud crashed ({0}): {1}'.format(
            getattr(err, 'error_name',
                    type(err).__name__), err_string))
        ReportError(err, is_crash=True)
        log.err.Print(
            '\nIf you would like to report this issue, please run the '
            'following command:')
        log.err.Print('  gcloud feedback')
        log.err.Print('\nTo check gcloud for common problems, please run the '
                      'following command:')
        log.err.Print('  gcloud info --run-diagnostics')
Beispiel #11
0
    def ReportMetrics(self, wait_for_report=False):
        """Reports the collected metrics using a separate async process."""
        if not self._metrics:
            return

        temp_metrics_file = tempfile.NamedTemporaryFile(delete=False)
        with temp_metrics_file:
            pickle.dump(self._metrics, temp_metrics_file)
            self._metrics = []

        this_file = encoding.Decode(__file__)
        reporting_script_path = os.path.realpath(
            os.path.join(os.path.dirname(this_file), 'metrics_reporter.py'))
        execution_args = execution_utils.ArgsForPythonTool(
            reporting_script_path, temp_metrics_file.name)
        # popen is silly when it comes to non-ascii args. The executable has to be
        # _unencoded_, while the rest of the args have to be _encoded_.
        execution_args = execution_args[0:1] + [
            console_attr.EncodeForConsole(a) for a in execution_args[1:]
        ]

        exec_env = os.environ.copy()
        exec_env['PYTHONPATH'] = os.pathsep.join(sys.path)

        try:
            p = subprocess.Popen(execution_args,
                                 env=exec_env,
                                 **self._async_popen_args)
            log.debug('Metrics reporting process started...')
        except OSError:
            # This can happen specifically if the Python executable moves between the
            # start of this process and now.
            log.debug('Metrics reporting process failed to start.')
        if wait_for_report:
            # NOTE: p.wait() can cause a deadlock. p.communicate() is recommended.
            # See python docs for more information.
            p.communicate()
            log.debug('Metrics reporting process finished.')
def ReportError(err, is_crash):
    """Report the anonymous crash information to the Error Reporting service.

  Args:
    err: Exception, the error that caused the crash.
    is_crash: bool, True if this is a crash, False if it is a user error.
  """
    if properties.VALUES.core.disable_usage_reporting.GetBool():
        return

    stacktrace = traceback.format_exc(err)
    stacktrace = error_reporting_util.RemovePrivateInformationFromTraceback(
        stacktrace)
    command = properties.VALUES.metrics.command_name.Get()
    cid = metrics.GetCIDIfMetricsEnabled()

    client = _GetReportingClient()
    reporter = util.ErrorReporting(client)
    try:
        method_config = client.projects_events.GetMethodConfig('Report')
        request = reporter.GenerateReportRequest(
            error_message=stacktrace,
            service=CRASH_SERVICE if is_crash else ERROR_SERVICE,
            version=config.CLOUD_SDK_VERSION,
            project=CRASH_PROJECT,
            request_url=command,
            user=cid)
        http_request = client.projects_events.PrepareHttpRequest(
            method_config, request)
        metrics.CustomBeacon(http_request.url, http_request.http_method,
                             http_request.body, http_request.headers)

    except apitools_exceptions.Error as e:
        log.file_only_logger.error(
            'Unable to report crash stacktrace:\n{0}'.format(
                console_attr.EncodeForConsole(e)))
 def __init__(self, attr, string):
   self._string = console_attr.EncodeForConsole(
       string, encoding=attr.GetEncoding(), escape=False)
   self._adjust = attr.DisplayWidth(self._string) - len(self._string)
Beispiel #14
0
def _FormatNonAsciiMarkerString(args):
    r"""Format a string that will mark the first non-ASCII character it contains.


  Example:

  >>> args = ['command.py', '--foo=\xce\x94']
  >>> _FormatNonAsciiMarkerString(args) == (
  ...     'command.py --foo=\u0394\n'
  ...     '                 ^ invalid character'
  ... )
  True

  Args:
    args: The arg list for the command executed

  Returns:
    unicode, a properly formatted string with two lines, the second of which
      indicates the non-ASCII character in the first.

  Raises:
    ValueError: if the given string is all ASCII characters
  """
    # nonascii will be True if at least one arg contained a non-ASCII character
    nonascii = False
    # pos is the position of the first non-ASCII character in ' '.join(args)
    pos = 0
    for arg in args:
        try:
            # idx is the index of the first non-ASCII character in arg
            for idx, char in enumerate(arg):
                char.decode('ascii')
        except UnicodeError:
            # idx will remain set, indicating the first non-ASCII character
            pos += idx
            nonascii = True
            break
        # this arg was all ASCII; add 1 for the ' ' between args
        pos += len(arg) + 1
    if not nonascii:
        raise ValueError('The command line is composed entirely of ASCII '
                         'characters.')

    # Make a string that, when printed in parallel, will point to the non-ASCII
    # character
    marker_string = ' ' * pos + _MARKER

    # Make sure that this will still print out nicely on an odd-sized screen
    align = len(marker_string)
    args_string = u' '.join(
        [console_attr.EncodeForConsole(arg) for arg in args])
    width, _ = console_attr_os.GetTermSize()
    fill = '...'
    if width < len(_MARKER) + len(fill):
        # It's hopeless to try to wrap this and make it look nice. Preserve it in
        # full for logs and so on.
        return '\n'.join((args_string, marker_string))
    # If len(args_string) < width < len(marker_string) (ex:)
    #
    #   args_string   = 'command BAD'
    #   marker_string = '        ^ invalid character'
    #   width     = len('----------------')
    #
    # then the truncation can give a result like the following:
    #
    #   args_string   = 'command BAD'
    #   marker_string = '   ^ invalid character'
    #
    # (This occurs when args_string is short enough to not be truncated, but
    # marker_string is long enough to be truncated.)
    #
    # ljust args_string to make it as long as marker_string before passing to
    # _TruncateToLineWidth, which will yield compatible truncations. rstrip at the
    # end to get rid of the new trailing spaces.
    formatted_args_string = _TruncateToLineWidth(args_string.ljust(align),
                                                 align,
                                                 width,
                                                 fill=fill).rstrip()
    formatted_marker_string = _TruncateToLineWidth(marker_string, align, width)
    return u'\n'.join((formatted_args_string, formatted_marker_string))
Beispiel #15
0
def Exec(args,
         env=None,
         no_exit=False,
         out_func=None,
         err_func=None,
         in_str=None,
         **extra_popen_kwargs):
  """Emulates the os.exec* set of commands, but uses subprocess.

  This executes the given command, waits for it to finish, and then exits this
  process with the exit code of the child process.

  Args:
    args: [str], The arguments to execute.  The first argument is the command.
    env: {str: str}, An optional environment for the child process.
    no_exit: bool, True to just return the exit code of the child instead of
      exiting.
    out_func: str->None, a function to call with the stdout of the executed
      process. This can be e.g. log.file_only_logger.debug or log.out.write.
    err_func: str->None, a function to call with the stderr of the executed
      process. This can be e.g. log.file_only_logger.debug or log.err.write.
    in_str: str, input to send to the subprocess' stdin.
    **extra_popen_kwargs: Any additional kwargs will be passed through directly
      to subprocess.Popen

  Returns:
    int, The exit code of the child if no_exit is True, else this method does
    not return.

  Raises:
    PermissionError: if user does not have execute permission for cloud sdk bin
    files.
    InvalidCommandError: if the command entered cannot be found.
  """
  log.debug('Executing command: %s', args)
  # We use subprocess instead of execv because windows does not support process
  # replacement.  The result of execv on windows is that a new processes is
  # started and the original is killed.  When running in a shell, the prompt
  # returns as soon as the parent is killed even though the child is still
  # running.  subprocess waits for the new process to finish before returning.
  env = _GetToolEnv(env=env)

  process_holder = _ProcessHolder()
  with _ReplaceSignal(signal.SIGTERM, process_holder.Handler):
    with _ReplaceSignal(signal.SIGINT, process_holder.Handler):
      if out_func:
        extra_popen_kwargs['stdout'] = subprocess.PIPE
      if err_func:
        extra_popen_kwargs['stderr'] = subprocess.PIPE
      if in_str:
        extra_popen_kwargs['stdin'] = subprocess.PIPE
      try:
        # popen is silly when it comes to non-ascii args. The executable has to
        # be _unencoded_, while the rest of the args have to be _encoded_.
        if args and isinstance(args, list):
          args = args[0:1] + [
              console_attr.EncodeForConsole(a) for a in args[1:]]
        p = subprocess.Popen(args, env=env, **extra_popen_kwargs)
      except OSError as err:
        if err.errno == errno.EACCES:
          raise PermissionError(err.strerror)
        elif err.errno == errno.ENOENT:
          raise InvalidCommandError(args[0])
        raise
      process_holder.process = p
      stdout, stderr = p.communicate(input=in_str)
      if out_func:
        out_func(stdout)
      if err_func:
        err_func(stderr)
      ret_val = p.returncode

  if no_exit:
    return ret_val
  sys.exit(ret_val)