Get endpoint urls for all CLI operations
authorStavros Sachtouris <saxtouri@admin.grnet.gr>
Fri, 31 May 2013 14:37:37 +0000 (17:37 +0300)
committerStavros Sachtouris <saxtouri@admin.grnet.gr>
Fri, 31 May 2013 14:37:37 +0000 (17:37 +0300)
Refs: #3874

Use kamaki.clients.astakos.AstakosClient as a cached astakos client to get
user information and, most importantly, endpoints. Allow users to authenticate
multiple tokens on the same session. In every session there must be at most
one authentication per user/token.

Major change: top kamaki.cli.commands class now contains a base_auth field
with the cached authenticating client. All urls are drained from this field.

15 files changed:
kamaki/cli/__init__.py
kamaki/cli/argument.py
kamaki/cli/command_shell.py
kamaki/cli/commands/__init__.py
kamaki/cli/commands/astakos.py
kamaki/cli/commands/config.py
kamaki/cli/commands/cyclades.py
kamaki/cli/commands/errors.py
kamaki/cli/commands/history.py
kamaki/cli/commands/image.py
kamaki/cli/commands/pithos.py
kamaki/cli/commands/snf-astakos.py
kamaki/cli/config.py
kamaki/cli/one_command.py
kamaki/clients/astakos/__init__.py

index f1ceaed..d53427d 100644 (file)
@@ -208,20 +208,26 @@ def _init_session(arguments):
         remove_colors()
     _silent = arguments['silent'].value
     _setup_logging(_silent, _debug, _verbose, _include)
+    global_url = arguments['config'].get('global', 'url')
+    global_token = arguments['config'].get('global', 'token')
+    from kamaki.clients.astakos import AstakosClient as AuthCachedClient
+    return AuthCachedClient(global_url, global_token)
 
 
 def _load_spec_module(spec, arguments, module):
-    spec_name = arguments['config'].get(spec, 'cli')
-    if spec_name is None:
+    #spec_name = arguments['config'].get('cli', spec)
+    if not spec:
         return None
     pkg = None
     for location in cmd_spec_locations:
-        location += spec_name if location == '' else '.%s' % spec_name
+        location += spec if location == '' else '.%s' % spec
         try:
             pkg = __import__(location, fromlist=[module])
             return pkg
-        except ImportError:
+        except ImportError as ie:
             continue
+    if not pkg:
+        kloger.debug('Loading cmd grp %s failed: %s' % (spec, ie))
     return pkg
 
 
@@ -229,43 +235,44 @@ def _groups_help(arguments):
     global _debug
     global kloger
     descriptions = {}
-    for spec in arguments['config'].get_groups():
+    for cmd_group, spec in arguments['config'].get_cli_specs():
         pkg = _load_spec_module(spec, arguments, '_commands')
         if pkg:
-            cmds = None
-            try:
-                _cnf = arguments['config']
-                cmds = [cmd for cmd in getattr(pkg, '_commands') if _cnf.get(
-                    cmd.name, 'cli')]
-            except AttributeError:
-                if _debug:
-                    kloger.warning('No description for %s' % spec)
+            cmds = getattr(pkg, '_commands')
+            #try:
+            #   #_cnf = arguments['config']
+            #   #cmds = [cmd for cmd in getattr(pkg, '_commands') if _cnf.get(
+            #   #    'cli', cmd.name)]
+            #except AttributeError:
+            #   if _debug:
+            #       kloger.warning('No description for %s' % cmd_group)
             try:
                 for cmd in cmds:
                     descriptions[cmd.name] = cmd.description
             except TypeError:
                 if _debug:
-                    kloger.warning('no cmd specs in module %s' % spec)
+                    kloger.warning(
+                        'No cmd description for module %s' % cmd_group)
         elif _debug:
-            kloger.warning('Loading of %s cmd spec failed' % spec)
+            kloger.warning('Loading of %s cmd spec failed' % cmd_group)
     print('\nOptions:\n - - - -')
     print_dict(descriptions)
 
 
 def _load_all_commands(cmd_tree, arguments):
     _cnf = arguments['config']
