Ejemplo n.º 1
0
def handle_oauth_token_error(e, soft_errors):
    """On a token error, exits the application and writes a message to stderr.

  Args:
    e: google.auth.exceptions.RefreshError, The error to handle.
    soft_errors: Boolean, if True, suppresses any applicable errors and instead
      returns to the caller.
  """
    token_error = str(e).replace('.', '')
    if token_error in errors.OAUTH2_TOKEN_ERRORS or e.startswith(
            'Invalid response'):
        if soft_errors:
            return
        if not GM_Globals[GM_CURRENT_API_USER]:
            display.print_error(
                MESSAGE_API_ACCESS_DENIED.format(
                    GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID],
                    ','.join(GM_Globals[GM_CURRENT_API_SCOPES])))
            controlflow.system_error_exit(12, MESSAGE_API_ACCESS_CONFIG)
        else:
            controlflow.system_error_exit(
                19,
                MESSAGE_SERVICE_NOT_APPLICABLE.format(
                    GM_Globals[GM_CURRENT_API_USER]))
    controlflow.system_error_exit(18, f'Authentication Token Error - {str(e)}')
Ejemplo n.º 2
0
def write_file(filename,
               data,
               mode='w',
               continue_on_error=False,
               display_errors=True):
    """Writes data to a file.

  Args:
    filename: String, the path of the file to write to disk.
    data: Serializable data to write to the file.
    mode: String, the mode in which to open the file and write to it.
    continue_on_error: Boolean, If True, suppresses any IO errors and returns to
      the caller without any externalities.
    display_errors: Boolean, If True, prints error messages when errors are
      encountered and continue_on_error is True.

  Returns:
    Boolean, True if the write operation succeeded, or False if not.
  """
    try:
        with _open_file(filename, mode) as f:
            f.write(data)
        return True

    except IOError as e:
        if continue_on_error:
            if display_errors:
                display.print_error(e)
            return False
        else:
            controlflow.system_error_exit(6, e)
Ejemplo n.º 3
0
 def test_print_error_prints_error_prefix(self, mock_write):
     message = 'test error'
     display.print_error(message)
     printed_message = mock_write.call_args[0][0]
     self.assertLess(
         printed_message.find(ERROR_PREFIX), printed_message.find(message),
         'The error prefix does not appear before the error message')
Ejemplo n.º 4
0
 def test_print_warning_ends_message_with_newline(self):
   message = 'test warning'
   with patch.object(display.sys.stderr, 'write') as mock_write:
     display.print_error(message)
   printed_message = mock_write.call_args[0][0]
   self.assertRegex(printed_message, '\n$',
                    'The warning message does not end in a newline.')
Ejemplo n.º 5
0
 def test_print_warning_prints_error_prefix(self):
   message = 'test warning'
   with patch.object(display.sys.stderr, 'write') as mock_write:
     display.print_error(message)
   printed_message = mock_write.call_args[0][0]
   self.assertLess(
       printed_message.find(WARNING_PREFIX), printed_message.find(message),
       'The warning prefix does not appear before the error message')
Ejemplo n.º 6
0
def system_error_exit(return_code, message):
  """Raises a system exit with the given return code and message.

  Args:
    return_code: Int, the return code to yield when the system exits.
    message: An error message to print before the system exits.
  """
  if message:
    display.print_error(message)
  sys.exit(return_code)
Ejemplo n.º 7
0
def close_file(f):
    """Closes a file.

  Args:
    f: The file to close

  Returns:
    Boolean, True if the file was successfully closed. False if an error
        was encountered while closing.
  """
    try:
        f.close()
        return True
    except IOError as e:
        display.print_error(e)
        return False
Ejemplo n.º 8
0
def close_file(f, force_flush=False):
    """Closes a file.

  Args:
    f: The file to close
    force_flush: Flush file to disk emptying Python and OS caches. See:
       https://stackoverflow.com/a/13762137/1503886

  Returns:
    Boolean, True if the file was successfully closed. False if an error
        was encountered while closing.
  """
    if force_flush:
        f.flush()
        os.fsync(f.fileno())
    try:
        f.close()
        return True
    except IOError as e:
        display.print_error(e)
        return False
