 3f9c68f1c6
			
		
	
	3f9c68f1c6
	
	
	
		
			
			Add --os-cacert and --verify|--insecure options using the same sematics as the other project CLIs. --verify is included for completeness. Bug: 1236608 Change-Id: I8a116d790db5aa4cb17a2207efedce7cb229eba3
		
			
				
	
	
		
			190 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			190 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #   Copyright 2013 Nebula 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.
 | |
| #
 | |
| 
 | |
| """REST API bits"""
 | |
| 
 | |
| import json
 | |
| import logging
 | |
| import requests
 | |
| 
 | |
| try:
 | |
|     from urllib.parse import urlencode
 | |
| except ImportError:
 | |
|     from urllib import urlencode
 | |
| 
 | |
| 
 | |
| _logger = logging.getLogger(__name__)
 | |
| 
 | |
| 
 | |
| class RESTApi(object):
 | |
|     """A REST api client that handles the interface from us to the server
 | |
| 
 | |
|     RESTApi is an extension of a requests.Session that knows
 | |
|     how to do:
 | |
|     * JSON serialization/deserialization
 | |
|     * log requests in 'curl' format
 | |
|     * basic API boilerplate for create/delete/list/set/show verbs
 | |
| 
 | |
|     * authentication is handled elsewhere and a token is passed in
 | |
| 
 | |
|     The expectation that there will be a RESTApi object per authentication
 | |
|     token in use, i.e. project/username/auth_endpoint
 | |
| 
 | |
|     On the other hand, a Client knows details about the specific REST Api that
 | |
|     it communicates with, such as the available endpoints, API versions, etc.
 | |
|     """
 | |
| 
 | |
|     USER_AGENT = 'RAPI'
 | |
| 
 | |
|     def __init__(
 | |
|         self,
 | |
|         os_auth=None,
 | |
|         user_agent=USER_AGENT,
 | |
|         debug=None,
 | |
|         verify=True,
 | |
|         **kwargs
 | |
|     ):
 | |
|         self.set_auth(os_auth)
 | |
|         self.debug = debug
 | |
|         self.session = requests.Session(**kwargs)
 | |
| 
 | |
|         self.set_header('User-Agent', user_agent)
 | |
|         self.set_header('Content-Type', 'application/json')
 | |
| 
 | |
|     def set_auth(self, os_auth):
 | |
|         """Sets the current auth blob"""
 | |
|         self.os_auth = os_auth
 | |
| 
 | |
|     def set_header(self, header, content):
 | |
|         """Sets passed in headers into the session headers
 | |
| 
 | |
|         Replaces existing headers!!
 | |
|         """
 | |
|         if content is None:
 | |
|             del self.session.headers[header]
 | |
|         else:
 | |
|             self.session.headers[header] = content
 | |
| 
 | |
|     def request(self, method, url, **kwargs):
 | |
|         if self.os_auth:
 | |
|             self.session.headers.setdefault('X-Auth-Token', self.os_auth)
 | |
|         if 'data' in kwargs and isinstance(kwargs['data'], type({})):
 | |
|             kwargs['data'] = json.dumps(kwargs['data'])
 | |
|         log_request(method, url, headers=self.session.headers, **kwargs)
 | |
|         response = self.session.request(method, url, **kwargs)
 | |
|         log_response(response)
 | |
|         return self._error_handler(response)
 | |
| 
 | |
|     def create(self, url, data=None, response_key=None, **kwargs):
 | |
|         response = self.request('POST', url, data=data, **kwargs)
 | |
|         if response_key:
 | |
|             return response.json()[response_key]
 | |
|         else:
 | |
|             return response.json()
 | |
| 
 | |
|         #with self.completion_cache('human_id', self.resource_class, mode="a"):
 | |
|         #    with self.completion_cache('uuid', self.resource_class, mode="a"):
 | |
|         #        return self.resource_class(self, body[response_key])
 | |
| 
 | |
|     def delete(self, url):
 | |
|         self.request('DELETE', url)
 | |
| 
 | |
|     def list(self, url, data=None, response_key=None, **kwargs):
 | |
|         if data:
 | |
|             response = self.request('POST', url, data=data, **kwargs)
 | |
|         else:
 | |
|             kwargs.setdefault('allow_redirects', True)
 | |
|             response = self.request('GET', url, **kwargs)
 | |
| 
 | |
|         return response.json()[response_key]
 | |
| 
 | |
|         ###hack this for keystone!!!
 | |
|         #data = body[response_key]
 | |
|         # NOTE(ja): keystone returns values as list as {'values': [ ... ]}
 | |
|         #           unlike other services which just return the list...
 | |
|         #if isinstance(data, dict):
 | |
|         #    try:
 | |
|         #        data = data['values']
 | |
|         #    except KeyError:
 | |
|         #        pass
 | |
| 
 | |
|         #with self.completion_cache('human_id', obj_class, mode="w"):
 | |
|         #    with self.completion_cache('uuid', obj_class, mode="w"):
 | |
|         #        return [obj_class(self, res, loaded=True)
 | |
|         #                for res in data if res]
 | |
| 
 | |
|     def set(self, url, data=None, response_key=None, **kwargs):
 | |
|         response = self.request('PUT', url, data=data)
 | |
|         if data:
 | |
|             if response_key:
 | |
|                 return response.json()[response_key]
 | |
|             else:
 | |
|                 return response.json()
 | |
|         else:
 | |
|             return None
 | |
| 
 | |
|     def show(self, url, response_key=None, **kwargs):
 | |
|         response = self.request('GET', url, **kwargs)
 | |
|         if response_key:
 | |
|             return response.json()[response_key]
 | |
|         else:
 | |
|             return response.json()
 | |
| 
 | |
|     def _error_handler(self, response):
 | |
|         if response.status_code < 200 or response.status_code >= 300:
 | |
|             _logger.debug(
 | |
|                 "ERROR: %s",
 | |
|                 response.text,
 | |
|             )
 | |
|             response.raise_for_status()
 | |
|         return response
 | |
| 
 | |
| 
 | |
| def log_request(method, url, **kwargs):
 | |
|     # put in an early exit if debugging is not enabled?
 | |
|     if 'params' in kwargs and kwargs['params'] != {}:
 | |
|         url += '?' + urlencode(kwargs['params'])
 | |
| 
 | |
|     string_parts = [
 | |
|         "curl -i",
 | |
|         "-X '%s'" % method,
 | |
|         "'%s'" % url,
 | |
|     ]
 | |
| 
 | |
|     for element in kwargs['headers']:
 | |
|         header = " -H '%s: %s'" % (element, kwargs['headers'][element])
 | |
|         string_parts.append(header)
 | |
| 
 | |
|     _logger.debug("REQ: %s" % " ".join(string_parts))
 | |
|     if 'data' in kwargs:
 | |
|         _logger.debug("REQ BODY: %s\n" % (kwargs['data']))
 | |
| 
 | |
| 
 | |
| def log_response(response):
 | |
|     _logger.debug(
 | |
|         "RESP: [%s] %s\n",
 | |
|         response.status_code,
 | |
|         response.headers,
 | |
|     )
 | |
|     if response._content_consumed:
 | |
|         _logger.debug(
 | |
|             "RESP BODY: %s\n",
 | |
|             response.text,
 | |
|         )
 | |
|     _logger.debug(
 | |
|         "encoding: %s",
 | |
|         response.encoding,
 | |
|     )
 |