From ee86bb5cfe08e2d47079182b11afb66de67dd3cc Mon Sep 17 00:00:00 2001 From: David Moreau Simard Date: Wed, 12 Sep 2018 09:09:39 -0400 Subject: [PATCH] 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 --- ara/clients/http.py | 127 +++++++++++++++++++++++++++++++++++++++++ ara/clients/offline.py | 21 ++++--- ara/clients/utils.py | 3 + 3 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 ara/clients/http.py diff --git a/ara/clients/http.py b/ara/clients/http.py new file mode 100644 index 0000000..30c8dd0 --- /dev/null +++ b/ara/clients/http.py @@ -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 . + +# 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: + # + # 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) diff --git a/ara/clients/offline.py b/ara/clients/offline.py index 2d25a5b..f2876ed 100644 --- a/ara/clients/offline.py +++ b/ara/clients/offline.py @@ -22,16 +22,21 @@ import json import logging import os -from django import setup as django_setup -from django.core.management import execute_from_command_line -from django.test import Client +try: + from django import setup as django_setup + from django.core.management import execute_from_command_line + from django.test import Client -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']) + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ara.server.settings') -# Set up the things Django needs -django_setup() + # Automatically create the database and run migrations (is there a better way?) + 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): diff --git a/ara/clients/utils.py b/ara/clients/utils.py index 99006a8..e1ce500 100644 --- a/ara/clients/utils.py +++ b/ara/clients/utils.py @@ -16,10 +16,13 @@ # along with ARA. If not, see . from ara.clients.offline import AraOfflineClient +from ara.clients.http import AraHttpClient def get_client(client=None, **kwargs): if client is None or client == 'offline': return AraOfflineClient(**kwargs) + elif client == 'http': + return AraHttpClient(**kwargs) else: raise ValueError('Unsupported client: %s' % client)