def _get_auth_token(self):
     filename = os.path.expanduser('~/.tower_cli_token.json')
     token_json = None
     try:
         with open(filename) as f:
             token_json = json.load(f)
         if not isinstance(token_json, dict) or self.cli_client.get_prefix() not in token_json or \
                 'token' not in token_json[self.cli_client.get_prefix()] or \
                 'expires' not in token_json[self.cli_client.get_prefix()] or \
                 dt.utcnow() > dt.strptime(token_json[self.cli_client.get_prefix()]['expires'], TOWER_DATETIME_FMT):
             raise Exception("Current token expires.")
         return 'Token ' + token_json[self.cli_client.get_prefix()]['token']
     except Exception as e:
         debug.log('Acquiring and caching auth token due to:\n%s' % str(e),
                   fg='blue',
                   bold=True)
         if not isinstance(token_json, dict):
             token_json = {}
         token_json[self.cli_client.get_prefix()] = self._acquire_token()
         if not isinstance(token_json[self.cli_client.get_prefix()], dict) or \
                 'token' not in token_json[self.cli_client.get_prefix()] or \
                 'expires' not in token_json[self.cli_client.get_prefix()]:
             raise exc.AuthError(
                 'Invalid Tower auth token format: %s' %
                 json.dumps(token_json[self.cli_client.get_prefix()]))
         with open(filename, 'w') as f:
             json.dump(token_json, f)
         try:
             os.chmod(filename, stat.S_IRUSR | stat.S_IWUSR)
         except Exception as e:
             warnings.warn(
                 'Unable to set permissions on {0} - {1} '.format(
                     filename, e), UserWarning)
         return 'Token ' + token_json[self.cli_client.get_prefix()]['token']
Пример #2
0
 def _get_auth_token(self):
     filename = os.path.expanduser('~/.tower_cli_token.json')
     try:
         with open(filename) as f:
             token_json = json.load(f)
         if not isinstance(token_json, dict) or 'token' not in token_json or 'expires' not in token_json or \
                 dt.utcnow() > dt.strptime(token_json['expires'], TOWER_DATETIME_FMT):
             raise Exception("Current token expires.")
         return 'Token ' + token_json['token']
     except Exception as e:
         debug.log('Acquiring and caching auth token due to:\n%s' % str(e),
                   fg='blue',
                   bold=True)
         token_json = self._acquire_token()
         if not isinstance(
                 token_json, dict
         ) or 'token' not in token_json or 'expires' not in token_json:
             raise exc.AuthError('Invalid Tower auth token format: %s' %
                                 json.dumps(token_json))
         with open(filename, 'w') as f:
             json.dump(token_json, f)
         return 'Token ' + token_json['token']
    def request(self, method, url, *args, **kwargs):
        """Make a request to the Ansible Tower API, and return the
        response.
        """

        # If the URL has the api/vX at the front strip it off
        # This is common to have if you are extracting a URL from an existing object.
        # For example, any of the 'related' fields of an object will have this
        import re
        url = re.sub("^/?api/v[0-9]+/", "", url)

        # Piece together the full URL.
        use_version = not url.startswith('/o/')
        url = '%s%s' % (self.get_prefix(use_version), url.lstrip('/'))

        # Ansible Tower expects authenticated requests; add the authentication
        # from settings if it's provided.
        kwargs.setdefault(
            'auth', BasicTowerAuth(settings.username, settings.password, self))

        # POST and PUT requests will send JSON by default; make this
        # the content_type by default.  This makes it such that we don't have
        # to constantly write that in our code, which gets repetitive.
        headers = kwargs.get('headers', {})
        if method.upper() in ('PATCH', 'POST', 'PUT'):
            headers.setdefault('Content-Type', 'application/json')
            kwargs['headers'] = headers

        # If debugging is on, print the URL and data being sent.
        debug.log('%s %s' % (method, url), fg='blue', bold=True)
        if method in ('POST', 'PUT', 'PATCH'):
            debug.log('Data: %s' % kwargs.get('data', {}),
                      fg='blue',
                      bold=True)
        if method == 'GET' or kwargs.get('params', None):
            debug.log('Params: %s' % kwargs.get('params', {}),
                      fg='blue',
                      bold=True)
        debug.log('')

        # If this is a JSON request, encode the data value.
        if headers.get('Content-Type', '') == 'application/json':
            kwargs['data'] = json.dumps(kwargs.get('data', {}))

        r = self._make_request(method, url, args, kwargs)

        # Sanity check: Did the server send back some kind of internal error?
        # If so, bubble this up.
        if r.status_code >= 500:
            raise exc.ServerError('The Tower server sent back a server error. '
                                  'Please try again later.')

        # Sanity check: Did we fail to authenticate properly?
        # If so, fail out now; this is always a failure.
        if r.status_code == 401:
            raise exc.AuthError(
                'Invalid Tower authentication credentials (HTTP 401).')

        # Sanity check: Did we get a forbidden response, which means that
        # the user isn't allowed to do this? Report that.
        if r.status_code == 403:
            raise exc.Forbidden(
                "You don't have permission to do that (HTTP 403).")

        # Sanity check: Did we get a 404 response?
        # Requests with primary keys will return a 404 if there is no response,
        # and we want to consistently trap these.
        if r.status_code == 404:
            raise exc.NotFound('The requested object could not be found.')

        # Sanity check: Did we get a 405 response?
        # A 405 means we used a method that isn't allowed. Usually this
        # is a bad request, but it requires special treatment because the
        # API sends it as a logic error in a few situations (e.g. trying to
        # cancel a job that isn't running).
        if r.status_code == 405:
            raise exc.MethodNotAllowed(
                "The Tower server says you can't make a request with the "
                "%s method to that URL (%s)." % (method, url), )

        # Sanity check: Did we get some other kind of error?
        # If so, write an appropriate error message.
        if r.status_code >= 400:
            raise exc.BadRequest(
                'The Tower server claims it was sent a bad request.\n\n'
                '%s %s\nParams: %s\nData: %s\n\nResponse: %s' %
                (method, url, kwargs.get('params', None),
                 kwargs.get('data', None), r.content.decode('utf8')))

        # Django REST Framework intelligently prints API keys in the
        # order that they are defined in the models and serializer.
        #
        # We want to preserve this behavior when it is possible to do so
        # with minimal effort, because while the order has no explicit meaning,
        # we make some effort to order keys in a convenient manner.
        #
        # To this end, make this response into an APIResponse subclass
        # (defined below), which has a `json` method that doesn't lose key
        # order.
        r.__class__ = APIResponse

        # Return the response object.
        return r