Merge branch 'master' into senslab2
[sfa.git] / sfa / client / sfi.py
index 512bf9a..cc8ef3f 100644 (file)
@@ -13,10 +13,12 @@ import datetime
 import codecs
 import pickle
 import json
 import codecs
 import pickle
 import json
+import shutil
 from lxml import etree
 from StringIO import StringIO
 from optparse import OptionParser
 from pprint import PrettyPrinter
 from lxml import etree
 from StringIO import StringIO
 from optparse import OptionParser
 from pprint import PrettyPrinter
+from tempfile import mkstemp
 
 from sfa.trust.certificate import Keypair, Certificate
 from sfa.trust.gid import GID
 
 from sfa.trust.certificate import Keypair, Certificate
 from sfa.trust.gid import GID
@@ -40,6 +42,7 @@ from sfa.client.sfaclientlib import SfaClientBootstrap
 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
 from sfa.client.return_value import ReturnValue
 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
 from sfa.client.return_value import ReturnValue
+from sfa.client.candidates import Candidates
 
 CM_PORT=12346
 
 
 CM_PORT=12346
 
@@ -112,6 +115,21 @@ def filter_records(type, records):
     return filtered_records
 
 
     return filtered_records
 
 
+def credential_printable (credential_string):
+    credential=Credential(string=credential_string)
+    result=""
+    result += credential.get_summary_tostring()
+    result += "\n"
+    rights = credential.get_privileges()
+    result += "rights=%s"%rights
+    result += "\n"
+    return result
+
+def show_credentials (cred_s):
+    if not isinstance (cred_s,list): cred_s = [cred_s]
+    for cred in cred_s:
+        print "Using Credential %s"%credential_printable(cred)
+
 # save methods
 def save_raw_to_file(var, filename, format="text", banner=None):
     if filename == "-":
 # save methods
 def save_raw_to_file(var, filename, format="text", banner=None):
     if filename == "-":
@@ -179,6 +197,54 @@ def save_record_to_file(filename, record_dict):
     f.close()
     return
 
     f.close()
     return
 
+# used in sfi list
+def terminal_render (records,options):
+    # sort records by type
+    grouped_by_type={}
+    for record in records:
+        type=record['type']
+        if type not in grouped_by_type: grouped_by_type[type]=[]
+        grouped_by_type[type].append(record)
+    group_types=grouped_by_type.keys()
+    group_types.sort()
+    for type in group_types:
+        group=grouped_by_type[type]
+#        print 20 * '-', type
+        try:    renderer=eval('terminal_render_'+type)
+        except: renderer=terminal_render_default
+        for record in group: renderer(record,options)
+
+def render_plural (how_many, name,names=None):
+    if not names: names="%ss"%name
+    if how_many<=0: return "No %s"%name
+    elif how_many==1: return "1 %s"%name
+    else: return "%d %s"%(how_many,names)
+
+def terminal_render_default (record,options):
+    print "%s (%s)" % (record['hrn'], record['type'])
+def terminal_render_user (record, options):
+    print "%s (User)"%record['hrn'],
+    if record.get('reg-pi-authorities',None): print " [PI at %s]"%(" and ".join(record['reg-pi-authorities'])),
+    if record.get('reg-slices',None): print " [IN slices %s]"%(" and ".join(record['reg-slices'])),
+    user_keys=record.get('reg-keys',[])
+    if not options.verbose:
+        print " [has %s]"%(render_plural(len(user_keys),"key"))
+    else:
+        print ""
+        for key in user_keys: print 8*' ',key.strip("\n")
+        
+def terminal_render_slice (record, options):
+    print "%s (Slice)"%record['hrn'],
+    if record.get('reg-researchers',None): print " [USERS %s]"%(" and ".join(record['reg-researchers'])),
+#    print record.keys()
+    print ""
+def terminal_render_authority (record, options):
+    print "%s (Authority)"%record['hrn'],
+    if record.get('reg-pis',None): print " [PIS %s]"%(" and ".join(record['reg-pis'])),
+    print ""
+def terminal_render_node (record, options):
+    print "%s (Node)"%record['hrn']
+
 # minimally check a key argument
 def check_ssh_key (key):
     good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
 # minimally check a key argument
 def check_ssh_key (key):
     good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