-    specs = [spec for spec in _cnf.get_groups() if _cnf.get(spec, 'cli')]
-    for spec in specs:
+    #specs = [spec for spec in _cnf.get_groups() if _cnf.get(spec, 'cli')]
+    for cmd_group, spec in _cnf.get_cli_specs():
         try:
             spec_module = _load_spec_module(spec, arguments, '_commands')
             spec_commands = getattr(spec_module, '_commands')
         except AttributeError:
             if _debug:
                 global kloger
-                kloger.warning('No valid description for %s' % spec)
+                kloger.warning('No valid description for %s' % cmd_group)
             continue
         for spec_tree in spec_commands:
-            if spec_tree.name == spec:
+            if spec_tree.name == cmd_group:
                 cmd_tree.add_tree(spec_tree)
                 break
 
@@ -314,7 +321,7 @@ def print_error_message(cli_err):
         errmsg = red(errmsg)
     stdout.write(errmsg)
     for errmsg in cli_err.details:
-        print('| %s' % errmsg)
+        print('|  %s' % errmsg)
 
 
 def exec_cmd(instance, cmd_args, help_method):
@@ -358,20 +365,20 @@ def set_command_params(parameters):
 
 #  CLI Choice:
 
-def run_one_cmd(exe_string, parser):
+def run_one_cmd(exe_string, parser, auth_base):
     global _history
     _history = History(
         parser.arguments['config'].get('history', 'file'))
     _history.add(' '.join([exe_string] + argv[1:]))
     from kamaki.cli import one_command
-    one_command.run(parser, _help)
+    one_command.run(auth_base, parser, _help)
 
 
-def run_shell(exe_string, parser):
+def run_shell(exe_string, parser, auth_base):
     from command_shell import _init_shell
     shell = _init_shell(exe_string, parser)
     _load_all_commands(shell.cmd_tree, parser.arguments)
-    shell.run(parser)
+    shell.run(auth_base, parser)
 
 
 def main():
@@ -389,18 +396,18 @@ def main():
         filelog = logger.add_file_logger(__name__.split('.')[0])
         filelog.info('* Initial Call *\n%s\n- - -' % ' '.join(argv))
 
-        _init_session(parser.arguments)
+        auth_base = _init_session(parser.arguments)
 
         from kamaki.cli.utils import suggest_missing
         suggest_missing()
 
         if parser.unparsed:
-            run_one_cmd(exe, parser)
+            run_one_cmd(exe, parser, auth_base)
         elif _help:
             parser.parser.print_help()
             _groups_help(parser.arguments)
         else:
-            run_shell(exe, parser)
+            run_shell(exe, parser, auth_base)
     except CLIError as err:
         print_error_message(err)
         if _debug:
index 852147d..4fd5eb3 100644 (file)
@@ -167,7 +167,10 @@ class ConfigArgument(Argument):
         return self.value.get(group, term)
 
     def get_groups(self):
-        return self.value.apis()
+        return self.value.keys('cli')
+
+    def get_cli_specs(self):
+        return self.value.items('cli')
 
 _config_arg = ConfigArgument(
     1, 'Path to configuration file',
index 789b090..f464ac8 100644 (file)
@@ -68,6 +68,7 @@ class Shell(Cmd):
     _context_stack = []
     _prompt_stack = []
     _parser = None
+    auth_base = None
 
     undoc_header = 'interactive shell commands:'
 
@@ -197,11 +198,11 @@ class Shell(Cmd):
                     if subcmd.path == 'history_run':
                         instance = cls(
                             dict(cmd_parser.arguments),
-                            self.cmd_tree)
+                            cmd_tree=self.cmd_tree)
                     else:
-                        instance = cls(dict(cmd_parser.arguments))
+                        instance = cls(
+                            dict(cmd_parser.arguments), self.auth_base)
                     cmd_parser.update_arguments(instance.arguments)
