From: Kiran Kamineni Date: Fri, 17 Aug 2018 22:54:05 +0000 (-0700) Subject: Add python client for SMS service X-Git-Tag: 3.0.0~14 X-Git-Url: https://gerrit.onap.org/r/gitweb?p=aaf%2Fsms.git;a=commitdiff_plain;h=6546bbac701bdff45adb11c7e93236e6736898f6 Add python client for SMS service Add python client for SMS service. Other ONAP microservices can use this client to connect to SMS to store and get secrets. Issue-ID: AAF-438 Change-Id: I5bb39001e8482b9191512b1422ed7edadbd2ec67 Signed-off-by: Kiran Kamineni --- diff --git a/sms-client/.gitignore b/sms-client/.gitignore new file mode 100644 index 0000000..7e99e36 --- /dev/null +++ b/sms-client/.gitignore @@ -0,0 +1 @@ +*.pyc \ No newline at end of file diff --git a/sms-client/python/sms/__init__.py b/sms-client/python/sms/__init__.py new file mode 100644 index 0000000..88a7621 --- /dev/null +++ b/sms-client/python/sms/__init__.py @@ -0,0 +1,248 @@ +# Copyright 2018 Intel Corporation, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import requests +import requests.exceptions +import urlparse + +class InvalidRequestException(Exception): + pass + +class InternalServerError(Exception): + pass + +class UnexpectedError(Exception): + pass + +class Client(object): + """Python Client for Secret Management Service""" + + def __init__(self, url='http://localhost:10443', timeout=30, cacert=None): + """Creates a new SMS client instance + + Args: + url (str): Base URL with port pointing to the SMS service + timeout (int): Number of seconds before aborting the connection + cacert (str): Path to the cacert that will be used to verify + If this is None, verify will be False and the server cert + is not verified by the client. + Returns: + A Client object which can be used to interact with SMS service + """ + + self.base_url = url + self.timeout = timeout + self.cacert = cacert + self.session = requests.Session() + + self._base_api_url = '/v1/sms' + + def _urlJoin(self, *urls): + """Joins given urls into a single url + + Args: + urls (str): url fragments to be combined. + + Returns: + str: Joined URL + """ + + return '/'.join(urls) + + def _raiseException(self, statuscode, errors=None): + """ Handles Exception Raising based on statusCode + + Args: + statuscode (int): status code returned by the server + errors (str): list of strings containing error messages + + Returns: + exception: An exception is raised based on error message + """ + + if statuscode == 400: + raise InvalidRequestException(errors) + if statuscode == 500: + raise InternalServerError(errors) + + raise UnexpectedError(errors) + + def _request(self, method, url, headers=None, **kwargs): + """Handles request for all the client methods + + Args: + method (str): type of HTTP method (get, post or delete). + url (str): api URL. + headers (dict): custom headers if any. + **kwargs: various args supported by requests library + + Returns: + requests.Response: An object containing status_code and returned + json data is returned here. + """ + + if headers is None: + headers = { + 'content-type': "application/json", + 'Accept': "application/json" + } + + #Verify the server or not based on the cacert argument + if self.cacert is None: + verify = False + else: + verify = self.cacert + + url = urlparse.urljoin(self.base_url, url) + response = self.session.request(method, url, headers=headers, + allow_redirects=False, verify=verify, + timeout = self.timeout, **kwargs) + + errors = None + if response.status_code >= 400 and response.status_code < 600: + #Request Failed. Raise Exception. + errors = response.text + self._raiseException(response.status_code, errors) + + return response + + def getStatus(self): + """Returns Status of SMS Service + + Returns: + bool: True or False depending on if SMS Service is ready. + """ + + url = self._urlJoin(self._base_api_url, 'quorum', 'status') + + response = self._request('get', url) + return response.json()['sealstatus'] + + def createDomain(self, domainName): + """Creates a Secret Domain + + Args: + domainName (str): Name of the secret domain to create + + Returns: + string: UUID of the created domain name + """ + + + domainName = domainName.strip() + data = {"name": domainName} + url = self._urlJoin(self._base_api_url, 'domain') + + response = self._request('post', url, json = data) + return response.json()['uuid'] + + def deleteDomain(self, domainName): + """Deletes a Secret Domain + + Args: + domainName (str): Name of the secret domain to delete + + Returns: + bool: True. An exception will be raised if delete failed. + """ + + domainName = domainName.strip() + url = self._urlJoin(self._base_api_url, 'domain', domainName) + + self._request('delete', url) + return True + + def getSecretNames(self, domainName): + """Get all Secret Names in Domain + + Args: + domainName (str): Name of the secret domain + + Returns: + string[]: List of strings each corresponding to a + Secret's Name in this Domain. + """ + + domainName = domainName.strip() + url = self._urlJoin(self._base_api_url, 'domain', domainName, + 'secret') + + response = self._request('get', url) + return response.json()['secretnames'] + + + def storeSecret(self, domainName, secretName, values): + """Store a Secret in given Domain + + Args: + domainName (str): Name of the secret domain + secretName (str): Name for the Secret + values (dict): A dict containing name-value pairs which + form the secret + + Returns: + bool: True. An exception will be raised if store failed. + """ + + domainName = domainName.strip() + secretName = secretName.strip() + url = self._urlJoin(self._base_api_url, 'domain', domainName, + 'secret') + + if not isinstance(values, dict): + raise TypeError('Input values is not a dictionary') + + data = {"name": secretName, "values": values} + self._request('post', url, json = data) + return True + + def getSecret(self, domainName, secretName): + """Get a particular Secret from Domain. + + Args: + domainName (str): Name of the secret domain + secretName (str): Name of the secret + + Returns: + dict: dictionary containing the name-value pairs + which form the secret + """ + + domainName = domainName.strip() + secretName = secretName.strip() + url = self._urlJoin(self._base_api_url, 'domain', domainName, + 'secret', secretName) + + response = self._request('get', url) + return response.json()['values'] + + def deleteSecret(self, domainName, secretName): + """Delete a particular Secret from Domain. + + Args: + domainName (str): Name of the secret domain + secretName (str): Name of the secret + + Returns: + bool: True. An exception will be raised if delete failed. + """ + + domainName = domainName.strip() + secretName = secretName.strip() + url = self._urlJoin(self._base_api_url, 'domain', domainName, + 'secret', secretName) + + self._request('delete', url) + return True \ No newline at end of file