Ejemplo n.º 9
0
def get_gapi_error_detail(e,
                          soft_errors=False,
                          silent_errors=False,
                          retry_on_http_error=False):
    """Extracts error detail from a non-200 GAPI Response.

  Args:
    e: googleapiclient.HttpError, The HTTP Error received.
    soft_errors: Boolean, If true, causes error messages to be surpressed,
      rather than sending them to stderr.
    silent_errors: Boolean, If true, suppresses and ignores any errors from
      being displayed
    retry_on_http_error: Boolean, If true, will return -1 as the HTTP Response
      code, indicating that the request can be retried. TODO: Remove this param,
        as it seems to be outside the scope of this method.

  Returns:
    A tuple containing the HTTP Response code, GAPI error reason, and error
        message.
  """
    try:
        error = json.loads(e.content.decode(UTF8))
    except ValueError:
        error_content = e.content.decode(UTF8) if isinstance(
            e.content, bytes) else e.content
        if (e.resp['status']
                == '503') and (error_content
                               == 'Quota exceeded for the current request'):
            return (e.resp['status'], ErrorReason.QUOTA_EXCEEDED.value,
                    error_content)
        if (e.resp['status'] == '403') and (error_content.startswith(
                'Request rate higher than configured')):
            return (e.resp['status'], ErrorReason.QUOTA_EXCEEDED.value,
                    error_content)
        if (e.resp['status'] == '403') and ('Invalid domain.'
                                            in error_content):
            error = _create_http_error_dict(403, ErrorReason.NOT_FOUND.value,
                                            'Domain not found')
        elif (e.resp['status'] == '400') and ('InvalidSsoSigningKey'
                                              in error_content):
            error = _create_http_error_dict(400, ErrorReason.INVALID.value,
                                            'InvalidSsoSigningKey')
        elif (e.resp['status'] == '400') and ('UnknownError' in error_content):
            error = _create_http_error_dict(400, ErrorReason.INVALID.value,
                                            'UnknownError')
        elif retry_on_http_error:
            return (-1, None, None)
        elif soft_errors:
            if not silent_errors:
                display.print_error(error_content)
            return (0, None, None)
        else:
            controlflow.system_error_exit(5, error_content)
        # END: ValueError catch

    if 'error' in error:
        http_status = error['error']['code']
        try:
            message = error['error']['errors'][0]['message']
        except KeyError:
            message = error['error']['message']
    else:
        if 'error_description' in error:
            if error['error_description'] == 'Invalid Value':
                message = error['error_description']
                http_status = 400
                error = _create_http_error_dict(400, ErrorReason.INVALID.value,
                                                message)
            else:
                controlflow.system_error_exit(4, str(error))
        else:
            controlflow.system_error_exit(4, str(error))

    # Extract the error reason
    try:
        reason = error['error']['errors'][0]['reason']
        if reason == 'notFound':
            if 'userKey' in message:
                reason = ErrorReason.USER_NOT_FOUND.value
            elif 'groupKey' in message:
                reason = ErrorReason.GROUP_NOT_FOUND.value
            elif 'memberKey' in message:
                reason = ErrorReason.MEMBER_NOT_FOUND.value
            elif 'Domain not found' in message:
                reason = ErrorReason.DOMAIN_NOT_FOUND.value
            elif 'Resource Not Found' in message:
                reason = ErrorReason.RESOURCE_NOT_FOUND.value
        elif reason == 'invalid':
            if 'userId' in message:
                reason = ErrorReason.USER_NOT_FOUND.value
            elif 'memberKey' in message:
                reason = ErrorReason.INVALID_MEMBER.value
        elif reason == 'failedPrecondition':
            if 'Bad Request' in message:
                reason = ErrorReason.BAD_REQUEST.value
            elif 'Mail service not enabled' in message:
                reason = ErrorReason.SERVICE_NOT_AVAILABLE.value
        elif reason == 'required':
            if 'memberKey' in message:
                reason = ErrorReason.MEMBER_NOT_FOUND.value
        elif reason == 'conditionNotMet':
            if 'Cyclic memberships not allowed' in message:
                reason = ErrorReason.CYCLIC_MEMBERSHIPS_NOT_ALLOWED.value
    except KeyError:
        reason = '{0}'.format(http_status)
    return (http_status, reason, message)