-                    #instance.arguments.pop('config')
                     cmd_parser.arguments = instance.arguments
                     cmd_parser.syntax = '%s %s' % (
                         subcmd.path.replace('_', ' '), cls.syntax)
@@ -296,7 +297,8 @@ class Shell(Cmd):
         hdr = tmp_partition[0].strip()
         return '%s commands:' % hdr
 
-    def run(self, parser, path=''):
+    def run(self, auth_base, parser, path=''):
+        self.auth_base = auth_base
         self._parser = parser
         self._history = History(
             parser.arguments['config'].get('history', 'file'))
index 223ac8a..0ca163c 100644 (file)
@@ -40,7 +40,7 @@ log = get_logger(__name__)
 
 class _command_init(object):
 
-    def __init__(self, arguments={}):
+    def __init__(self, arguments={}, auth_base=None):
         if hasattr(self, 'arguments'):
             arguments.update(self.arguments)
         if isinstance(self, _optional_output_cmd):
@@ -52,6 +52,7 @@ class _command_init(object):
             self.config = self['config']
         except KeyError:
             pass
+        self.auth_base = auth_base or getattr(self, 'auth_base', None)
 
     def _set_log_params(self):
         try:
index 81c9ff7..c395625 100644 (file)
@@ -32,7 +32,7 @@
 # or implied, of GRNET S.A.command
 
 from kamaki.cli import command
-from kamaki.clients.astakos import AstakosClient
+#from kamaki.clients.astakos import AstakosClient
 from kamaki.cli.commands import _command_init, errors, _optional_json
 from kamaki.cli.command_tree import CommandTree
 
@@ -45,11 +45,11 @@ class _user_init(_command_init):
     @errors.generic.all
     @errors.user.load
     def _run(self):
-        token = self.config.get('user', 'token')\
-            or self.config.get('global', 'token')
-        base_url = self.config.get('user', 'url')\
-            or self.config.get('global', 'url')
-        self.client = AstakosClient(base_url=base_url, token=token)
+        #token = self.config.get('user', 'token')\
+        #    or self.config.get('global', 'token')
+        #base_url = self.config.get('global', 'url')
+        #self.client = AstakosClient(base_url=base_url, token=token)
+        self.client = self.auth_base
         self._set_log_params()
         self._update_max_threads()
 
@@ -71,9 +71,8 @@ class user_authenticate(_user_init, _optional_json):
     @errors.user.authenticate
     def _run(self, custom_token=None):
         super(self.__class__, self)._run()
-        self._print(
-            [self.client.authenticate(custom_token)],
-            title=('uuid', 'name',), with_redundancy=True)
+        r = self.auth_base.authenticate(custom_token)
+        self._print([r], title=('uuid', 'name',), with_redundancy=True)
 
     def main(self, custom_token=None):
         self._run(custom_token)
index 08eac3f..2e7e51b 100644 (file)
@@ -41,7 +41,7 @@ _commands = [config_cmds]
 
 about_options = '\nAbout options:\
     \n. syntax: [group.]option\
-    \n. example: file.account\
+    \n. example: file.uuid\
     \n. special case: <option> is equivalent to global.<option>\
     \n. configuration file syntax:\
     \n.   [group]\
index 6f456b1..7a3ab32 100644 (file)
@@ -70,8 +70,10 @@ class _init_cyclades(_command_init):
     def _run(self, service='compute'):
         token = self.config.get(service, 'token')\
             or self.config.get('global', 'token')
-        base_url = self.config.get(service, 'url')\
-            or self.config.get('global', 'url')
+        cyclades_endpoints = self.auth_base.get_service_endpoints(
+            self.config.get('cyclades', 'type'),
+            self.config.get('cyclades', 'version'))
+        base_url = cyclades_endpoints['publicURL']
         self.client = CycladesClient(base_url=base_url, token=token)
         self._set_log_params()
         self._update_max_threads()
index d89eaa6..d490bfa 100644 (file)
@@ -54,7 +54,7 @@ class generic(object):
         return _raise
 
     @classmethod
