diff options
Diffstat (limited to 'troveclient/common.py')
| -rw-r--r-- | troveclient/common.py | 406 |
1 files changed, 406 insertions, 0 deletions
diff --git a/troveclient/common.py b/troveclient/common.py new file mode 100644 index 0000000..d10fb22 --- /dev/null +++ b/troveclient/common.py @@ -0,0 +1,406 @@ +# Copyright 2011 OpenStack LLC +# +# 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 copy +import json +import optparse +import os +import pickle +import sys + +from troveclient import client +from troveclient.xml import TroveXmlClient +from troveclient import exceptions + +from urllib import quote + + +def methods_of(obj): + """Get all callable methods of an object that don't start with underscore + returns a list of tuples of the form (method_name, method)""" + result = {} + for i in dir(obj): + if callable(getattr(obj, i)) and not i.startswith('_'): + result[i] = getattr(obj, i) + return result + + +def check_for_exceptions(resp, body): + if resp.status in (400, 422, 500): + raise exceptions.from_response(resp, body) + + +def print_actions(cmd, actions): + """Print help for the command with list of options and description""" + print ("Available actions for '%s' cmd:") % cmd + for k, v in actions.iteritems(): + print "\t%-20s%s" % (k, v.__doc__) + sys.exit(2) + + +def print_commands(commands): + """Print the list of available commands and description""" + + print "Available commands" + for k, v in commands.iteritems(): + print "\t%-20s%s" % (k, v.__doc__) + sys.exit(2) + + +def limit_url(url, limit=None, marker=None): + if not limit and not marker: + return url + query = [] + if marker: + query.append("marker=%s" % marker) + if limit: + query.append("limit=%s" % limit) + query = '?' + '&'.join(query) + return url + query + + +def quote_user_host(user, host): + quoted = '' + if host: + quoted = quote("%s@%s" % (user, host)) + else: + quoted = quote("%s" % user) + return quoted.replace('.', '%2e') + + +class CliOptions(object): + """A token object containing the user, apikey and token which + is pickleable.""" + + APITOKEN = os.path.expanduser("~/.apitoken") + + DEFAULT_VALUES = { + 'username': None, + 'apikey': None, + 'tenant_id': None, + 'auth_url': None, + 'auth_type': 'keystone', + 'service_type': 'database', + 'service_name': 'trove', + 'region': 'RegionOne', + 'service_url': None, + 'insecure': False, + 'verbose': False, + 'debug': False, + 'token': None, + 'xml': None, + } + + def __init__(self, **kwargs): + for key, value in self.DEFAULT_VALUES.items(): + setattr(self, key, value) + + @classmethod + def default(cls): + kwargs = copy.deepcopy(cls.DEFAULT_VALUES) + return cls(**kwargs) + + @classmethod + def load_from_file(cls): + try: + with open(cls.APITOKEN, 'rb') as token: + return pickle.load(token) + except IOError: + pass # File probably not found. + except: + print("ERROR: Token file found at %s was corrupt." % cls.APITOKEN) + return cls.default() + + @classmethod + def save_from_instance_fields(cls, instance): + apitoken = cls.default() + for key, default_value in cls.DEFAULT_VALUES.items(): + final_value = getattr(instance, key, default_value) + setattr(apitoken, key, final_value) + with open(cls.APITOKEN, 'wb') as token: + pickle.dump(apitoken, token, protocol=2) + + @classmethod + def create_optparser(cls, load_file): + oparser = optparse.OptionParser( + usage="%prog [options] <cmd> <action> <args>", + version='1.0', conflict_handler='resolve') + if load_file: + file = cls.load_from_file() + else: + file = cls.default() + + def add_option(*args, **kwargs): + if len(args) == 1: + name = args[0] + else: + name = args[1] + kwargs['default'] = getattr(file, name, cls.DEFAULT_VALUES[name]) + oparser.add_option("--%s" % name, **kwargs) + + add_option("verbose", action="store_true", + help="Show equivalent curl statement along " + "with actual HTTP communication.") + add_option("debug", action="store_true", + help="Show the stack trace on errors.") + add_option("auth_url", help="Auth API endpoint URL with port and " + "version. Default: http://localhost:5000/v2.0") + add_option("username", help="Login username") + add_option("apikey", help="Api key") + add_option("tenant_id", + help="Tenant Id associated with the account") + add_option("auth_type", + help="Auth type to support different auth environments, \ + Supported values are 'keystone', 'rax'.") + add_option("service_type", + help="Service type is a name associated for the catalog") + add_option("service_name", + help="Service name as provided in the service catalog") + add_option("service_url", + help="Service endpoint to use if the catalog doesn't have one.") + add_option("region", help="Region the service is located in") + add_option("insecure", action="store_true", + help="Run in insecure mode for https endpoints.") + add_option("token", help="Token from a prior login.") + add_option("xml", action="store_true", help="Changes format to XML.") + + oparser.add_option("--secure", action="store_false", dest="insecure", + help="Run in insecure mode for https endpoints.") + oparser.add_option("--json", action="store_false", dest="xml", + help="Changes format to JSON.") + oparser.add_option("--terse", action="store_false", dest="verbose", + help="Toggles verbose mode off.") + oparser.add_option("--hide-debug", action="store_false", dest="debug", + help="Toggles debug mode off.") + return oparser + + +class ArgumentRequired(Exception): + def __init__(self, param): + self.param = param + + def __str__(self): + return 'Argument "--%s" required.' % self.param + + +class CommandsBase(object): + params = [] + + def __init__(self, parser): + self._parse_options(parser) + + def _get_client(self): + """Creates the all important client object.""" + try: + if self.xml: + client_cls = TroveXmlClient + else: + client_cls = client.TroveHTTPClient + if self.verbose: + client.log_to_streamhandler(sys.stdout) + client.RDC_PP = True + return client.Dbaas(self.username, self.apikey, self.tenant_id, + auth_url=self.auth_url, + auth_strategy=self.auth_type, + service_type=self.service_type, + service_name=self.service_name, + region_name=self.region, + service_url=self.service_url, + insecure=self.insecure, + client_cls=client_cls) + except: + if self.debug: + raise + print sys.exc_info()[1] + + def _safe_exec(self, func, *args, **kwargs): + if not self.debug: + try: + return func(*args, **kwargs) + except: + print(sys.exc_info()[1]) + return None + else: + return func(*args, **kwargs) + + @classmethod + def _prepare_parser(cls, parser): + for param in cls.params: + parser.add_option("--%s" % param) + + def _parse_options(self, parser): + opts, args = parser.parse_args() + for param in opts.__dict__: + value = getattr(opts, param) + setattr(self, param, value) + + def _require(self, *params): + for param in params: + if not hasattr(self, param): + raise ArgumentRequired(param) + if not getattr(self, param): + raise ArgumentRequired(param) + + def _make_list(self, *params): + # Convert the listed params to lists. + for param in params: + raw = getattr(self, param) + if isinstance(raw, list): + return + raw = [item.strip() for item in raw.split(',')] + setattr(self, param, raw) + + def _pretty_print(self, func, *args, **kwargs): + if self.verbose: + self._safe_exec(func, *args, **kwargs) + return # Skip this, since the verbose stuff will show up anyway. + + def wrapped_func(): + result = func(*args, **kwargs) + if result: + print(json.dumps(result._info, sort_keys=True, indent=4)) + else: + print("OK") + self._safe_exec(wrapped_func) + + def _dumps(self, item): + return json.dumps(item, sort_keys=True, indent=4) + + def _pretty_list(self, func, *args, **kwargs): + result = self._safe_exec(func, *args, **kwargs) + if self.verbose: + return + if result and len(result) > 0: + for item in result: + print(self._dumps(item._info)) + else: + print("OK") + + def _pretty_paged(self, func, *args, **kwargs): + try: + limit = self.limit + if limit: + limit = int(limit, 10) + result = func(*args, limit=limit, marker=self.marker, **kwargs) + if self.verbose: + return # Verbose already shows the output, so skip this. + if result and len(result) > 0: + for item in result: + print self._dumps(item._info) + if result.links: + print("Links:") + for link in result.links: + print self._dumps((link)) + else: + print("OK") + except: + if self.debug: + raise + print sys.exc_info()[1] + + +class Auth(CommandsBase): + """Authenticate with your username and api key""" + params = [ + 'apikey', + 'auth_strategy', + 'auth_type', + 'auth_url', + 'options', + 'region', + 'service_name', + 'service_type', + 'service_url', + 'tenant_id', + 'username', + ] + + def __init__(self, parser): + super(Auth, self).__init__(parser) + self.dbaas = None + + def login(self): + """Login to retrieve an auth token to use for other api calls""" + self._require('username', 'apikey', 'tenant_id', 'auth_url') + try: + self.dbaas = self._get_client() + self.dbaas.authenticate() + self.token = self.dbaas.client.auth_token + self.service_url = self.dbaas.client.service_url + CliOptions.save_from_instance_fields(self) + print("Token aquired! Saving to %s..." % CliOptions.APITOKEN) + print(" service_url = %s" % self.service_url) + print(" token = %s" % self.token) + except: + if self.debug: + raise + print sys.exc_info()[1] + + +class AuthedCommandsBase(CommandsBase): + """Commands that work only with an authicated client.""" + + def __init__(self, parser): + """Makes sure a token is available somehow and logs in.""" + super(AuthedCommandsBase, self).__init__(parser) + try: + self._require('token') + except ArgumentRequired: + if self.debug: + raise + print('No token argument supplied. Use the "auth login" command ' + 'to log in and get a token.\n') + sys.exit(1) + try: + self._require('service_url') + except ArgumentRequired: + if self.debug: + raise + print('No service_url given.\n') + sys.exit(1) + self.dbaas = self._get_client() + # Actually set the token to avoid a re-auth. + self.dbaas.client.auth_token = self.token + self.dbaas.client.authenticate_with_token(self.token, self.service_url) + + +class Paginated(object): + """ Pretends to be a list if you iterate over it, but also keeps a + next property you can use to get the next page of data. """ + + def __init__(self, items=[], next_marker=None, links=[]): + self.items = items + self.next = next_marker + self.links = links + + def __len__(self): + return len(self.items) + + def __iter__(self): + return self.items.__iter__() + + def __getitem__(self, key): + return self.items[key] + + def __setitem__(self, key, value): + self.items[key] = value + + def __delitem__(self, key): + del self.items[key] + + def __reversed__(self): + return reversed(self.items) + + def __contains__(self, needle): + return needle in self.items |