@@ -278,8 +344,8 @@ class Sfi:
         ("get_ticket", "slice_hrn rspec"),
         ("redeem_ticket", "ticket"),
         ("delegate", "name"),
         ("get_ticket", "slice_hrn rspec"),
         ("redeem_ticket", "ticket"),
         ("delegate", "name"),
-        ("create_gid", "[name]"),
-        ("get_trusted_certs", "cred"),
+        ("gid", "[name]"),
+        ("trusted", "cred"),
         ("config", ""),
         ]
 
         ("config", ""),
         ]
 
@@ -350,6 +416,10 @@ class Sfi:
                              help="Include a credential delegated to the user's root"+\
                                   "authority in set of credentials for this call")
 
                              help="Include a credential delegated to the user's root"+\
                                   "authority in set of credentials for this call")
 
+        # show_credential option
+        if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
+            parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
+                              help="show credential(s) used in human-readable form")
         # registy filter option
         if command in ("list", "show", "remove"):
             parser.add_option("-t", "--type", dest="type", type="choice",
         # registy filter option
         if command in ("list", "show", "remove"):
             parser.add_option("-t", "--type", dest="type", type="choice",
@@ -381,7 +451,7 @@ class Sfi:
 
 
         # 'create' does return the new rspec, makes sense to save that too
 
 
         # 'create' does return the new rspec, makes sense to save that too
-        if command in ("resources", "show", "list", "create_gid", 'create'):
+        if command in ("resources", "show", "list", "gid", 'create'):
            parser.add_option("-o", "--output", dest="file",
                             help="output XML to file", metavar="FILE", default=None)
 
            parser.add_option("-o", "--output", dest="file",
                             help="output XML to file", metavar="FILE", default=None)
 
@@ -396,6 +466,8 @@ class Sfi:
         if command == 'list':
            parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
                              help="list all child records", default=False)
         if command == 'list':
            parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
                              help="list all child records", default=False)