Ejemplo n.º 10
0
def call(service,
         function,
         silent_errors=False,
         soft_errors=False,
         throw_reasons=None,
         retry_reasons=None,
         **kwargs):
    """Executes a single request on a Google service function.

  Args:
    service: A Google service object for the desired API.
    function: String, The name of a service request method to execute.
    silent_errors: Bool, If True, error messages are suppressed when
      encountered.
    soft_errors: Bool, If True, writes non-fatal errors to stderr.
    throw_reasons: A list of Google HTTP error reason strings indicating the
      errors generated by this request should be re-thrown. All other HTTP
      errors are consumed.
    retry_reasons: A list of Google HTTP error reason strings indicating which
      error should be retried, using exponential backoff techniques, when the
      error reason is encountered.
    **kwargs: Additional params to pass to the request method.

  Returns:
    A response object for the corresponding Google API call.
  """
    if throw_reasons is None:
        throw_reasons = []
    if retry_reasons is None:
        retry_reasons = []

    method = getattr(service, function)
    retries = 10
    parameters = dict(
        list(kwargs.items()) + list(GM_Globals[GM_EXTRA_ARGS_DICT].items()))
    for n in range(1, retries + 1):
        try:
            return method(**parameters).execute()
        except googleapiclient.errors.HttpError as e:
            http_status, reason, message = errors.get_gapi_error_detail(
                e,
                soft_errors=soft_errors,
                silent_errors=silent_errors,
                retry_on_http_error=n < 3)
            if http_status == -1:
                # The error detail indicated that we should retry this request
                # We'll refresh credentials and make another pass
                service._http.request.credentials.refresh(
                    transport.create_http())
                continue
            if http_status == 0:
                return None

            is_known_error_reason = reason in [
                r.value for r in errors.ErrorReason
            ]
            if is_known_error_reason and errors.ErrorReason(
                    reason) in throw_reasons:
                if errors.ErrorReason(
                        reason) in errors.ERROR_REASON_TO_EXCEPTION:
                    raise errors.ERROR_REASON_TO_EXCEPTION[errors.ErrorReason(
                        reason)](message)
                raise e
            if (n != retries) and (
                    is_known_error_reason and errors.ErrorReason(reason)
                    in errors.DEFAULT_RETRY_REASONS + retry_reasons):
                controlflow.wait_on_failure(n, retries, reason)
                continue
            if soft_errors:
                display.print_error(
                    f'{http_status}: {message} - {reason}{["", ": Giving up."][n > 1]}'
                )
                return None
            controlflow.system_error_exit(
                int(http_status), f'{http_status}: {message} - {reason}')
        except google.auth.exceptions.RefreshError as e:
            handle_oauth_token_error(
                e, soft_errors
                or errors.ErrorReason.SERVICE_NOT_AVAILABLE in throw_reasons)
            if errors.ErrorReason.SERVICE_NOT_AVAILABLE in throw_reasons:
                raise errors.GapiServiceNotAvailableError(str(e))
            display.print_error(
                f'User {GM_Globals[GM_CURRENT_API_USER]}: {str(e)}')
            return None
        except ValueError as e:
            if hasattr(service._http,
                       'cache') and service._http.cache is not None:
                service._http.cache = None
                continue
            controlflow.system_error_exit(4, str(e))
        except (httplib2.ServerNotFoundError, RuntimeError) as e:
            if n != retries:
                service._http.connections = {}
                controlflow.wait_on_failure(n, retries, str(e))
                continue
            controlflow.system_error_exit(4, str(e))
        except TypeError as e:
            controlflow.system_error_exit(4, str(e))
Ejemplo n.º 11
0
 def test_print_warning_ends_message_with_newline(self, mock_write):
     message = 'test warning'
     display.print_error(message)
     printed_message = mock_write.call_args[0][0]
     self.assertRegex(printed_message, '\n$',
                      'The warning message does not end in a newline.')
Ejemplo n.º 12
0
 def test_print_error_prints_to_stderr(self, mock_write):
     message = 'test error'
     display.print_error(message)
     printed_message = mock_write.call_args[0][0]
     self.assertIn(message, printed_message)
Ejemplo n.º 13
0
 def test_print_warning_prints_to_stderr(self):
   message = 'test warning'
   with patch.object(display.sys.stderr, 'write') as mock_write:
     display.print_error(message)
   printed_message = mock_write.call_args[0][0]
   self.assertIn(message, printed_message)