Merge branch 'master' of ssh://git.onelab.eu/git/sfa
[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:
496             self.logger.log_exc ("sfi command %s failed"%command)
497             sys.exit(1)
498
499         return
500     
501     ####################
502     def read_config(self):
503         config_file = os.path.join(self.options.sfi_dir,"sfi_config")
504         shell_config_file  = os.path.join(self.options.sfi_dir,"sfi_config.sh")
505         try:
506             if Config.is_ini(config_file):
507                 config = Config (config_file)
508             else:
509                 # try upgrading from shell config format
510                 fp, fn = mkstemp(suffix='sfi_config', text=True)  
511                 config = Config(fn)
512                 # we need to preload the sections we want parsed 
513                 # from the shell config
514                 config.add_section('sfi')
515                 config.add_section('sface')
516                 config.load(config_file)
517                 # back up old config
518                 shutil.move(config_file, shell_config_file)
519                 # write new config
520                 config.save(config_file)
521                  
522         except:
523             self.logger.critical("Failed to read configuration file %s"%config_file)
524             self.logger.info("Make sure to remove the export clauses and to add quotes")
525             if self.options.verbose==0:
526                 self.logger.info("Re-run with -v for more details")
527             else:
528                 self.logger.log_exc("Could not read config file %s"%config_file)
529             sys.exit(1)
530      
531         errors = 0
532         # Set SliceMgr URL
533         if (self.options.sm is not None):
534            self.sm_url = self.options.sm
535         elif hasattr(config, "SFI_SM"):
536            self.sm_url = config.SFI_SM
537         else:
538            self.logger.error("You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file)
539            errors += 1 
540
541         # Set Registry URL
542         if (self.options.registry is not None):
543            self.reg_url = self.options.registry
544         elif hasattr(config, "SFI_REGISTRY"):
545            self.reg_url = config.SFI_REGISTRY
546         else:
547            self.logger.error("You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file)
548            errors += 1 
549
550         # Set user HRN
551         if (self.options.user is not None):
552            self.user = self.options.user
553         elif hasattr(config, "SFI_USER"):
554            self.user = config.SFI_USER
555         else:
556            self.logger.error("You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file)
557            errors += 1 
558
559         # Set authority HRN
560         if (self.options.auth is not None):
561            self.authority = self.options.auth
562         elif hasattr(config, "SFI_AUTH"):
563            self.authority = config.SFI_AUTH
564         else:
565            self.logger.error("You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file)
566            errors += 1 
567
568         self.config_file=config_file
569         if errors:
570            sys.exit(1)
571
572     def show_config (self):
573         print "From configuration file %s"%self.config_file
574         flags=[ 
575             ('SFI_USER','user'),
576             ('SFI_AUTH','authority'),
577             ('SFI_SM','sm_url'),
578             ('SFI_REGISTRY','reg_url'),
579             ]
580         for (external_name, internal_name) in flags:
581             print "%s='%s'"%(external_name,getattr(self,internal_name))
582
583     #
584     # Get various credential and spec files
585     #
586     # Establishes limiting conventions
587     #   - conflates MAs and SAs
588     #   - assumes last token in slice name is unique
589     #
590     # Bootstraps credentials
591     #   - bootstrap user credential from self-signed certificate
592     #   - bootstrap authority credential from user credential
593     #   - bootstrap slice credential from user credential
594     #
595     
596     # init self-signed cert, user credentials and gid
597     def bootstrap (self):
598         client_bootstrap = SfaClientBootstrap (self.user, self.reg_url, self.options.sfi_dir,
599                                                logger=self.logger)
600         # if -k is provided, use this to initialize private key
601         if self.options.user_private_key:
602             client_bootstrap.init_private_key_if_missing (self.options.user_private_key)
603         else:
604             # trigger legacy compat code if needed 
605             # the name has changed from just <leaf>.pkey to <hrn>.pkey
606             if not os.path.isfile(client_bootstrap.private_key_filename()):
607                 self.logger.info ("private key not found, trying legacy name")
608                 try:
609                     legacy_private_key = os.path.join (self.options.sfi_dir, "%s.pkey"%Xrn.unescape(get_leaf(self.user)))
610                     self.logger.debug("legacy_private_key=%s"%legacy_private_key)
611                     client_bootstrap.init_private_key_if_missing (legacy_private_key)
612                     self.logger.info("Copied private key from legacy location %s"%legacy_private_key)
613                 except:
614                     self.logger.log_exc("Can't find private key ")
615                     sys.exit(1)
616             
617         # make it bootstrap
618         client_bootstrap.bootstrap_my_gid()
619         # extract what's needed
620         self.private_key = client_bootstrap.private_key()
621         self.my_credential_string = client_bootstrap.my_credential_string ()
622         self.my_gid = client_bootstrap.my_gid ()
623         self.client_bootstrap = client_bootstrap
624
625
626     def my_authority_credential_string(self):
627         if not self.authority:
628             self.logger.critical("no authority specified. Use -a or set SF_AUTH")
629             sys.exit(-1)
630         return self.client_bootstrap.authority_credential_string (self.authority)
631
632     def authority_credential_string(self, auth_hrn):
633         return self.client_bootstrap.authority_credential_string (auth_hrn)
634
635     def slice_credential_string(self, name):
636         return self.client_bootstrap.slice_credential_string (name)
637
638     #
639     # Management of the servers
640     # 
641
642     def registry (self):
643         # cache the result
644         if not hasattr (self, 'registry_proxy'):
645             self.logger.info("Contacting Registry at: %s"%self.reg_url)
646             self.registry_proxy = SfaServerProxy(self.reg_url, self.private_key, self.my_gid, 
647                                                  timeout=self.options.timeout, verbose=self.options.debug)  
648         return self.registry_proxy
649
650     def sliceapi (self):
651         # cache the result
652         if not hasattr (self, 'sliceapi_proxy'):
653             # if the command exposes the --component option, figure it's hostname and connect at CM_PORT
654             if hasattr(self.command_options,'component') and self.command_options.component:
655                 # resolve the hrn at the registry
656                 node_hrn = self.command_options.component
657                 records = self.registry().Resolve(node_hrn, self.my_credential_string)
658                 records = filter_records('node', records)
659                 if not records:
660                     self.logger.warning("No such component:%r"% opts.component)
661                 record = records[0]
662                 cm_url = "http://%s:%d/"%(record['hostname'],CM_PORT)
663                 self.sliceapi_proxy=SfaServerProxy(cm_url, self.private_key, self.my_gid)
664             else:
665                 # otherwise use what was provided as --sliceapi, or SFI_SM in the config
666                 if not self.sm_url.startswith('http://') or self.sm_url.startswith('https://'):
667                     self.sm_url = 'http://' + self.sm_url
668                 self.logger.info("Contacting Slice Manager at: %s"%self.sm_url)
669                 self.sliceapi_proxy = SfaServerProxy(self.sm_url, self.private_key, self.my_gid, 
670                                                      timeout=self.options.timeout, verbose=self.options.debug)  
671         return self.sliceapi_proxy
672
673     def get_cached_server_version(self, server):
674         # check local cache first
675         cache = None
676         version = None 
677         cache_file = os.path.join(self.options.sfi_dir,'sfi_cache.dat')
678         cache_key = server.url + "-version"
679         try:
680             cache = Cache(cache_file)
681         except IOError:
682             cache = Cache()
683             self.logger.info("Local cache not found at: %s" % cache_file)
684
685         if cache:
686             version = cache.get(cache_key)
687
688         if not version: 
689             result = server.GetVersion()
690             version= ReturnValue.get_value(result)
691             # cache version for 20 minutes
692             cache.add(cache_key, version, ttl= 60*20)
693             self.logger.info("Updating cache file %s" % cache_file)
694             cache.save_to_file(cache_file)
695
696         return version   
697         
698     ### resurrect this temporarily so we can support V1 aggregates for a while
699     def server_supports_options_arg(self, server):
700         """
701         Returns true if server support the optional call_id arg, false otherwise. 
702         """
703         server_version = self.get_cached_server_version(server)
704         result = False
705         # xxx need to rewrite this 
706         if int(server_version.get('geni_api')) >= 2:
707             result = True
708         return result
709
710     def server_supports_call_id_arg(self, server):
711         server_version = self.get_cached_server_version(server)
712         result = False      
713         if 'sfa' in server_version and 'code_tag' in server_version:
714             code_tag = server_version['code_tag']
715             code_tag_parts = code_tag.split("-")
716             version_parts = code_tag_parts[0].split(".")
717             major, minor = version_parts[0], version_parts[1]
718             rev = code_tag_parts[1]
719             if int(major) == 1 and minor == 0 and build >= 22:
720                 result = True
721         return result                 
722
723     ### ois = options if supported
724     # to be used in something like serverproxy.Method (arg1, arg2, *self.ois(api_options))
725     def ois (self, server, option_dict):
726         if self.server_supports_options_arg (server): 
727             return [option_dict]
728         elif self.server_supports_call_id_arg (server):
729             return [ unique_call_id () ]
730         else: 
731             return []
732
733     ### cis = call_id if supported - like ois
734     def cis (self, server):
735         if self.server_supports_call_id_arg (server):
736             return [ unique_call_id ]
737         else:
738             return []
739
740     ######################################## miscell utilities
741     def get_rspec_file(self, rspec):
742        if (os.path.isabs(rspec)):
743           file = rspec
744        else:
745           file = os.path.join(self.options.sfi_dir, rspec)
746        if (os.path.isfile(file)):
747           return file
748        else:
749           self.logger.critical("No such rspec file %s"%rspec)
750           sys.exit(1)
751     
752     def get_record_file(self, record):
753        if (os.path.isabs(record)):
754           file = record
755        else:
756           file = os.path.join(self.options.sfi_dir, record)
757        if (os.path.isfile(file)):
758           return file
759        else:
760           self.logger.critical("No such registry record file %s"%record)
761           sys.exit(1)
762
763
764     #==========================================================================
765     # Following functions implement the commands
766     #
767     # Registry-related commands
768     #==========================================================================
769
770     def version(self, options, args):
771         """
772         display an SFA server version (GetVersion)
773 or version information about sfi itself
774         """
775         if options.version_local:
776             version=version_core()
777         else:
778             if options.version_registry:
779                 server=self.registry()
780             else:
781                 server = self.sliceapi()
782             result = server.GetVersion()
783             version = ReturnValue.get_value(result)
784         if self.options.raw:
785             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
786         else:
787             pprinter = PrettyPrinter(indent=4)
788             pprinter.pprint(version)
789
790     def list(self, options, args):
791         """
792         list entries in named authority registry (List)
793         """
794         if len(args)!= 1:
795             self.print_help()
796             sys.exit(1)
797         hrn = args[0]
798         opts = {}
799         if options.recursive:
800             opts['recursive'] = options.recursive
801         
802         if options.show_credential:
803             show_credentials(self.my_credential_string)
804         try:
805             list = self.registry().List(hrn, self.my_credential_string, options)
806         except IndexError:
807             raise Exception, "Not enough parameters for the 'list' command"
808
809         # filter on person, slice, site, node, etc.
810         # This really should be in the self.filter_records funct def comment...
811         list = filter_records(options.type, list)
812         terminal_render (list, options)
813         if options.file:
814             save_records_to_file(options.file, list, options.fileformat)
815         return
816     
817     def show(self, options, args):
818         """
819         show details about named registry record (Resolve)
820         """
821         if len(args)!= 1:
822             self.print_help()
823             sys.exit(1)
824         hrn = args[0]
825         # explicitly require Resolve to run in details mode
826         record_dicts = self.registry().Resolve(hrn, self.my_credential_string, {'details':True})
827         record_dicts = filter_records(options.type, record_dicts)
828         if not record_dicts:
829             self.logger.error("No record of type %s"% options.type)
830             return
831         # user has required to focus on some keys
832         if options.keys:
833             def project (record):
834                 projected={}
835                 for key in options.keys:
836                     try: projected[key]=record[key]
837                     except: pass
838                 return projected
839             record_dicts = [ project (record) for record in record_dicts ]
840         records = [ Record(dict=record_dict) for record_dict in record_dicts ]
841         for record in records:
842             if (options.format == "text"):      record.dump(sort=True)  
843             else:                               print record.save_as_xml() 
844         if options.file:
845             save_records_to_file(options.file, record_dicts, options.fileformat)
846         return
847     
848     def add(self, options, args):
849         "add record into registry by using the command options (Recommended) or from xml file (Register)"
850         auth_cred = self.my_authority_credential_string()
851         if options.show_credential:
852             show_credentials(auth_cred)
853         record_dict = {}
854         if len(args) > 1:
855             self.print_help()
856             sys.exit(1)
857         if len(args)==1:
858             try:
859                 record_filepath = args[0]
860                 rec_file = self.get_record_file(record_filepath)
861                 record_dict.update(load_record_from_file(rec_file).todict())
862             except:
863                 print "Cannot load record file %s"%record_filepath
864                 sys.exit(1)
865         if options:
866             record_dict.update(load_record_from_opts(options).todict())
867         # we should have a type by now
868         if 'type' not in record_dict :
869             self.print_help()
870             sys.exit(1)
871         # this is still planetlab dependent.. as plc will whine without that
872         # also, it's only for adding
873         if record_dict['type'] == 'user':
874             if not 'first_name' in record_dict:
875                 record_dict['first_name'] = record_dict['hrn']
876             if 'last_name' not in record_dict:
877                 record_dict['last_name'] = record_dict['hrn'] 
878         return self.registry().Register(record_dict, auth_cred)
879     
880     def update(self, options, args):
881         "update record into registry by using the command options (Recommended) or from xml file (Update)"
882         record_dict = {}
883         if len(args) > 0:
884             record_filepath = args[0]
885             rec_file = self.get_record_file(record_filepath)
886             record_dict.update(load_record_from_file(rec_file).todict())
887         if options:
888             record_dict.update(load_record_from_opts(options).todict())
889         # at the very least we need 'type' here
890         if 'type' not in record_dict:
891             self.print_help()
892             sys.exit(1)
893
894         # don't translate into an object, as this would possibly distort
895         # user-provided data; e.g. add an 'email' field to Users
896         if record_dict['type'] == "user":
897             if record_dict['hrn'] == self.user:
898                 cred = self.my_credential_string
899             else:
900                 cred = self.my_authority_credential_string()
901         elif record_dict['type'] in ["slice"]:
902             try:
903                 cred = self.slice_credential_string(record_dict['hrn'])
904             except ServerException, e:
905                # XXX smbaker -- once we have better error return codes, update this
906                # to do something better than a string compare
907                if "Permission error" in e.args[0]:
908                    cred = self.my_authority_credential_string()
909                else:
910                    raise
911         elif record_dict['type'] in ["authority"]:
912             cred = self.my_authority_credential_string()
913         elif record_dict['type'] == 'node':
914             cred = self.my_authority_credential_string()
915         else:
916             raise "unknown record type" + record_dict['type']
917         if options.show_credential:
918             show_credentials(cred)
919         return self.registry().Update(record_dict, cred)
920   
921     def remove(self, options, args):
922         "remove registry record by name (Remove)"
923         auth_cred = self.my_authority_credential_string()
924         if len(args)!=1:
925             self.print_help()
926             sys.exit(1)
927         hrn = args[0]
928         type = options.type 
929         if type in ['all']:
930             type = '*'
931         if options.show_credential:
932             show_credentials(auth_cred)
933         return self.registry().Remove(hrn, auth_cred, type)
934     
935     # ==================================================================
936     # Slice-related commands
937     # ==================================================================
938
939     def slices(self, options, args):
940         "list instantiated slices (ListSlices) - returns urn's"
941         server = self.sliceapi()
942         # creds
943         creds = [self.my_credential_string]
944         # options and call_id when supported
945         api_options = {}
946         api_options['call_id']=unique_call_id()
947         if options.show_credential:
948             show_credentials(creds)
949         result = server.ListSlices(creds, *self.ois(server,api_options))
950         value = ReturnValue.get_value(result)
951         if self.options.raw:
952             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
953         else:
954             display_list(value)
955         return
956
957     # show rspec for named slice
958     def resources(self, options, args):
959         """
960         with no arg, discover available resources, (ListResources)
961 or with an slice hrn, shows currently provisioned resources
962         """
963         server = self.sliceapi()
964
965         # set creds
966         creds = []
967         if args:
968             the_credential=self.slice_credential_string(args[0])
969             creds.append(the_credential)
970         else:
971             the_credential=self.my_credential_string
972             creds.append(the_credential)
973         if options.show_credential:
974             show_credentials(creds)
975
976         # no need to check if server accepts the options argument since the options has
977         # been a required argument since v1 API
978         api_options = {}
979         # always send call_id to v2 servers
980         api_options ['call_id'] = unique_call_id()
981         # ask for cached value if available
982         api_options ['cached'] = True
983         if args:
984             hrn = args[0]
985             api_options['geni_slice_urn'] = hrn_to_urn(hrn, 'slice')
986         if options.info:
987             api_options['info'] = options.info
988         if options.list_leases:
989             api_options['list_leases'] = options.list_leases
990         if options.current:
991             if options.current == True:
992                 api_options['cached'] = False
993             else:
994                 api_options['cached'] = True
995         if options.rspec_version:
996             version_manager = VersionManager()
997             server_version = self.get_cached_server_version(server)
998             if 'sfa' in server_version:
999                 # just request the version the client wants
1000                 api_options['geni_rspec_version'] = version_manager.get_version(options.rspec_version).to_dict()
1001             else:
1002                 api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1003         else:
1004             api_options['geni_rspec_version'] = {'type': 'geni', 'version': '3.0'}
1005         result = server.ListResources (creds, api_options)
1006         value = ReturnValue.get_value(result)
1007         if self.options.raw:
1008             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1009         if options.file is not None:
1010             save_rspec_to_file(value, options.file)
1011         if (self.options.raw is None) and (options.file is None):
1012             display_rspec(value, options.format)
1013
1014         return
1015
1016     def create(self, options, args):
1017         """
1018         create or update named slice with given rspec
1019         """
1020         server = self.sliceapi()
1021
1022         # xxx do we need to check usage (len(args)) ?
1023         # slice urn
1024         slice_hrn = args[0]
1025         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1026
1027         # credentials
1028         creds = [self.slice_credential_string(slice_hrn)]
1029
1030         delegated_cred = None
1031         server_version = self.get_cached_server_version(server)
1032         if server_version.get('interface') == 'slicemgr':
1033             # delegate our cred to the slice manager
1034             # do not delegate cred to slicemgr...not working at the moment
1035             pass
1036             #if server_version.get('hrn'):
1037             #    delegated_cred = self.delegate_cred(slice_cred, server_version['hrn'])
1038             #elif server_version.get('urn'):
1039             #    delegated_cred = self.delegate_cred(slice_cred, urn_to_hrn(server_version['urn']))
1040
1041         if options.show_credential:
1042             show_credentials(creds)
1043
1044         # rspec
1045         rspec_file = self.get_rspec_file(args[1])
1046         rspec = open(rspec_file).read()
1047
1048         # users
1049         # need to pass along user keys to the aggregate.
1050         # users = [
1051         #  { urn: urn:publicid:IDN+emulab.net+user+alice
1052         #    keys: [<ssh key A>, <ssh key B>]
1053         #  }]
1054         users = []
1055         # xxx Thierry 2012 sept. 21
1056         # contrary to what I was first thinking, calling Resolve with details=False does not yet work properly here
1057         # I am turning details=True on again on a - hopefully - temporary basis, just to get this whole thing to work again
1058         slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string])
1059         # slice_records = self.registry().Resolve(slice_urn, [self.my_credential_string], {'details':True})
1060         if slice_records and 'reg-researchers' in slice_records[0] and slice_records[0]['reg-researchers']:
1061             slice_record = slice_records[0]
1062             user_hrns = slice_record['reg-researchers']
1063             user_urns = [hrn_to_urn(hrn, 'user') for hrn in user_hrns]
1064             user_records = self.registry().Resolve(user_urns, [self.my_credential_string])
1065
1066             if 'sfa' not in server_version:
1067                 users = pg_users_arg(user_records)
1068                 rspec = RSpec(rspec)
1069                 rspec.filter({'component_manager_id': server_version['urn']})
1070                 rspec = RSpecConverter.to_pg_rspec(rspec.toxml(), content_type='request')
1071             else:
1072                 users = sfa_users_arg(user_records, slice_record)
1073
1074         # do not append users, keys, or slice tags. Anything
1075         # not contained in this request will be removed from the slice
1076
1077         # CreateSliver has supported the options argument for a while now so it should
1078         # be safe to assume this server support it
1079         api_options = {}
1080         api_options ['append'] = False
1081         api_options ['call_id'] = unique_call_id()
1082         result = server.CreateSliver(slice_urn, creds, rspec, users, *self.ois(server, api_options))
1083         value = ReturnValue.get_value(result)
1084         if self.options.raw:
1085             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1086         if options.file is not None:
1087             save_rspec_to_file (value, options.file)
1088         if (self.options.raw is None) and (options.file is None):
1089             print value
1090
1091         return value
1092
1093     def delete(self, options, args):
1094         """
1095         delete named slice (DeleteSliver)
1096         """
1097         server = self.sliceapi()
1098
1099         # slice urn
1100         slice_hrn = args[0]
1101         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1102
1103         # creds
1104         slice_cred = self.slice_credential_string(slice_hrn)
1105         creds = [slice_cred]
1106         
1107         # options and call_id when supported
1108         api_options = {}
1109         api_options ['call_id'] = unique_call_id()
1110         if options.show_credential:
1111             show_credentials(creds)
1112         result = server.DeleteSliver(slice_urn, creds, *self.ois(server, api_options ) )
1113         value = ReturnValue.get_value(result)
1114         if self.options.raw:
1115             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1116         else:
1117             print value
1118         return value 
1119   
1120     def status(self, options, args):
1121         """
1122         retrieve slice status (SliverStatus)
1123         """
1124         server = self.sliceapi()
1125
1126         # slice urn
1127         slice_hrn = args[0]
1128         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1129
1130         # creds 
1131         slice_cred = self.slice_credential_string(slice_hrn)
1132         creds = [slice_cred]
1133
1134         # options and call_id when supported
1135         api_options = {}
1136         api_options['call_id']=unique_call_id()
1137         if options.show_credential:
1138             show_credentials(creds)
1139         result = server.SliverStatus(slice_urn, creds, *self.ois(server,api_options))
1140         value = ReturnValue.get_value(result)
1141         if self.options.raw:
1142             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1143         else:
1144             print value
1145
1146     def start(self, options, args):
1147         """
1148         start named slice (Start)
1149         """
1150         server = self.sliceapi()
1151
1152         # the slice urn
1153         slice_hrn = args[0]
1154         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1155         
1156         # cred
1157         slice_cred = self.slice_credential_string(args[0])
1158         creds = [slice_cred]
1159         # xxx Thierry - does this not need an api_options as well ?
1160         result = server.Start(slice_urn, creds)
1161         value = ReturnValue.get_value(result)
1162         if self.options.raw:
1163             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1164         else:
1165             print value
1166         return value
1167     
1168     def stop(self, options, args):
1169         """
1170         stop named slice (Stop)
1171         """
1172         server = self.sliceapi()
1173         # slice urn
1174         slice_hrn = args[0]
1175         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1176         # cred
1177         slice_cred = self.slice_credential_string(args[0])
1178         creds = [slice_cred]
1179         result =  server.Stop(slice_urn, creds)
1180         value = ReturnValue.get_value(result)
1181         if self.options.raw:
1182             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1183         else:
1184             print value
1185         return value
1186     
1187     # reset named slice
1188     def reset(self, options, args):
1189         """
1190         reset named slice (reset_slice)
1191         """
1192         server = self.sliceapi()
1193         # slice urn
1194         slice_hrn = args[0]
1195         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1196         # cred
1197         slice_cred = self.slice_credential_string(args[0])
1198         creds = [slice_cred]
1199         result = server.reset_slice(creds, slice_urn)
1200         value = ReturnValue.get_value(result)
1201         if self.options.raw:
1202             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1203         else:
1204             print value
1205         return value
1206
1207     def renew(self, options, args):
1208         """
1209         renew slice (RenewSliver)
1210         """
1211         server = self.sliceapi()
1212         if len(args) != 2:
1213             self.print_help()
1214             sys.exit(1)
1215         [ slice_hrn, input_time ] = args
1216         # slice urn    
1217         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1218         # time: don't try to be smart on the time format, server-side will
1219         # creds
1220         slice_cred = self.slice_credential_string(args[0])
1221         creds = [slice_cred]
1222         # options and call_id when supported
1223         api_options = {}
1224         api_options['call_id']=unique_call_id()
1225         if options.show_credential:
1226             show_credentials(creds)
1227         result =  server.RenewSliver(slice_urn, creds, input_time, *self.ois(server,api_options))
1228         value = ReturnValue.get_value(result)
1229         if self.options.raw:
1230             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1231         else:
1232             print value
1233         return value
1234
1235
1236     def shutdown(self, options, args):
1237         """
1238         shutdown named slice (Shutdown)
1239         """
1240         server = self.sliceapi()
1241         # slice urn
1242         slice_hrn = args[0]
1243         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1244         # creds
1245         slice_cred = self.slice_credential_string(slice_hrn)
1246         creds = [slice_cred]
1247         result = server.Shutdown(slice_urn, creds)
1248         value = ReturnValue.get_value(result)
1249         if self.options.raw:
1250             save_raw_to_file(result, self.options.raw, self.options.rawformat, self.options.rawbanner)
1251         else:
1252             print value
1253         return value         
1254     
1255
1256     def get_ticket(self, options, args):
1257         """
1258         get a ticket for the specified slice
1259         """
1260         server = self.sliceapi()
1261         # slice urn
1262         slice_hrn, rspec_path = args[0], args[1]
1263         slice_urn = hrn_to_urn(slice_hrn, 'slice')
1264         # creds
1265         slice_cred = self.slice_credential_string(slice_hrn)
1266         creds = [slice_cred]
1267         # rspec
1268         rspec_file = self.get_rspec_file(rspec_path) 
1269         rspec = open(rspec_file).read()
1270         # options and call_id when supported
1271         api_options = {}
1272         api_options['call_id']=unique_call_id()
1273         # get ticket at the server
1274         ticket_string = server.GetTicket(slice_urn, creds, rspec, *self.ois(server,api_options))
1275         # save
1276         file = os.path.join(self.options.sfi_dir, get_leaf(slice_hrn) + ".ticket")
1277         self.logger.info("writing ticket to %s"%file)
1278         ticket = SfaTicket(string=ticket_string)
1279         ticket.save_to_file(filename=file, save_parents=True)
1280
1281     def redeem_ticket(self, options, args):
1282         """
1283         Connects to nodes in a slice and redeems a ticket
1284 (slice hrn is retrieved from the ticket)
1285         """
1286         ticket_file = args[0]
1287         
1288         # get slice hrn from the ticket
1289         # use this to get the right slice credential 
1290         ticket = SfaTicket(filename=ticket_file)
1291         ticket.decode()
1292         ticket_string = ticket.save_to_string(save_parents=True)
1293
1294         slice_hrn = ticket.gidObject.get_hrn()
1295         slice_urn = hrn_to_urn(slice_hrn, 'slice') 
1296         #slice_hrn = ticket.attributes['slivers'][0]['hrn']
1297         slice_cred = self.slice_credential_string(slice_hrn)
1298         
1299         # get a list of node hostnames from the RSpec 
1300         tree = etree.parse(StringIO(ticket.rspec))
1301         root = tree.getroot()
1302         hostnames = root.xpath("./network/site/node/hostname/text()")
1303         
1304         # create an xmlrpc connection to the component manager at each of these
1305         # components and gall redeem_ticket
1306         connections = {}
1307         for hostname in hostnames:
1308             try:
1309                 self.logger.info("Calling redeem_ticket at %(hostname)s " % locals())
1310                 cm_url="http://%s:%s/"%(hostname,CM_PORT)
1311                 server = SfaServerProxy(cm_url, self.private_key, self.my_gid)
1312                 server = self.server_proxy(hostname, CM_PORT, self.private_key, 
1313                                            timeout=self.options.timeout, verbose=self.options.debug)
1314                 server.RedeemTicket(ticket_string, slice_cred)
1315                 self.logger.info("Success")
1316             except socket.gaierror:
1317                 self.logger.error("redeem_ticket failed on %s: Component Manager not accepting requests"%hostname)
1318             except Exception, e:
1319                 self.logger.log_exc(e.message)
1320         return
1321
1322     def gid(self, options, args):
1323         """
1324         Create a GID (CreateGid)
1325         """
1326         if len(args) < 1:
1327             self.print_help()
1328             sys.exit(1)
1329         target_hrn = args[0]
1330         my_gid_string = open(self.client_bootstrap.my_gid()).read() 
1331         gid = self.registry().CreateGid(self.my_credential_string, target_hrn, my_gid_string)
1332         if options.file:
1333             filename = options.file
1334         else:
1335             filename = os.sep.join([self.options.sfi_dir, '%s.gid' % target_hrn])
1336         self.logger.info("writing %s gid to %s" % (target_hrn, filename))
1337         GID(string=gid).save_to_file(filename)
1338          
1339
1340     def delegate (self, options, args):
1341         """
1342         (locally) create delegate credential for use by given hrn
1343         """
1344         if len(args) != 1:
1345             self.print_help()
1346             sys.exit(1)
1347         to_hrn = args[0]
1348         # support for several delegations in the same call
1349         # so first we gather the things to do
1350         tuples=[]
1351         for slice_hrn in options.delegate_slices:
1352             message="%s.slice"%slice_hrn
1353             original = self.slice_credential_string(slice_hrn)
1354             tuples.append ( (message, original,) )
1355         if options.delegate_pi:
1356             my_authority=self.authority
1357             message="%s.pi"%my_authority
1358             original = self.my_authority_credential_string()
1359             tuples.append ( (message, original,) )
1360         for auth_hrn in options.delegate_auths:
1361             message="%s.auth"%auth_hrn
1362             original=self.authority_credential_string(auth_hrn)
1363             tuples.append ( (message, original, ) )
1364         # if nothing was specified at all at this point, let's assume -u
1365         if not tuples: options.delegate_user=True
1366         # this user cred
1367         if options.delegate_user:
1368             message="%s.user"%self.user
1369             original = self.my_credential_string
1370             tuples.append ( (message, original, ) )
1371
1372         # default type for beneficial is user unless -A
1373         if options.delegate_to_authority:       to_type='authority'
1374         else:                                   to_type='user'
1375
1376         # let's now handle all this
1377         # it's all in the filenaming scheme
1378         for (message,original) in tuples:
1379             delegated_string = self.client_bootstrap.delegate_credential_string(original, to_hrn, to_type)
1380             delegated_credential = Credential (string=delegated_string)
1381             filename = os.path.join ( self.options.sfi_dir,
1382                                       "%s_for_%s.%s.cred"%(message,to_hrn,to_type))
1383             delegated_credential.save_to_file(filename, save_parents=True)
1384             self.logger.info("delegated credential for %s to %s and wrote to %s"%(message,to_hrn,filename))
1385     
1386     def trusted(self, options, args):
1387         """
1388         return uhe trusted certs at this interface (get_trusted_certs)
1389         """ 
1390         trusted_certs = self.registry().get_trusted_certs()
1391         for trusted_cert in trusted_certs:
1392             gid = GID(string=trusted_cert)
1393             gid.dump()
1394             cert = Certificate(string=trusted_cert)
1395             self.logger.debug('Sfi.trusted -> %r'%cert.get_subject())
1396         return 
1397
1398     def config (self, options, args):
1399         "Display contents of current config"
1400         self.show_config()