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