2016-08-19 09:19:06 +02:00
|
|
|
"""PillarSDK subclass for direct Flask-internal calls."""
|
|
|
|
|
|
|
|
import logging
|
2017-03-03 12:00:24 +01:00
|
|
|
import urllib.parse
|
2016-08-19 09:19:06 +02:00
|
|
|
from flask import current_app
|
|
|
|
|
|
|
|
import pillarsdk
|
|
|
|
from pillarsdk import exceptions
|
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
class FlaskInternalApi(pillarsdk.Api):
|
|
|
|
"""SDK API subclass that calls Flask directly.
|
|
|
|
|
|
|
|
Can only be used from the same Python process the Pillar server itself is
|
|
|
|
running on.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def http_call(self, url, method, **kwargs):
|
|
|
|
"""Fakes a http call through Flask/Werkzeug."""
|
|
|
|
client = current_app.test_client()
|
|
|
|
self.requests_to_flask_kwargs(kwargs)
|
2016-08-23 14:34:15 +02:00
|
|
|
|
|
|
|
# Leave out the query string and fragment from the URL.
|
2017-03-03 12:00:24 +01:00
|
|
|
split_url = urllib.parse.urlsplit(url)
|
|
|
|
path = urllib.parse.urlunsplit(split_url[:-2] + (None, None))
|
2016-08-19 09:19:06 +02:00
|
|
|
try:
|
2016-08-23 14:34:15 +02:00
|
|
|
response = client.open(path=path, query_string=split_url.query, method=method,
|
2016-08-19 09:19:06 +02:00
|
|
|
**kwargs)
|
|
|
|
except Exception as ex:
|
|
|
|
log.warning('Error performing HTTP %s request to %s: %s', method,
|
|
|
|
url, str(ex))
|
|
|
|
raise
|
|
|
|
|
|
|
|
if method == 'OPTIONS':
|
|
|
|
return response
|
|
|
|
|
|
|
|
self.flask_to_requests_response(response)
|
|
|
|
|
|
|
|
try:
|
|
|
|
content = self.handle_response(response, response.data)
|
|
|
|
except:
|
2018-01-03 14:39:02 +01:00
|
|
|
log.debug("%s: Response[%s]: %s", url, response.status_code,
|
|
|
|
response.data, exc_info=True)
|
2016-08-19 09:19:06 +02:00
|
|
|
raise
|
|
|
|
|
|
|
|
return content
|
|
|
|
|
|
|
|
def requests_to_flask_kwargs(self, kwargs):
|
|
|
|
"""Converts Requests arguments to Flask test client arguments."""
|
|
|
|
|
|
|
|
kwargs.pop('verify', None)
|
|
|
|
# No network connection, so nothing to verify.
|
|
|
|
|
|
|
|
# Files to upload need to be sent in the 'data' kwarg instead of the
|
|
|
|
# 'files' kwarg, and have a different order.
|
|
|
|
if 'files' in kwargs:
|
|
|
|
# By default, 'data' is there but None, so setdefault('data', {})
|
|
|
|
# won't work.
|
|
|
|
data = kwargs.get('data') or {}
|
|
|
|
|
|
|
|
for file_name, file_value in kwargs['files'].items():
|
|
|
|
fname, fobj, mimeytpe = file_value
|
|
|
|
data[file_name] = (fobj, fname)
|
|
|
|
|
|
|
|
del kwargs['files']
|
|
|
|
kwargs['data'] = data
|
|
|
|
|
|
|
|
def flask_to_requests_response(self, response):
|
|
|
|
"""Adds some properties to a Flask response object to mimick a Requests
|
|
|
|
object.
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Our API always sends back UTF8, so we don't have to check headers for
|
|
|
|
# that.
|
|
|
|
if response.mimetype.startswith('text'):
|
|
|
|
response.text = response.data.decode('utf8')
|
|
|
|
else:
|
|
|
|
response.text = None
|
|
|
|
|
|
|
|
def OPTIONS(self, action, headers=None):
|
|
|
|
"""Make OPTIONS request.
|
|
|
|
|
|
|
|
Contrary to other requests, this method returns the raw requests.Response object.
|
|
|
|
|
|
|
|
:rtype: requests.Response
|
|
|
|
"""
|
|
|
|
import os
|
|
|
|
|
|
|
|
url = os.path.join(self.endpoint, action.strip('/'))
|
|
|
|
response = self.request(url, 'OPTIONS', headers=headers)
|
|
|
|
if 200 <= response.status_code <= 299:
|
|
|
|
return response
|
|
|
|
|
|
|
|
exception = exceptions.exception_for_status(response.status_code)
|
2017-09-15 10:06:06 +02:00
|
|
|
text = getattr(response, 'text', '')
|
2016-08-19 09:19:06 +02:00
|
|
|
if exception:
|
2017-09-15 10:06:06 +02:00
|
|
|
raise exception(response, text)
|
2016-08-19 09:19:06 +02:00
|
|
|
|
2017-09-15 10:06:06 +02:00
|
|
|
raise exceptions.ConnectionError(response, text,
|
2016-08-19 09:19:06 +02:00
|
|
|
"Unknown response code: %s" % response.status_code)
|