nicer output of sfi (catches SystemExit)
[sfa.git] / sfa / client / sfi.py
1 #
2 # sfi.py - basic SFA command-line client
3 # this module is also used in sfascan
4 #
5
6 import sys
7 sys.path.append('.')
8
9 import os, os.path
10 import socket
11 import re
12 import datetime
13 import codecs
14 import pickle
15 import json
16 import shutil
17 from lxml import etree
18 from StringIO import StringIO
19 from optparse import OptionParser
20 from pprint import PrettyPrinter
21 from tempfile import mkstemp
22
23 from sfa.trust.certificate import Keypair, Certificate
24 from sfa.trust.gid import GID
25 from sfa.trust.credential import Credential
26 from sfa.trust.sfaticket import SfaTicket
27
28 from sfa.util.faults import SfaInvalidArgument
29 from sfa.util.sfalogging import sfi_logger
30 from sfa.util.xrn import get_leaf, get_authority, hrn_to_urn, Xrn
31 from sfa.util.config import Config
32 from sfa.util.version import version_core
33 from sfa.util.cache import Cache
34
35 from sfa.storage.record import Record
36
37 from sfa.rspecs.rspec import RSpec
38 from sfa.rspecs.rspec_converter import RSpecConverter
39 from sfa.rspecs.version_manager import VersionManager
40
41 from sfa.client.sfaclientlib import SfaClientBootstrap
42 from sfa.client.sfaserverproxy import SfaServerProxy, ServerException
43 from sfa.client.client_helper import pg_users_arg, sfa_users_arg
44 from sfa.client.return_value import ReturnValue
45 from sfa.client.candidates import Candidates
46
47 CM_PORT=12346
48
49 from sfa.client.common import optparse_listvalue_callback, optparse_dictvalue_callback, \
50     terminal_render, filter_records 
51
52 # display methods
53 def display_rspec(rspec, format='rspec'):
54     if format in ['dns']:
55         tree = etree.parse(StringIO(rspec))
56         root = tree.getroot()
57         result = root.xpath("./network/site/node/hostname/text()")
58     elif format in ['ip']:
59         # The IP address is not yet part of the new RSpec
60         # so this doesn't do anything yet.
61         tree = etree.parse(StringIO(rspec))
62         root = tree.getroot()
63         result = root.xpath("./network/site/node/ipv4/text()")
64     else:
65         result = rspec
66
67     print result
68     return
69
70 def display_list(results):
71     for result in results:
72         print result
73
74 def display_records(recordList, dump=False):
75     ''' Print all fields in the record'''
76     for record in recordList:
77         display_record(record, dump)
78
79 def display_record(record, dump=False):
80     if dump:
81         record.dump(sort=True)
82     else:
83         info = record.getdict()
84         print "%s (%s)" % (info['hrn'], info['type'])
85     return
86
87
88 def credential_printable (credential_string):
89     credential=Credential(string=credential_string)
90     result=""
91     result += credential.get_summary_tostring()
92     result += "\n"
93     rights = credential.get_privileges()
94     result += "rights=%s"%rights
95     result += "\n"
96     return result
97
98 def show_credentials (cred_s):
99     if not isinstance (cred_s,list): cred_s = [cred_s]
100     for cred in cred_s:
101         print "Using Credential %s"%credential_printable(cred)
102
103 # save methods
104 def save_raw_to_file(var, filename, format="text", banner=None):
105     if filename == "-":
106         # if filename is "-", send it to stdout
107         f = sys.stdout
108     else:
109         f = open(filename, "w")
110     if banner:
111         f.write(banner+"\n")
112     if format == "text":
113         f.write(str(var))
114     elif format == "pickled":
115         f.write(pickle.dumps(var))
116     elif format == "json":
117         if hasattr(json, "dumps"):
118             f.write(json.dumps(var))   # python 2.6
119         else:
120             f.write(json.write(var))   # python 2.5
121     else:
122         # this should never happen
123         print "unknown output format", format
124     if banner:
125         f.write('\n'+banner+"\n")
126
127 def save_rspec_to_file(rspec, filename):
128     if not filename.endswith(".rspec"):
129         filename = filename + ".rspec"
130     f = open(filename, 'w')
131     f.write(rspec)
132     f.close()
133     return
134
135 def save_records_to_file(filename, record_dicts, format="xml"):
136     if format == "xml":
137         index = 0
138         for record_dict in record_dicts:
139             if index > 0:
140                 save_record_to_file(filename + "." + str(index), record_dict)
141             else:
142                 save_record_to_file(filename, record_dict)
143             index = index + 1
144     elif format == "xmllist":
145         f = open(filename, "w")
146         f.write("<recordlist>\n")
147         for record_dict in record_dicts:
148             record_obj=Record(dict=record_dict)
149             f.write('<record hrn="' + record_obj.hrn + '" type="' + record_obj.type + '" />\n')
150         f.write("</recordlist>\n")
151         f.close()
152     elif format == "hrnlist":
153         f = open(filename, "w")
154         for record_dict in record_dicts:
155             record_obj=Record(dict=record_dict)
156             f.write(record_obj.hrn + "\n")
157         f.close()
158     else:
159         # this should never happen
160         print "unknown output format", format
161
162 def save_record_to_file(filename, record_dict):
163     record = Record(dict=record_dict)
164     xml = record.save_as_xml()
165     f=codecs.open(filename, encoding='utf-8',mode="w")
166     f.write(xml)
167     f.close()
168     return
169
170 # minimally check a key argument
171 def check_ssh_key (key):
172     good_ssh_key = r'^.*(?:ssh-dss|ssh-rsa)[ ]+[A-Za-z0-9+/=]+(?: .*)?$'
173     return re.match(good_ssh_key, key, re.IGNORECASE)
174
175 # load methods
176 def load_record_from_opts(options):
177     record_dict = {}
178     if hasattr(options, 'xrn') and options.xrn:
179         if hasattr(options, 'type') and options.type:
180             xrn = Xrn(options.xrn, options.type)
181         else:
182             xrn = Xrn(options.xrn)
183         record_dict['urn'] = xrn.get_urn()
184         record_dict['hrn'] = xrn.get_hrn()
185         record_dict['type'] = xrn.get_type()
186     if hasattr(options, 'key') and options.key:
187         try:
188             pubkey = open(options.key, 'r').read()
189         except IOError:
190             pubkey = options.key
191         if not check_ssh_key (pubkey):
192             raise SfaInvalidArgument(name='key',msg="Could not find file, or wrong key format")
193         record_dict['keys'] = [pubkey]
194     if hasattr(options, 'slices') and options.slices:
195         record_dict['slices'] = options.slices
196     if hasattr(options, 'researchers') and options.researchers:
197         record_dict['researcher'] = options.researchers
198     if hasattr(options, 'email') and options.email:
199         record_dict['email'] = options.email
200     if hasattr(options, 'pis') and options.pis:
201         record_dict['pi'] = options.pis
202
203     # handle extra settings
204     record_dict.update(options.extras)
205     
206     return Record(dict=record_dict)
207
208 def load_record_from_file(filename):
209     f=codecs.open(filename, encoding="utf-8", mode="r")
210     xml_string = f.read()
211     f.close()
212     return Record(xml=xml_string)
213
214
215 import uuid
216 def unique_call_id(): return uuid.uuid4().urn
217
218 class Sfi:
219     
220     # dirty hack to make this class usable from the outside
221     required_options=['verbose',  'debug',  'registry',  'sm',  'auth',  'user', 'user_private_key']
222
223     @staticmethod
224     def default_sfi_dir ():
225         if os.path.isfile("./sfi_config"): 
226             return os.getcwd()
227         else:
228             return os.path.expanduser("~/.sfi/")
229
230     # dummy to meet Sfi's expectations for its 'options' field
231     # i.e. s/t we can do setattr on
232     class DummyOptions:
233         pass
234
235     def __init__ (self,options=None):
236         if options is None: options=Sfi.DummyOptions()
237         for opt in Sfi.required_options:
238             if not hasattr(options,opt): setattr(options,opt,None)
239         if not hasattr(options,'sfi_dir'): options.sfi_dir=Sfi.default_sfi_dir()
240         self.options = options
241         self.user = None
242         self.authority = None
243         self.logger = sfi_logger
244         self.logger.enable_console()
245         self.available_names = [ tuple[0] for tuple in Sfi.available ]
246         self.available_dict = dict (Sfi.available)
247    
248     # tuples command-name expected-args in the order in which they should appear in the help
249     available = [ 
250         ("version", ""),  
251         ("list", "authority"),
252         ("show", "name"),
253         ("add", "[record]"),
254         ("update", "[record]"),
255         ("remove", "name"),
256         ("slices", ""),
257         ("resources", "[slice_hrn]"),
258         ("create", "slice_hrn rspec"),
259         ("delete", "slice_hrn"),
260         ("status", "slice_hrn"),
261         ("start", "slice_hrn"),
262         ("stop", "slice_hrn"),
263         ("reset", "slice_hrn"),
264         ("renew", "slice_hrn time"),
265         ("shutdown", "slice_hrn"),
266         ("get_ticket", "slice_hrn rspec"),
267         ("redeem_ticket", "ticket"),
268         ("delegate", "to_hrn"),
269         ("gid", "[name]"),
270         ("trusted", "cred"),
271         ("config", ""),
272         ]
273
274     def print_command_help (self, options):
275         verbose=getattr(options,'verbose')
276         format3="%18s %-15s %s"
277         line=80*'-'
278         if not verbose:
279             print format3%("command","cmd_args","description")
280             print line
281         else:
282             print line
283             self.create_parser().print_help()
284         for command in self.available_names:
285             args=self.available_dict[command]
286             method=getattr(self,command,None)
287             doc=""
288             if method: doc=getattr(method,'__doc__',"")
289             if not doc: doc="*** no doc found ***"
290             doc=doc.strip(" \t\n")
291             doc=doc.replace("\n","\n"+35*' ')
292             if verbose:
293                 print line
294             print format3%(command,args,doc)
295             if verbose:
296                 self.create_command_parser(command).print_help()
297
298     def create_command_parser(self, command):
299         if command not in self.available_dict:
300             msg="Invalid command\n"
301             msg+="Commands: "
302             msg += ','.join(self.available_names)            
303             self.logger.critical(msg)
304             sys.exit(2)
305
306         parser = OptionParser(usage="sfi [sfi_options] %s [cmd_options] %s" \
307                                      % (command, self.available_dict[command]))
308
309         if command in ("add", "update"):
310             parser.add_option('-x', '--xrn', dest='xrn', metavar='<xrn>', help='object hrn/urn (mandatory)')
311             parser.add_option('-t', '--type', dest='type', metavar='<type>', help='object type', default=None)
312             parser.add_option('-e', '--email', dest='email', default="",  help="email (mandatory for users)") 
313             parser.add_option('-k', '--key', dest='key', metavar='<key>', help='public key string or file', 
314                               default=None)
315             parser.add_option('-s', '--slices', dest='slices', metavar='<slices>', help='Set/replace slice xrns',
316                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
317             parser.add_option('-r', '--researchers', dest='researchers', metavar='<researchers>', 
318                               help='Set/replace slice researchers', default='', type="str", action='callback', 
319                               callback=optparse_listvalue_callback)
320             parser.add_option('-p', '--pis', dest='pis', metavar='<PIs>', help='Set/replace Principal Investigators/Project Managers',
321                               default='', type="str", action='callback', callback=optparse_listvalue_callback)
322             parser.add_option ('-X','--extra',dest='extras',default={},type='str',metavar="<EXTRA_ASSIGNS>",
323                                action="callback", callback=optparse_dictvalue_callback, nargs=1,
324                                help="set extra/testbed-dependent flags, e.g. --extra enabled=true")
325
326         # show_credential option
327         if command in ("list","resources","create","add","update","remove","slices","delete","status","renew"):
328             parser.add_option("-C","--credential",dest='show_credential',action='store_true',default=False,
329                               help="show credential(s) used in human-readable form")
330         # registy filter option
331         if command in ("list", "show", "remove"):
332             parser.add_option("-t", "--type", dest="type", type="choice",
333                             help="type filter ([all]|user|slice|authority|node|aggregate)",
334                             choices=("all", "user", "slice", "authority", "node", "aggregate"),
335                             default="all")
336         if command in ("show"):
337             parser.add_option("-k","--key",dest="keys",action="append",default=[],
338                               help="specify specific keys to be displayed from record")
339         if command in ("resources"):
340             # rspec version
341             parser.add_option("-r", "--rspec-version", dest="rspec_version", default="SFA 1",
342                               help="schema type and version of resulting RSpec")
343             # disable/enable cached rspecs
344             parser.add_option("-c", "--current", dest="current", default=False,
345                               action="store_true",  
346                               help="Request the current rspec bypassing the cache. Cached rspecs are returned by default")
347             # display formats
348             parser.add_option("-f", "--format", dest="format", type="choice",
349                              help="display format ([xml]|dns|ip)", default="xml",
350                              choices=("xml", "dns", "ip"))
351             #panos: a new option to define the type of information about resources a user is interested in
352             parser.add_option("-i", "--info", dest="info",
353                                 help="optional component information", default=None)
354             # a new option to retreive or not reservation-oriented RSpecs (leases)
355             parser.add_option("-l", "--list_leases", dest="list_leases", type="choice",
356                                 help="Retreive or not reservation-oriented RSpecs ([resources]|leases|all )",
357                                 choices=("all", "resources", "leases"), default="resources")
358
359
360         # 'create' does return the new rspec, makes sense to save that too
361         if command in ("resources", "show", "list", "gid", 'create'):
362            parser.add_option("-o", "--output", dest="file",
363                             help="output XML to file", metavar="FILE", default=None)
364
365         if command in ("show", "list"):
366            parser.add_option("-f", "--format", dest="format", type="choice",
367                              help="display format ([text]|xml)", default="text",
368                              choices=("text", "xml"))
369
370            parser.add_option("-F", "--fileformat", dest="fileformat", type="choice",
371                              help="output file format ([xml]|xmllist|hrnlist)", default="xml",
372                              choices=("xml", "xmllist", "hrnlist"))
373         if command == 'list':
374            parser.add_option("-r", "--recursive", dest="recursive", action='store_true',
375                              help="list all child records", default=False)
376            parser.add_option("-v", "--verbose", dest="verbose", action='store_true',
377                              help="gives details, like user keys", default=False)
378         if command in ("delegate"):
379            parser.add_option("-u", "--user",
380                              action="store_true", dest="delegate_user", default=False,
381                              help="delegate your own credentials; default if no other option is provided")
382            parser.add_option("-s", "--slice", dest="delegate_slices",action='append',default=[],
383                              metavar="slice_hrn", help="delegate cred. for slice HRN")
384            parser.add_option("-a", "--auths", dest='delegate_auths',action='append',default=[],
385                              metavar='auth_hrn', help="delegate cred for auth HRN")
386            # this primarily is a shorthand for -a my_hrn^
387            parser.add_option("-p", "--pi", dest='delegate_pi', default=None, action='store_true',
388                              help="delegate your PI credentials, so s.t. like -a your_hrn^")
389            parser.add_option("-A","--to-authority",dest='delegate_to_authority',action='store_true',default=False,
390                              help="""by default the mandatory argument is expected to be a user, 
391 use this if you mean an authority instead""")
392         
393         if command in ("version"):
394             parser.add_option("-R","--registry-version",
395                               action="store_true", dest="version_registry", default=False,
396                               help="probe registry version instead of sliceapi")
397             parser.add_option("-l","--local",
398                               action="store_true", dest="version_local", default=False,
399                               help="display version of the local client")
400
401         return parser
402
403         
404     def create_parser(self):
405
406         # Generate command line parser
407         parser = OptionParser(usage="sfi [sfi_options] command [cmd_options] [cmd_args]",
408                              description="Commands: %s"%(" ".join(self.available_names)))
409         parser.add_option("-r", "--registry", dest="registry",
410                          help="root registry", metavar="URL", default=None)
411         parser.add_option("-s", "--sliceapi", dest="sm", default=None, metavar="URL",
412                          help="slice API - in general a SM URL, but can be used to talk to an aggregate")
413         parser.add_option("-R", "--raw", dest="raw", default=None,
414                           help="Save raw, unparsed server response to a file")
415         parser.add_option("", "--rawformat", dest="rawformat", type="choice",
416                           help="raw file format ([text]|pickled|json)", default="text",
417                           choices=("text","pickled","json"))
418         parser.add_option("", "--rawbanner", dest="rawbanner", default=None,
419                           help="text string to write before and after raw output")
420         parser.add_option("-d", "--dir", dest="sfi_dir",
421                          help="config & working directory - default is %default",
422                          metavar="PATH", default=Sfi.default_sfi_dir())
423         parser.add_option("-u", "--user", dest="user",
424                          help="user name", metavar="HRN", default=None)
425         parser.add_option("-a", "--auth", dest="auth",
426                          help="authority name", metavar="HRN", default=None)
427         parser.add_option("-v", "--verbose", action="count", dest="verbose", default=0,
428                          help="verbose mode - cumulative")
429         parser.add_option("-D", "--debug",
430                           action="store_true", dest="debug", default=False,
431                           help="Debug (xml-rpc) protocol messages")
432         # would it make sense to use ~/.ssh/id_rsa as a default here ?
433         parser.add_option("-k", "--private-key",
434                          action="store", dest="user_private_key", default=None,
435                          help="point to the private key file to use if not yet installed in sfi_dir")
436         parser.add_option("-t", "--timeout", dest="timeout", default=None,
437                          help="Amout of time to wait before timing out the request")
438         parser.add_option("-?", "--commands", 
439                          action="store_true", dest="command_help", default=False,
440                          help="one page summary on commands & exit")
441         parser.disable_interspersed_args()
442
443         return parser
444         
445
446     def print_help (self):
447         print "==================== Generic sfi usage"
448         self.sfi_parser.print_help()
449         print "==================== Specific command usage"
450         self.command_parser.print_help()
451
452     #
453     # Main: parse arguments and dispatch to command
454     #
455     def dispatch(self, command, command_options, command_args):
456         method=getattr(self, command,None)
457         if not method:
458             print "Unknown command %s"%command
459             return
460         return method(command_options, command_args)
461
462     def main(self):
463         self.sfi_parser = self.create_parser()
464         (options, args) = self.sfi_parser.parse_args()
465         if options.command_help: 
466             self.print_command_help(options)
467             sys.exit(1)
468         self.options = options
469
470         self.logger.setLevelFromOptVerbose(self.options.verbose)
471
472         if len(args) <= 0:
473             self.logger.critical("No command given. Use -h for help.")
474             self.print_command_help(options)
475             return -1
476     
477         # complete / find unique match with command set
478         command_candidates = Candidates (self.available_names)
479         input = args[0]
480         command = command_candidates.only_match(input)
481         if not command:
482             self.print_command_help(options)
483             sys.exit(1)
484         # second pass options parsing
485         self.command_parser = self.create_command_parser(command)
486         (command_options, command_args) = self.command_parser.parse_args(args[1:])
487         self.command_options = command_options
488
489         self.read_config () 
490         self.bootstrap ()
491         self.logger.debug("Command=%s" % command)
492
493         try:
494             self.dispatch(command, command_options, command_args)
495         except SystemExit:
496             return 1
497         except:
498             self.logger.log_exc ("sfi command %s failed"%command)
499             return 1
500
501         return 0
502     
503     ####################
504     def read_config(self):
505         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
506         shell_config_file  = os.path.join(self.options.sfi_dir,"sfi_config.sh")
507         try:
508             if Config.is_ini(config_file):
509                 config = Config (config_file)
510             else:
511                 # try upgrading from shell config format
512                 fp, fn = mkstemp(suffix='sfi_config', text=True)  
513                 config = Config(fn)
514                 # we need to preload the sections we want parsed 
515                 # from the shell config
516                 config.add_section('sfi')
517                 config.add_section('sface')
518                 config.load(config_file)
519                 # back up old config
520                 shutil.move(config_file, shell_config_file)
521                 # write new config
522                 config.save(config_file)
523                  
524         except:
525             self.logger.critical("Failed to read configuration file %s"%config_file)
526             self.logger.info("Make sure to remove the export clauses and to add quotes")
527             if self.options.verbose==0:
528                 self.logger.info("Re-run with -v for more details")
529             else:
530                 self.logger.log_exc("Could not read config file %s"%config_file)
531             sys.exit(1)
532      
533         errors = 0
534         # Set SliceMgr URL
535         if (self.options.sm is not None):
536            self.sm_url = self.options.sm
537         elif hasattr(config, "SFI_SM"):
538            self.sm_url = config.SFI_SM
539         else:
540            self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
541            errors += 1 
542
543         # Set Registry URL
544         if (self.options.registry is not None):
545            self.reg_url = self.options.registry
546         elif hasattr(config, "SFI_REGISTRY"):
547            self.reg_url = config.SFI_REGISTRY
548         else:
549            self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
550            errors += 1 
551
552         # Set user HRN
553         if (self.options.user is not None):
554            self.user = self.options.user
555         elif hasattr(config, "SFI_USER"):
556            self.user = config.SFI_USER
557         else:
558            self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
559            errors += 1 
560
561         # Set authority HRN
562         if (self.options.auth is not None):
563            self.authority = self.options.auth
564         elif hasattr(config, "SFI_AUTH"):
565            self.authority = config.SFI_AUTH
566         else:
567            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
568            errors += 1 
569
570         self.config_file=config_file
571         if errors:
572            sys.exit(1)
573
574     def show_config (self):
575         print "From configuration file %s"%self.config_file
576         flags=[ 
577             ('SFI_USER','user'),
578             ('SFI_AUTH','authority'),
579             ('SFI_SM','sm_url'),
580             ('SFI_REGISTRY','reg_url'),
581             ]
582         for (external_name, internal_name) in flags:
583             print "%s='%s'"%(external_name,getattr(self,internal_name))
584
585     #
586     # Get various credential and spec files
587     #
588     # Establishes limiting conventions
589     #   - conflates MAs and SAs
590     #   - assumes last token in slice name is unique
591     #
592     # Bootstraps credentials
593     #   - bootstrap user credential from self-signed certificate
594     #   - bootstrap authority credential from user credential
595     #   - bootstrap slice credential from user credential
596     #
597     
598     # init self-signed cert, user credentials and gid
599     def bootstrap (self):
600         client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
601                                                logger=self.logger)
602         # if -k is provided, use this to initialize private key
603         if self.options.user_private_key:
604             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
605         else:
606             # trigger legacy compat code if needed 
607             # the name has changed from just <leaf>.pkey to <hrn>.pkey
608             if not os.path.isfile(client_bootstrap.private_key_filename()):
609                 self.logger.info ("private key not found, trying legacy name")
610                 try:
611                     legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
612                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
613                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
614                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
615                 except:
616                     self.logger.log_exc("Can't find private key ")
617                     sys.exit(1)
618             
619         # make it bootstrap
620         client_bootstrap.bootstrap_my_gid()
621         # extract what's needed
622         self.private_key = client_bootstrap.private_key()
623         self.my_credential_string = client_bootstrap.my_credential_string ()
624         self.my_gid = client_bootstrap.my_gid ()
625         self.client_bootstrap = client_bootstrap
626
627
628     def my_authority_credential_string(self):
629         if not self.authority:
630             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
631             sys.exit(-1)
632         return self.client_bootstrap.authority_credential_string (self.authority)
633
634     def authority_credential_string(self, auth_hrn):
635         return self.client_bootstrap.authority_credential_string (auth_hrn)
636
637     def slice_credential_string(self, name):
638         return self.client_bootstrap.slice_credential_string (name)
639
640     #
641     # Management of the servers
642     # 
643
644     def registry (self):
645         # cache the result
646         if not hasattr (self, 'registry_proxy'):
647             self.logger.info("Contacting Registry at: %s"%self.reg_url)
648             self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
649                                                  timeout=self.options.timeout, verbose=self.options.debug)  
650         return self.registry_proxy
651
652     def sliceapi (self):
653         # cache the result
654         if not hasattr (self, 'sliceapi_proxy'):
655             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
656             if hasattr(self.command_options,'component') and self.command_options.component:
657                 # resolve the hrn at the registry
658                 node_hrn = self.command_options.component
659                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
660                 records = filter_records('node', records)
661                 if not records:
662                     self.logger.warning("No such component:%r"% opts.component)
663                 record = records[0]
664                 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
665                 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
666             else:
667                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
668                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
669                     self.sm_url = 'http://' + self.sm_url
670                 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
671                 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
672                                                      timeout=self.options.timeout, verbose=self.options.debug)  
673         return self.sliceapi_proxy
674
675     def get_cached_server_version(self, server):
676         # check local cache first
677         cache = None
678         version = None 
679         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
680         cache_key = server.url + "-version"
681         try:
682             cache = Cache(cache_file)
683         except IOError:
684             cache = Cache()
685             self.logger.info("Local cache not found at: %s" % cache_file)
686
687         if cache:
688             version = cache.get(cache_key)
689
690         if not version: 
691             result = server.GetVersion()
692             version= ReturnValue.get_value(result)
693             # cache version for 20 minutes
694             cache.add(cache_key, version, ttl= 60*20)
695             self.logger.info("Updating cache file %s" % cache_file)
696             cache.save_to_file(cache_file)
697
698         return version   
699         
700     ### resurrect this temporarily so we can support V1 aggregates for a while
701     def server_supports_options_arg(self, server):
702         """
703         Returns true if server support the optional call_id arg, false otherwise. 
704         """
705         server_version = self.get_cached_server_version(server)
706         result = False
707         # xxx need to rewrite this 
708         if int(server_version.get('geni_api')) >= 2:
709             result = True
710         return result
711
712     def server_supports_call_id_arg(self, server):
713         server_version = self.get_cached_server_version(server)
714         result = False      
715         if 'sfa' in server_version and 'code_tag' in server_version:
716             code_tag = server_version['code_tag']
717             code_tag_parts = code_tag.split("-")
718             version_parts = code_tag_parts[0].split(".")
719             major, minor = version_parts[0], version_parts[1]
720             rev = code_tag_parts[1]
721             if int(major) == 1 and minor == 0 and build >= 22:
722                 result = True
723         return result                 
724
725     ### ois = options if supported
726     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
727     def ois (self, server, option_dict):
728         if self.server_supports_options_arg (server): 
729             return [option_dict]
730         elif self.server_supports_call_id_arg (server):
731             return [ unique_call_id () ]
732         else: 
733             return []
734
735     ### cis = call_id if supported - like ois
736     def cis (self, server):
737         if self.server_supports_call_id_arg (server):
738             return [ unique_call_id ]
739         else:
740             return []
741
742     ######################################## miscell utilities
743     def get_rspec_file(self, rspec):
744        if (os.path.isabs(rspec)):
745           file = rspec
746        else:
747           file = os.path.join(self.options.sfi_dir, rspec)
748        if (os.path.isfile(file)):
749           return file
750        else:
751           self.logger.critical("No such rspec file %s"%rspec)
752           sys.exit(1)
753     
754     def get_record_file(self, record):
755        if (os.path.isabs(record)):
756           file = record
757        else:
758           file = os.path.join(self.options.sfi_dir, record)
759        if (os.path.isfile(file)):
760           return file
761        else:
762           self.logger.critical("No such registry record file %s"%record)
763           sys.exit(1)
764
765
766     #==========================================================================
767     # Following functions implement the commands
768     #
769     # Registry-related commands
770     #==========================================================================
771
772     def version(self, options, args):
773         """
774         display an SFA server version (GetVersion)
775 or version information about sfi itself
776         """
777         if options.version_local:
778             version=version_core()
779         else:
780             if options.version_registry:
781                 server=self.registry()
782             else:
783                 server = self.sliceapi()
784             result = server.GetVersion()
785             version = ReturnValue.get_value(result)
786         if self.options.raw:
787             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
788         else:
789             pprinter = PrettyPrinter(indent=4)
790             pprinter.pprint(version)
791
792     def list(self, options, args):
793         """
794         list entries in named authority registry (List)
795         """
796         if len(args)!= 1:
797             self.print_help()
798             sys.exit(1)
799         hrn = args[0]
800         opts = {}
801         if options.recursive:
802             opts['recursive'] = options.recursive
803         
804         if options.show_credential:
805             show_credentials(self.my_credential_string)
806         try:
807             list = self.registry().List(hrn, self.my_credential_string, options)
808         except IndexError:
809             raise Exception, "Not enough parameters for the 'list' command"
810
811         # filter on person, slice, site, node, etc.
812         # This really should be in the self.filter_records funct def comment...
813         list = filter_records(options.type, list)
814         terminal_render (list, options)
815         if options.file:
816             save_records_to_file(options.file, list, options.fileformat)
817         return
818     
819     def show(self, options, args):
820         """
821         show details about named registry record (Resolve)
822         """
823         if len(args)!= 1:
824             self.print_help()
825             sys.exit(1)
826         hrn = args[0]
827         # explicitly require Resolve to run in details mode
828         record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
829         record_dicts = filter_records(options.type, record_dicts)
830         if not record_dicts:
831             self.logger.error("No record of type %s"% options.type)
832             return
833         # user has required to focus on some keys
834         if options.keys:
835             def project (record):
836                 projected={}
837                 for key in options.keys:
838                     try: projected[key]=record[key]
839                     except: pass
840                 return projected
841             record_dicts = [ project (record) for record in record_dicts ]
842         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
843         for record in records:
844             if (options.format == "text"):      record.dump(sort=True)  
845             else:                               print record.save_as_xml() 
846         if options.file:
847             save_records_to_file(options.file, record_dicts, options.fileformat)
848         return
849     
850     def add(self, options, args):
851         "add record into registry by using the command options (Recommended) or from xml file (Register)"
852         auth_cred = self.my_authority_credential_string()
853         if options.show_credential:
854             show_credentials(auth_cred)
855         record_dict = {}
856         if len(args) > 1:
857             self.print_help()
858             sys.exit(1)
859         if len(args)==1:
860             try:
861                 record_filepath = args[0]
862                 rec_file = self.get_record_file(record_filepath)
863                 record_dict.update(load_record_from_file(rec_file).todict())
864             except:
865                 print "Cannot load record file %s"%record_filepath
866                 sys.exit(1)
867         if options:
868             record_dict.update(load_record_from_opts(options).todict())
869         # we should have a type by now
870         if 'type' not in record_dict :
871             self.print_help()
872             sys.exit(1)
873         # this is still planetlab dependent.. as plc will whine without that
874         # also, it's only for adding
875         if record_dict['type'] == 'user':
876             if not 'first_name' in record_dict:
877                 record_dict['first_name'] = record_dict['hrn']
878             if 'last_name' not in record_dict:
879                 record_dict['last_name'] = record_dict['hrn'] 
880         return self.registry().Register(record_dict, auth_cred)
881     
882     def update(self, options, args):
883         "update record into registry by using the command options (Recommended) or from xml file (Update)"
884         record_dict = {}
885         if len(args) > 0:
886             record_filepath = args[0]
887             rec_file = self.get_record_file(record_filepath)
888             record_dict.update(load_record_from_file(rec_file).todict())
889         if options:
890             record_dict.update(load_record_from_opts(options).todict())
891         # at the very least we need 'type' here
892         if 'type' not in record_dict:
893             self.print_help()
894             sys.exit(1)
895
896         # don't translate into an object, as this would possibly distort
897         # user-provided data; e.g. add an 'email' field to Users
898         if record_dict['type'] == "user":
899             if record_dict['hrn'] == self.user:
900                 cred = self.my_credential_string
901             else:
902                 cred = self.my_authority_credential_string()
903         elif record_dict['type'] in ["slice"]:
904             try:
905                 cred = self.slice_credential_string(record_dict['hrn'])
906             except ServerException, e:
907                # XXX smbaker -- once we have better error return codes, update this
908                # to do something better than a string compare
909                if "Permission error" in e.args[0]:
910                    cred = self.my_authority_credential_string()
911                else:
912                    raise
913         elif record_dict['type'] in ["authority"]:
914             cred = self.my_authority_credential_string()
915         elif record_dict['type'] == 'node':
916             cred = self.my_authority_credential_string()
917         else:
918             raise "unknown record type" + record_dict['type']
919         if options.show_credential:
920             show_credentials(cred)
921         return self.registry().Update(record_dict, cred)
922   
923     def remove(self, options, args):
924         "remove registry record by name (Remove)"
925         auth_cred = self.my_authority_credential_string()
926         if len(args)!=1:
927             self.print_help()
928             sys.exit(1)
929         hrn = args[0]
930         type = options.type 
931         if type in ['all']:
932             type = '*'
933         if options.show_credential:
934             show_credentials(auth_cred)
935         return self.registry().Remove(hrn, auth_cred, type)
936     
937     # ==================================================================
938     # Slice-related commands
939     # ==================================================================
940
941     def slices(self, options, args):
942         "list instantiated slices (ListSlices) - returns urn's"
943         server = self.sliceapi()
944         # creds
945         creds = [self.my_credential_string]
946         # options and call_id when supported
947         api_options = {}
948         api_options['call_id']=unique_call_id()
949         if options.show_credential:
950             show_credentials(creds)
951         result = server.ListSlices(creds, *self.ois(server,api_options))
952         value = ReturnValue.get_value(result)
953         if self.options.raw:
954             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
955         else:
956             display_list(value)
957         return
958
959     # show rspec for named slice
960     def resources(self, options, args):
961         """
962         with no arg, discover available resources, (ListResources)
963 or with an slice hrn, shows currently provisioned resources
964         """
965         server = self.sliceapi()
966
967         # set creds
968         creds = []
969         if args:
970             the_credential=self.slice_credential_string(args[0])
971             creds.append(the_credential)
972         else:
973             the_credential=self.my_credential_string
974             creds.append(the_credential)
975         if options.show_credential:
976             show_credentials(creds)
977
978         # no need to check if server accepts the options argument since the options has
979         # been a required argument since v1 API
980         api_options = {}
981         # always send call_id to v2 servers
982         api_options ['call_id'] = unique_call_id()
983         # ask for cached value if available
984         api_options ['cached'] = True
985         if args:
986             hrn = args[0]
987             api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
988         if options.info:
989             api_options['info'] = options.info
990         if options.list_leases:
991             api_options['list_leases'] = options.list_leases
992         if options.current:
993             if options.current == True:
994                 api_options['cached'] = False
995             else:
996                 api_options['cached'] = True
997         if options.rspec_version:
998             version_manager = VersionManager()
999             server_version = self.get_cached_server_version(server)
1000             if 'sfa' in server_version:
1001                 # just request the version the client wants
1002                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1003             else:
1004                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1005         else:
1006             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3'}
1007         result = server.ListResources (creds, api_options)
1008         value = ReturnValue.get_value(result)
1009         if self.options.raw:
1010             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1011         if options.file is not None:
1012             save_rspec_to_file(value, options.file)
1013         if (self.options.raw is None) and (options.file is None):
1014             display_rspec(value, options.format)
1015
1016         return
1017
1018     def create(self, options, args):
1019         """
1020         create or update named slice with given rspec
1021         """
1022         server = self.sliceapi()
1023
1024         # xxx do we need to check usage (len(args)) ?
1025         # slice urn
1026         slice_hrn = args[0]
1027         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1028
1029         # credentials
1030         creds = [self.slice_credential_string(slice_hrn)]
1031
1032         delegated_cred = None
1033         server_version = self.get_cached_server_version(server)
1034         if server_version.get('interface') == 'slicemgr':
1035             # delegate our cred to the slice manager
1036             # do not delegate cred to slicemgr...not working at the moment
1037             pass
1038             #if server_version.get('hrn'):
1039             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1040             #elif server_version.get('urn'):
1041             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1042
1043         if options.show_credential:
1044             show_credentials(creds)
1045
1046         # rspec
1047         rspec_file = self.get_rspec_file(args[1])
1048         rspec = open(rspec_file).read()
1049
1050         # users
1051         # need to pass along user keys to the aggregate.
1052         # users = [
1053         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1054         #    keys: [<ssh key A>, <ssh key B>]
1055         #  }]
1056         users = []
1057         # xxx Thierry 2012 sept. 21
1058         # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here
1059         # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again
1060         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1061         # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True})
1062         if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']:
1063             slice_record = slice_records[0]
1064             user_hrns = slice_record['reg-researchers']
1065             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1066             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1067
1068             if 'sfa' not in server_version:
1069                 users = pg_users_arg(user_records)
1070                 rspec = RSpec(rspec)
1071                 rspec.filter({'component_manager_id': server_version['urn']})
1072                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1073             else:
1074                 users = sfa_users_arg(user_records, slice_record)
1075
1076         # do not append users, keys, or slice tags. Anything
1077         # not contained in this request will be removed from the slice
1078
1079         # CreateSliver has supported the options argument for a while now so it should
1080         # be safe to assume this server support it
1081         api_options = {}
1082         api_options ['append'] = False
1083         api_options ['call_id'] = unique_call_id()
1084         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1085         value = ReturnValue.get_value(result)
1086         if self.options.raw:
1087             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1088         if options.file is not None:
1089             save_rspec_to_file (value, options.file)
1090         if (self.options.raw is None) and (options.file is None):
1091             print value
1092
1093         return value
1094
1095     def delete(self, options, args):
1096         """
1097         delete named slice (DeleteSliver)
1098         """
1099         server = self.sliceapi()
1100
1101         # slice urn
1102         slice_hrn = args[0]
1103         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1104
1105         # creds
1106         slice_cred = self.slice_credential_string(slice_hrn)
1107         creds = [slice_cred]
1108         
1109         # options and call_id when supported
1110         api_options = {}
1111         api_options ['call_id'] = unique_call_id()
1112         if options.show_credential:
1113             show_credentials(creds)
1114         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1115         value = ReturnValue.get_value(result)
1116         if self.options.raw:
1117             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1118         else:
1119             print value
1120         return value 
1121   
1122     def status(self, options, args):
1123         """
1124         retrieve slice status (SliverStatus)
1125         """
1126         server = self.sliceapi()
1127
1128         # slice urn
1129         slice_hrn = args[0]
1130         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1131
1132         # creds 
1133         slice_cred = self.slice_credential_string(slice_hrn)
1134         creds = [slice_cred]
1135
1136         # options and call_id when supported
1137         api_options = {}
1138         api_options['call_id']=unique_call_id()
1139         if options.show_credential:
1140             show_credentials(creds)
1141         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1142         value = ReturnValue.get_value(result)
1143         if self.options.raw:
1144             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1145         else:
1146             print value
1147
1148     def start(self, options, args):
1149         """
1150         start named slice (Start)
1151         """
1152         server = self.sliceapi()
1153
1154         # the slice urn
1155         slice_hrn = args[0]
1156         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1157         
1158         # cred
1159         slice_cred = self.slice_credential_string(args[0])
1160         creds = [slice_cred]
1161         # xxx Thierry - does this not need an api_options as well ?
1162         result = server.Start(slice_urn, creds)
1163         value = ReturnValue.get_value(result)
1164         if self.options.raw:
1165             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1166         else:
1167             print value
1168         return value
1169     
1170     def stop(self, options, args):
1171         """
1172         stop named slice (Stop)
1173         """
1174         server = self.sliceapi()
1175         # slice urn
1176         slice_hrn = args[0]
1177         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1178         # cred
1179         slice_cred = self.slice_credential_string(args[0])
1180         creds = [slice_cred]
1181         result =  server.Stop(slice_urn, creds)
1182         value = ReturnValue.get_value(result)
1183         if self.options.raw:
1184             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1185         else:
1186             print value
1187         return value
1188     
1189     # reset named slice
1190     def reset(self, options, args):
1191         """
1192         reset named slice (reset_slice)
1193         """
1194         server = self.sliceapi()
1195         # slice urn
1196         slice_hrn = args[0]
1197         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1198         # cred
1199         slice_cred = self.slice_credential_string(args[0])
1200         creds = [slice_cred]
1201         result = server.reset_slice(creds, slice_urn)
1202         value = ReturnValue.get_value(result)
1203         if self.options.raw:
1204             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1205         else:
1206             print value
1207         return value
1208
1209     def renew(self, options, args):
1210         """
1211         renew slice (RenewSliver)
1212         """
1213         server = self.sliceapi()
1214         if len(args) != 2:
1215             self.print_help()
1216             sys.exit(1)
1217         [ slice_hrn, input_time ] = args
1218         # slice urn    
1219         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1220         # time: don't try to be smart on the time format, server-side will
1221         # creds
1222         slice_cred = self.slice_credential_string(args[0])
1223         creds = [slice_cred]
1224         # options and call_id when supported
1225         api_options = {}
1226         api_options['call_id']=unique_call_id()
1227         if options.show_credential:
1228             show_credentials(creds)
1229         result =  server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1230         value = ReturnValue.get_value(result)
1231         if self.options.raw:
1232             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1233         else:
1234             print value
1235         return value
1236
1237
1238     def shutdown(self, options, args):
1239         """
1240         shutdown named slice (Shutdown)
1241         """
1242         server = self.sliceapi()
1243         # slice urn
1244         slice_hrn = args[0]
1245         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1246         # creds
1247         slice_cred = self.slice_credential_string(slice_hrn)
1248         creds = [slice_cred]
1249         result = server.Shutdown(slice_urn, creds)
1250         value = ReturnValue.get_value(result)
1251         if self.options.raw:
1252             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1253         else:
1254             print value
1255         return value         
1256     
1257
1258     def get_ticket(self, options, args):
1259         """
1260         get a ticket for the specified slice
1261         """
1262         server = self.sliceapi()
1263         # slice urn
1264         slice_hrn, rspec_path = args[0], args[1]
1265         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1266         # creds
1267         slice_cred = self.slice_credential_string(slice_hrn)
1268         creds = [slice_cred]
1269         # rspec
1270         rspec_file = self.get_rspec_file(rspec_path) 
1271         rspec = open(rspec_file).read()
1272         # options and call_id when supported
1273         api_options = {}
1274         api_options['call_id']=unique_call_id()
1275         # get ticket at the server
1276         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1277         # save
1278         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1279         self.logger.info("writing ticket to %s"%file)
1280         ticket = SfaTicket(string=ticket_string)
1281         ticket.save_to_file(filename=file, save_parents=True)
1282
1283     def redeem_ticket(self, options, args):
1284         """
1285         Connects to nodes in a slice and redeems a ticket
1286 (slice hrn is retrieved from the ticket)
1287         """
1288         ticket_file = args[0]
1289         
1290         # get slice hrn from the ticket
1291         # use this to get the right slice credential 
1292         ticket = SfaTicket(filename=ticket_file)
1293         ticket.decode()
1294         ticket_string = ticket.save_to_string(save_parents=True)
1295
1296         slice_hrn = ticket.gidObject.get_hrn()
1297         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1298         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1299         slice_cred = self.slice_credential_string(slice_hrn)
1300         
1301         # get a list of node hostnames from the RSpec 
1302         tree = etree.parse(StringIO(ticket.rspec))
1303         root = tree.getroot()
1304         hostnames = root.xpath("./network/site/node/hostname/text()")
1305         
1306         # create an xmlrpc connection to the component manager at each of these
1307         # components and gall redeem_ticket
1308         connections = {}
1309         for hostname in hostnames:
1310             try:
1311                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1312                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1313                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1314                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1315                                            timeout=self.options.timeout, verbose=self.options.debug)
1316                 server.RedeemTicket(ticket_string, slice_cred)
1317                 self.logger.info("Success")
1318             except socket.gaierror:
1319                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1320             except Exception, e:
1321                 self.logger.log_exc(e.message)
1322         return
1323
1324     def gid(self, options, args):
1325         """
1326         Create a GID (CreateGid)
1327         """
1328         if len(args) < 1:
1329             self.print_help()
1330             sys.exit(1)
1331         target_hrn = args[0]
1332         my_gid_string = open(self.client_bootstrap.my_gid()).read() 
1333         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1334         if options.file:
1335             filename = options.file
1336         else:
1337             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1338         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1339         GID(string=gid).save_to_file(filename)
1340          
1341
1342     def delegate (self, options, args):
1343         """
1344         (locally) create delegate credential for use by given hrn
1345         """
1346         if len(args) != 1:
1347             self.print_help()
1348             sys.exit(1)
1349         to_hrn = args[0]
1350         # support for several delegations in the same call
1351         # so first we gather the things to do
1352         tuples=[]
1353         for slice_hrn in options.delegate_slices:
1354             message="%s.slice"%slice_hrn
1355             original = self.slice_credential_string(slice_hrn)
1356             tuples.append ( (message, original,) )
1357         if options.delegate_pi:
1358             my_authority=self.authority
1359             message="%s.pi"%my_authority
1360             original = self.my_authority_credential_string()
1361             tuples.append ( (message, original,) )
1362         for auth_hrn in options.delegate_auths:
1363             message="%s.auth"%auth_hrn
1364             original=self.authority_credential_string(auth_hrn)
1365             tuples.append ( (message, original, ) )
1366         # if nothing was specified at all at this point, let's assume -u
1367         if not tuples: options.delegate_user=True
1368         # this user cred
1369         if options.delegate_user:
1370             message="%s.user"%self.user
1371             original = self.my_credential_string
1372             tuples.append ( (message, original, ) )
1373
1374         # default type for beneficial is user unless -A
1375         if options.delegate_to_authority:       to_type='authority'
1376         else:                                   to_type='user'
1377
1378         # let's now handle all this
1379         # it's all in the filenaming scheme
1380         for (message,original) in tuples:
1381             delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1382             delegated_credential = Credential (string=delegated_string)
1383             filename = os.path.join ( self.options.sfi_dir,
1384                                       "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1385             delegated_credential.save_to_file(filename, save_parents=True)
1386             self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1387     
1388     def trusted(self, options, args):
1389         """
1390         return uhe trusted certs at this interface (get_trusted_certs)
1391         """ 
1392         trusted_certs = self.registry().get_trusted_certs()
1393         for trusted_cert in trusted_certs:
1394             gid = GID(string=trusted_cert)
1395             gid.dump()
1396             cert = Certificate(string=trusted_cert)
1397             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1398         return 
1399
1400     def config (self, options, args):
1401         "Display contents of current config"
1402         self.show_config()