-    def _connection(this, foo, base_url):
+    def _connection(this, foo):
         def _raise(self, *args, **kwargs):
             try:
                 foo(self, *args, **kwargs)
@@ -68,9 +68,7 @@ class generic(object):
                         '  to get current token: /config get [server.]token'])
                 elif ce.status in range(-12, 200) + [302, 401, 403, 500]:
                     raiseCLIError(ce, importance=3, details=[
-                        'Check if service is up or set to url %s' % base_url,
-                        '  to get url: /config get %s' % base_url,
-                        '  to set url: /config set %s <URL>' % base_url])
+                        'Check if serviceis up'])
                 elif ce.status == 404 and 'kamakihttpresponse' in ce_msg:
                     client = getattr(self, 'client', None)
                     if not client:
@@ -78,9 +76,9 @@ class generic(object):
                     url = getattr(client, 'base_url', '<empty>')
                     msg = 'Invalid service url %s' % url
                     raiseCLIError(ce, msg, details=[
-                        'Please, check if service url is correctly set',
-                        '* to get current url: /config get compute.url',
-                        '* to set url: /config set compute.url <URL>'])
+                        'Check if authentication url is correct',
+                        '  check current url:   /config get url',
+                        '  set new auth. url:   /config set url'])
                 raise
         return _raise
 
@@ -90,8 +88,8 @@ class user(object):
     _token_details = [
         'To check default token: /config get token',
         'If set/update a token:',
-        '*  (permanent):    /config set token <token>',
-        '*  (temporary):    re-run with <token> parameter']
+        '*  (permanent):  /config set token <token>',
+        '*  (temporary):  re-run with <token> parameter']
 
     @classmethod
     def load(this, foo):
@@ -105,11 +103,11 @@ class user(object):
                 kloger.warning(
                     'No permanent token (try: kamaki config set token <tkn>)')
             if not getattr(client, 'base_url', False):
-                msg = 'Missing astakos server URL'
+                msg = 'Missing synnefo URL'
                 raise CLIError(msg, importance=3, details=[
-                    'Check if user.url is set correctly',
-                    'To get astakos url:   /config get user.url',
-                    'To set astakos url:   /config set user.url <URL>'])
+                    'Check if authentication url is correct',
+                        '  check current url:  /config get url',
+                        '  set new auth. url:  /config set url'])
             return r
         return _raise
 
@@ -164,7 +162,7 @@ class cyclades(object):
 
     @classmethod
     def connection(this, foo):
-        return generic._connection(foo, 'compute.url')
+        return generic._connection(foo)
 
     @classmethod
     def date(this, foo):
@@ -360,7 +358,7 @@ class plankton(object):
 
     @classmethod
     def connection(this, foo):
-        return generic._connection(foo, 'image.url')
+        return generic._connection(foo)
 
     @classmethod
     def id(this, foo):
@@ -408,7 +406,7 @@ class pithos(object):
 
     @classmethod
     def connection(this, foo):
-        return generic._connection(foo, 'file.url')
+        return generic._connection(foo)
 
     @classmethod
     def account(this, foo):
index 0e7f727..1b16d67 100644 (file)
@@ -160,7 +160,7 @@ class history_run(_init_history):
 
     _cmd_tree = None
 
-    def __init__(self, arguments={}, cmd_tree=None):
+    def __init__(self, arguments={}, auth_base=None, cmd_tree=None):
         super(self.__class__, self).__init__(arguments)
         self._cmd_tree = cmd_tree
 
index 1402283..5cb44c0 100644 (file)
@@ -77,6 +77,10 @@ class _init_image(_command_init):
         token = self.config.get('image', 'token')\
             or self.config.get('compute', 'token')\
             or self.config.get('global', 'token')
+        plankton_endpoints = self.auth_base.get_service_endpoints(
+            self.config.get('plankton', 'type'),
+            self.config.get('plankton', 'version'))
+        base_url = plankton_endpoints['publicURL']
         base_url = self.config.get('image', 'url')\
             or self.config.get('compute', 'url')\
             or self.config.get('global', 'url')