+           parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
+                             help="gives details, like user keys", default=False)
         if command in ("delegate"):
            parser.add_option("-u", "--user",
                             action="store_true", dest="delegate_user", default=False,
         if command in ("delegate"):
            parser.add_option("-u", "--user",
                             action="store_true", dest="delegate_user", default=False,
@@ -466,7 +538,11 @@ class Sfi:
     # Main: parse arguments and dispatch to command
     #
     def dispatch(self, command, command_options, command_args):
     # Main: parse arguments and dispatch to command
     #
     def dispatch(self, command, command_options, command_args):
-        return getattr(self, command)(command_options, command_args)
+        method=getattr(self, command,None)
+        if not method:
+            print "Unknown command %s"%command
+            return
+        return method(command_options, command_args)
 
     def main(self):
         self.sfi_parser = self.create_parser()
 
     def main(self):
         self.sfi_parser = self.create_parser()
@@ -483,19 +559,26 @@ class Sfi:
             self.print_command_help(options)
             return -1
     
             self.print_command_help(options)
             return -1
     
-        command = args[0]
+        # complete / find unique match with command set
+        command_candidates = Candidates (self.available_names)
+        input = args[0]
+        command = command_candidates.only_match(input)
+        if not command:
+            self.print_command_help(options)
+            sys.exit(1)
+        # second pass options parsing
         self.command_parser = self.create_command_parser(command)
         (command_options, command_args) = self.command_parser.parse_args(args[1:])
         self.command_options = command_options
 
         self.read_config () 
         self.bootstrap ()
         self.command_parser = self.create_command_parser(command)
         (command_options, command_args) = self.command_parser.parse_args(args[1:])
         self.command_options = command_options
 
         self.read_config () 
         self.bootstrap ()
-        self.logger.info("Command=%s" % command)
+        self.logger.debug("Command=%s" % command)
 
         try:
             self.dispatch(command, command_options, command_args)
 
         try:
             self.dispatch(command, command_options, command_args)
-        except KeyError:
-            self.logger.critical ("Unknown command %s"%command)
+        except:
+            self.logger.log_exc ("sfi command %s failed"%command)
             sys.exit(1)
 
         return
             sys.exit(1)
 
         return
@@ -503,16 +586,32 @@ class Sfi:
     ####################
     def read_config(self):
         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
     ####################
     def read_config(self):
         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
+        shell_config_file  = os.path.join(self.options.sfi_dir,"sfi_config.sh")
         try:
         try:
-           config = Config (config_file)
+            if Config.is_ini(config_file):
+                config = Config (config_file)
+            else:
+                # try upgrading from shell config format
+                fp, fn = mkstemp(suffix='sfi_config', text=True)  
+                config = Config(fn)
+                # we need to preload the sections we want parsed 
+                # from the shell config
+                config.add_section('sfi')
+                config.add_section('sface')
+                config.load(config_file)
+                # back up old config
+                shutil.move(config_file, shell_config_file)
+                # write new config
+                config.save(config_file)
+                 
         except:
         except:
-           self.logger.critical("Failed to read configuration file %s"%config_file)
-           self.logger.info("Make sure to remove the export clauses and to add quotes")
-           if self.options.verbose==0:
-               self.logger.info("Re-run with -v for more details")
-           else:
-               self.logger.log_exc("Could not read config file %s"%config_file)
-           sys.exit(1)
+            self.logger.critical("Failed to read configuration file %s"%config_file)
+            self.logger.info("Make sure to remove the export clauses and to add quotes")
+            if self.options.verbose==0:
+                self.logger.info("Re-run with -v for more details")
+            else:
+                self.logger.log_exc("Could not read config file %s"%config_file)
+            sys.exit(1)
      
         errors = 0
         # Set SliceMgr URL
      
         errors = 0
         # Set SliceMgr URL
@@ -530,7 +629,7 @@ class Sfi:
         elif hasattr(config, "SFI_REGISTRY"):
            self.reg_url = config.SFI_REGISTRY
         else:
         elif hasattr(config, "SFI_REGISTRY"):
            self.reg_url = config.SFI_REGISTRY
         else:
-           self.logger.errors("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
+           self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
            errors += 1 
 
         # Set user HRN
            errors += 1 
 
         # Set user HRN
@@ -539,7 +638,7 @@ class Sfi:
         elif hasattr(config, "SFI_USER"):
            self.user = config.SFI_USER
         else:
         elif hasattr(config, "SFI_USER"):
            self.user = config.SFI_USER
         else:
-           self.logger.errors("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
+           self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
            errors += 1 
 
         # Set authority HRN
            errors += 1 
 
         # Set authority HRN
@@ -581,7 +680,8 @@ class Sfi:
     
     # init self-signed cert, user credentials and gid
     def bootstrap (self):
     
     # init self-signed cert, user credentials and gid
     def bootstrap (self):
-        client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir)
+        client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
+                                               logger=self.logger)
         # if -k is provided, use this to initialize private key
         if self.options.user_private_key:
             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
         # if -k is provided, use this to initialize private key
         if self.options.user_private_key:
             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
@@ -591,7 +691,7 @@ class Sfi:
             if not os.path.isfile(client_bootstrap.private_key_filename()):
                 self.logger.info ("private key not found, trying legacy name")
                 try:
             if not os.path.isfile(client_bootstrap.private_key_filename()):
                 self.logger.info ("private key not found, trying legacy name")
                 try:
-                    legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%get_leaf(self.user))
+                    legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
@@ -802,16 +902,17 @@ or version information about sfi itself
         if options.recursive:
             opts['recursive'] = options.recursive
         
         if options.recursive:
             opts['recursive'] = options.recursive
         
+        if options.show_credential:
+            show_credentials(self.my_credential_string)
         try:
             list = self.registry().List(hrn, self.my_credential_string, options)
         except IndexError:
             raise Exception, "Not enough parameters for the 'list' command"
 
         # filter on person, slice, site, node, etc.
         try:
             list = self.registry().List(hrn, self.my_credential_string, options)
         except IndexError:
             raise Exception, "Not enough parameters for the 'list' command"
 
         # filter on person, slice, site, node, etc.
-        # THis really should be in the self.filter_records funct def comment...
+        # This really should be in the self.filter_records funct def comment...
         list = filter_records(options.type, list)
         list = filter_records(options.type, list)
-        for record in list:
-            print "%s (%s)" % (record['hrn'], record['type'])
+        terminal_render (list, options)
         if options.file:
             save_records_to_file(options.file, list, options.fileformat)
         return
         if options.file:
             save_records_to_file(options.file, list, options.fileformat)
         return
@@ -824,7 +925,8 @@ or version information about sfi itself
             self.print_help()
             sys.exit(1)
         hrn = args[0]
             self.print_help()
             sys.exit(1)
         hrn = args[0]
-        record_dicts = self.registry().Resolve(hrn, self.my_credential_string)
+        # explicitly require Resolve to run in details mode
+        record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
         record_dicts = filter_records(options.type, record_dicts)
         if not record_dicts:
             self.logger.error("No record of type %s"% options.type)
         record_dicts = filter_records(options.type, record_dicts)
         if not record_dicts:
             self.logger.error("No record of type %s"% options.type)
@@ -849,6 +951,8 @@ or version information about sfi itself
     def add(self, options, args):
         "add record into registry from xml file (Register)"
         auth_cred = self.my_authority_credential_string()
     def add(self, options, args):
         "add record into registry from xml file (Register)"
         auth_cred = self.my_authority_credential_string()
+        if options.show_credential:
+            show_credentials(auth_cred)
         record_dict = {}
         if len(args) > 0:
             record_filepath = args[0]
         record_dict = {}
         if len(args) > 0:
             record_filepath = args[0]
@@ -906,6 +1010,8 @@ or version information about sfi itself
             cred = self.my_authority_credential_string()
         else:
             raise "unknown record type" + record_dict['type']
             cred = self.my_authority_credential_string()
         else:
             raise "unknown record type" + record_dict['type']
+        if options.show_credential:
+            show_credentials(cred)
         return self.registry().Update(record_dict, cred)
   
     def remove(self, options, args):
         return self.registry().Update(record_dict, cred)
   
     def remove(self, options, args):
@@ -918,6 +1024,8 @@ or version information about sfi itself
         type = options.type 
         if type in ['all']:
             type = '*'
         type = options.type 
         if type in ['all']:
             type = '*'
+        if options.show_credential:
+            show_credentials(auth_cred)
         return self.registry().Remove(hrn, auth_cred, type)
     
     # ==================================================================
         return self.registry().Remove(hrn, auth_cred, type)
     
     # ==================================================================
@@ -935,6 +1043,8 @@ or version information about sfi itself
         # options and call_id when supported
         api_options = {}
        api_options['call_id']=unique_call_id()
         # options and call_id when supported
         api_options = {}
        api_options['call_id']=unique_call_id()
+        if options.show_credential:
+            show_credentials(creds)
         result = server.ListSlices(creds, *self.ois(server,api_options))
         value = ReturnValue.get_value(result)
         if self.options.raw:
         result = server.ListSlices(creds, *self.ois(server,api_options))
         value = ReturnValue.get_value(result)
         if self.options.raw:
@@ -959,6 +1069,8 @@ or with an slice hrn, shows currently provisioned resources
             creds.append(self.my_credential_string)
         if options.delegate:
             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
             creds.append(self.my_credential_string)
         if options.delegate:
             creds.append(self.delegate_cred(cred, get_authority(self.authority)))
+        if options.show_credential:
+            show_credentials(creds)
 
         # no need to check if server accepts the options argument since the options has
         # been a required argument since v1 API
 
         # no need to check if server accepts the options argument since the options has
         # been a required argument since v1 API
@@ -1013,6 +1125,7 @@ or with an slice hrn, shows currently provisioned resources
 
         # credentials
         creds = [self.slice_credential_string(slice_hrn)]
 
         # credentials
         creds = [self.slice_credential_string(slice_hrn)]
+
         delegated_cred = None
         server_version = self.get_cached_server_version(server)
         if server_version.get('interface') == 'slicemgr':
         delegated_cred = None
         server_version = self.get_cached_server_version(server)
         if server_version.get('interface') == 'slicemgr':
@@ -1024,6 +1137,9 @@ or with an slice hrn, shows currently provisioned resources
             #elif server_version.get('urn'):
             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
 
             #elif server_version.get('urn'):
             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
 
+        if options.show_credential:
+            show_credentials(creds)
+
         # rspec
         rspec_file = self.get_rspec_file(args[1])
         rspec = open(rspec_file).read()
         # rspec
         rspec_file = self.get_rspec_file(args[1])
         rspec = open(rspec_file).read()
@@ -1035,10 +1151,14 @@ or with an slice hrn, shows currently provisioned resources
         #    keys: [<ssh key A>, <ssh key B>]
         #  }]
         users = []
         #    keys: [<ssh key A>, <ssh key B>]
         #  }]
         users = []
+        # xxx Thierry 2012 sept. 21
+        # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here
+        # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again
         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
-        if slice_records and 'researcher' in slice_records[0] and slice_records[0]['researcher']!=[]:
+        # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True})
+        if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']:
             slice_record = slice_records[0]
             slice_record = slice_records[0]
-            user_hrns = slice_record['researcher']
+            user_hrns = slice_record['reg-researchers']
             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
 
             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
 
@@ -1090,6 +1210,8 @@ or with an slice hrn, shows currently provisioned resources
         # options and call_id when supported
         api_options = {}
         api_options ['call_id'] = unique_call_id()
         # options and call_id when supported
         api_options = {}
         api_options ['call_id'] = unique_call_id()
+        if options.show_credential:
+            show_credentials(creds)
         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
         value = ReturnValue.get_value(result)
         if self.options.raw:
         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
         value = ReturnValue.get_value(result)
         if self.options.raw:
@@ -1118,6 +1240,8 @@ or with an slice hrn, shows currently provisioned resources
         # options and call_id when supported
         api_options = {}
         api_options['call_id']=unique_call_id()
         # options and call_id when supported
         api_options = {}
         api_options['call_id']=unique_call_id()
+        if options.show_credential:
+            show_credentials(creds)
         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
         value = ReturnValue.get_value(result)
         if self.options.raw:
         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
         value = ReturnValue.get_value(result)
         if self.options.raw:
@@ -1216,6 +1340,8 @@ or with an slice hrn, shows currently provisioned resources
         # options and call_id when supported
         api_options = {}
        api_options['call_id']=unique_call_id()
         # options and call_id when supported
         api_options = {}
        api_options['call_id']=unique_call_id()
+        if options.show_credential:
+            show_credentials(creds)
         result =  server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
         value = ReturnValue.get_value(result)
         if self.options.raw:
         result =  server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
         value = ReturnValue.get_value(result)
         if self.options.raw:
@@ -1317,7 +1443,7 @@ or with an slice hrn, shows currently provisioned resources
                 self.logger.log_exc(e.message)
         return
 
                 self.logger.log_exc(e.message)
         return
 
-    def create_gid(self, options, args):
+    def gid(self, options, args):
         """
         Create a GID (CreateGid)
         """
         """
         Create a GID (CreateGid)
         """
@@ -1360,7 +1486,7 @@ or with an slice hrn, shows currently provisioned resources
 
         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
     
 
         self.logger.info("delegated credential for %s to %s and wrote to %s"%(object_hrn, delegee_hrn,dest_fn))
     
-    def get_trusted_certs(self, options, args):
+    def trusted(self, options, args):
         """
         return uhe trusted certs at this interface (get_trusted_certs)
         """ 
         """
         return uhe trusted certs at this interface (get_trusted_certs)
         """ 
@@ -1369,7 +1495,7 @@ or with an slice hrn, shows currently provisioned resources
             gid = GID(string=trusted_cert)
             gid.dump()
             cert = Certificate(string=trusted_cert)
             gid = GID(string=trusted_cert)
             gid.dump()
             cert = Certificate(string=trusted_cert)
-            self.logger.debug('Sfi.get_trusted_certs -> %r'%cert.get_subject())
+            self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
         return 
 
     def config (self, options, args):
         return 
 
     def config (self, options, args):