Add first iteration of http client

The offline client is ideal for running offline and locally.
The http client uses requests to do actual http requests to an API
server.

Change-Id: I2b1ed6068ab547d55237b3f9ff7150935232caa6
This commit is contained in:
David Moreau Simard 2018-09-12 09:09:39 -04:00
parent 7310b6cea5
commit ee86bb5cfe
No known key found for this signature in database
GPG Key ID: 33A07694CBB71ECC
3 changed files with 143 additions and 8 deletions

127
ara/clients/http.py Normal file
View File

@ -0,0 +1,127 @@
# Copyright (c) 2018 Red Hat, Inc.
#
# This file is part of ARA: Ansible Run Analysis.
#
# ARA is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ARA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with ARA. If not, see <http://www.gnu.org/licenses/>.
# This is an "offline" API client that does not require standing up
# an API server and does not execute actual HTTP calls.
import json
import logging
import requests
class HttpClient(object):
def __init__(self, endpoint='http://127.0.0.1:8000', timeout=30, **params):
self.endpoint = endpoint
self.timeout = timeout
self.params = params
self.log = logging.getLogger(__name__)
self.user_agent = 'ara-http-client'
self.log.debug("%s: %s" % (self.user_agent, str(self.params)))
self.http = requests.Session()
def _request(self, method, url, **kwargs):
# Override timeout and headers only if user supplied
kwargs.setdefault('timeout', self.timeout)
kwargs.setdefault('headers', kwargs.get('headers', {}))
# Headers we're enforcing (kind of)
kwargs['headers']['User-Agent'] = self.user_agent
kwargs['headers']['Accept'] = 'application/json'
kwargs['headers']['Content-Type'] = 'application/json'
self.log.debug("%s on %s" % (method, url))
# Use requests.Session to do the query
# The actual endpoint is:
# <endpoint> <url>
# http://127.0.0.1:8000 / api/v1/playbooks
return self.http.request(method, self.endpoint + url, **kwargs)
def get(self, url, **kwargs):
return self._request('get', url, **kwargs)
def patch(self, url, **kwargs):
return self._request('patch', url, **kwargs)
def post(self, url, **kwargs):
return self._request('post', url, **kwargs)
def put(self, url, **kwargs):
return self._request('put', url, **kwargs)
def delete(self, url, **kwargs):
return self._request('delete', url, **kwargs)
class AraHttpClient(object):
def __init__(self):
self.log = logging.getLogger(__name__)
self.client = HttpClient()
def _request(self, method, url, **kwargs):
func = getattr(self.client, method)
# TODO: Is there a better way than doing this if/else ?
if kwargs:
response = func(url, json.dumps(kwargs))
else:
response = func(url)
if response.status_code >= 500:
self.log.error(
'Failed to {method} on {url}: {content}'.format(
method=method,
url=url,
content=kwargs
)
)
self.log.debug('HTTP {status}: {method} on {url}'.format(
status=response.status_code,
method=method,
url=url
))
if response.status_code not in [200, 201, 204]:
self.log.error(
'Failed to {method} on {url}: {content}'.format(
method=method,
url=url,
content=kwargs
)
)
if response.status_code == 204:
return response
return response.json()
def get(self, endpoint, **kwargs):
return self._request('get', endpoint, **kwargs)
def patch(self, endpoint, **kwargs):
return self._request('patch', endpoint, **kwargs)
def post(self, endpoint, **kwargs):
return self._request('post', endpoint, **kwargs)
def put(self, endpoint, **kwargs):
return self._request('put', endpoint, **kwargs)
def delete(self, endpoint, **kwargs):
return self._request('delete', endpoint, **kwargs)

View File

@ -22,16 +22,21 @@ import json
import logging import logging
import os import os
from django import setup as django_setup try:
from django.core.management import execute_from_command_line from django import setup as django_setup
from django.test import Client from django.core.management import execute_from_command_line
from django.test import Client
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ara.server.settings') os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ara.server.settings')
# Automatically create the database and run migrations (is there a better way?)
execute_from_command_line(['django', 'migrate'])
# Set up the things Django needs # Automatically create the database and run migrations (is there a better way?)
django_setup() execute_from_command_line(['django', 'migrate'])
# Set up the things Django needs
django_setup()
except ImportError as e:
print('ERROR: The offline client requires ara-server to be installed')
raise e
class AraOfflineClient(object): class AraOfflineClient(object):

View File

@ -16,10 +16,13 @@
# along with ARA. If not, see <http://www.gnu.org/licenses/>. # along with ARA. If not, see <http://www.gnu.org/licenses/>.
from ara.clients.offline import AraOfflineClient from ara.clients.offline import AraOfflineClient
from ara.clients.http import AraHttpClient
def get_client(client=None, **kwargs): def get_client(client=None, **kwargs):
if client is None or client == 'offline': if client is None or client == 'offline':
return AraOfflineClient(**kwargs) return AraOfflineClient(**kwargs)
elif client == 'http':
return AraHttpClient(**kwargs)
else: else:
raise ValueError('Unsupported client: %s' % client) raise ValueError('Unsupported client: %s' % client)