@@ -305,7 +309,10 @@ class image_register(_init_image, _optional_json):
     def _get_pithos_client(self, container):
         if self['no_metafile_upload']:
             return None
-        purl = self.config.get('file', 'url')
+        pithos_endpoints = self.auth_base.get_service_endpoints(
+            self.config.get('pithos', 'type'),
+            self.config.get('pithos', 'version'))
+        purl = pithos_endpoints['publicURL']
         ptoken = self.client.token
         return PithosClient(purl, ptoken, self._get_uuid(), container)
 
index 2317407..525f1ee 100644 (file)
@@ -154,8 +154,10 @@ class _pithos_init(_command_init):
     def _run(self):
         self.token = self.config.get('file', 'token')\
             or self.config.get('global', 'token')
-        self.base_url = self.config.get('file', 'url')\
-            or self.config.get('global', 'url')
+        pithos_endpoints = self.auth_base.get_service_endpoints(
+            self.config.get('pithos', 'type'),
+            self.config.get('pithos', 'version'))
+        self.base_url = pithos_endpoints['publicURL']
         self._set_account()
         self.container = self.config.get('file', 'container')\
             or self.config.get('global', 'container')
@@ -171,20 +173,14 @@ class _pithos_init(_command_init):
         self._run()
 
     def _set_account(self):
-        user = AstakosClient(self.config.get('user', 'url'), self.token)
-        self.account = self['account'] or user.term('uuid')
-
-        """Backwards compatibility"""
-        self.account = self.account\
-            or self.config.get('file', 'account')\
-            or self.config.get('global', 'account')
+        self.account = self.auth_base.user_term('uuid', self.token)
 
 
 class _file_account_command(_pithos_init):
     """Base class for account level storage commands"""
 
-    def __init__(self, arguments={}):
-        super(_file_account_command, self).__init__(arguments)
+    def __init__(self, arguments={}, auth_base=None):
+        super(_file_account_command, self).__init__(arguments, auth_base)
         self['account'] = ValueArgument(
             'Set user account (not permanent)',
             ('-A', '--account'))
@@ -207,8 +203,8 @@ class _file_container_command(_file_account_command):
     container = None
     path = None
 
-    def __init__(self, arguments={}):
-        super(_file_container_command, self).__init__(arguments)
+    def __init__(self, arguments={}, auth_base=None):
+        super(_file_container_command, self).__init__(arguments, auth_base)
         self['container'] = ValueArgument(
             'Set container to work with (temporary)',
             ('-C', '--container'))
@@ -525,9 +521,10 @@ class _source_destination_command(_file_container_command):
         suffix_replace=ValueArgument('', '--suffix-to-replace', default=''),
     )
 
-    def __init__(self, arguments={}):
+    def __init__(self, arguments={}, auth_base=None):
         self.arguments.update(arguments)
-        super(_source_destination_command, self).__init__(self.arguments)
+        super(_source_destination_command, self).__init__(
+            self.arguments, auth_base)
 
     def _run(self, source_container___path, path_is_optional=False):
         super(_source_destination_command, self)._run(
@@ -1480,8 +1477,8 @@ class file_delete(_file_container_command, _optional_output_cmd):
             ('-R', '--recursive'))
     )
 
-    def __init__(self, arguments={}):
-        super(self.__class__, self).__init__(arguments)
+    def __init__(self, arguments={}, auth_base=None):
+        super(self.__class__, self).__init__(arguments, auth_base)
         self['delimiter'] = DelimiterArgument(
             self,
             parsed_name='--delimiter',
index fa8d3f4..39dae6b 100644 (file)
@@ -50,8 +50,8 @@ log = add_stream_logger(__name__)
 
 class _astakos_init(_command_init):
 
-    def __init__(self, arguments=dict()):
-        super(_astakos_init, self).__init__(arguments)
+    def __init__(self, arguments=dict(), auth_base=None):
+        super(_astakos_init, self).__init__(arguments, auth_base)
         self['token'] = ValueArgument('Custom token', '--token')
 
     @errors.generic.all
@@ -61,9 +61,10 @@ class _astakos_init(_command_init):
             or self.config.get('astakos', 'token')\
             or self.config.get('user', 'token')\
             or self.config.get('global', 'token')
-        base_url = self.config.get('astakos', 'url')\
-            or self.config.get('user', 'url')\
-            or self.config.get('global', 'url')
+        astakos_endpoints = self.auth_base.get_service_endpoints(
+            self.config.get('astakos', 'type'),
+            self.config.get('astakos', 'version'))
+        base_url = astakos_endpoints['publicURL']
         self.client = AstakosClient(base_url, logger=log)
         self._set_log_params()
         self._update_max_threads()
index 280f2a3..9073b67 100644 (file)
@@ -108,12 +108,20 @@ class Config(RawConfigParser):
             for option, val in options.items():
                 self.set(section, option, val)
 
+    def _get_dict(self, section, include_defaults=True):
+        try:
+            d = dict(DEFAULTS[section]) if include_defaults else {}
+        except KeyError:
+            d = {}
+        try:
+            d.update(RawConfigParser.items(self, section))
+        except NoSectionError:
+            pass
+        return d
+
     def reload(self):
         self = self.__init__(self.path)
 
-    def apis(self):
-        return [api for api in self.sections() if api != 'global']
-
     def get(self, section, option):
         value = self._overrides.get(section, {}).get(option)
         if value is not None:
@@ -137,15 +145,12 @@ class Config(RawConfigParser):
         except NoSectionError:
             pass
 
+    def keys(self, section, include_defaults=True):
+        d = self._get_dict(section, include_defaults)
+        return d.keys()
+
     def items(self, section, include_defaults=True):
-        try:
-            d = dict(DEFAULTS[section]) if include_defaults else {}
-        except KeyError:
-            d = {}
-        try:
-            d.update(RawConfigParser.items(self, section))
-        except NoSectionError:
-            pass
+        d = self._get_dict(section, include_defaults)
         return d.items()
 
     def override(self, section, option, value):
index bcca9a2..5aeee9f 100644 (file)
@@ -55,7 +55,7 @@ def _get_best_match_from_cmd_tree(cmd_tree, unparsed):
     return None
 
 
-def run(parser, _help):
+def run(auth_base, parser, _help):
     group = get_command_group(list(parser.unparsed), parser.arguments)
     if not group:
         parser.parser.print_help()
@@ -68,7 +68,8 @@ def run(parser, _help):
     global _best_match
     _best_match = []
 
-    spec_module = _load_spec_module(group, parser.arguments, '_commands')
+    group_spec = parser.arguments['config'].get('cli', group)
+    spec_module = _load_spec_module(group_spec, parser.arguments, '_commands')
     if spec_module is None:
         raise CLIUnknownCommand(
             'Could not find specs for %s commands' % group,
@@ -95,7 +96,7 @@ def run(parser, _help):
         exit(0)
 
     cls = cmd.get_class()
-    executable = cls(parser.arguments)
+    executable = cls(parser.arguments, auth_base)
     parser.update_arguments(executable.arguments)
     #parsed, unparsed = parse_known_args(parser, executable.arguments)
     for term in _best_match:
index 77a5fe4..a9b631c 100644 (file)
@@ -35,8 +35,6 @@ from kamaki.clients import Client, ClientError
 from logging import getLogger
 
 
-
-
 class AstakosClient(Client):
     """Synnefo Astakos API client"""
 
@@ -139,4 +137,8 @@ class AstakosClient(Client):
 
     def term(self, key, token=None):
         """Get (cached) term, from user credentials"""
+        return self.user_term(key, token)
+
+    def user_term(self, key, token=None):
+        """Get (cached) term, from user credentials"""
         return self.user_info(token).get(key, None)