fix so that sfadump can be used to inspect gids and creds
[sfa.git] / config / sfa-config-tty
1 #!/usr/bin/env python
2
3 import os
4 import sys
5 import re
6 import time
7 import traceback
8 import types
9 import readline
10 from StringIO import StringIO
11 from optparse import OptionParser
12
13 from sfa.util.version import version_tag
14 from sfa.util.config import Config
15
16 def validator(validated_variables):
17     pass
18 #    maint_user = validated_variables["PLC_API_MAINTENANCE_USER"]
19 #    root_user = validated_variables["PLC_ROOT_USER"]
20 #    if maint_user == root_user:
21 #        errStr="PLC_API_MAINTENANCE_USER=%s cannot be the same as PLC_ROOT_USER=%s"%(maint_user,root_user)
22 #        raise plc_config.ConfigurationException(errStr)
23
24 usual_variables = [
25     "SFA_GENERIC_FLAVOUR",
26     "SFA_INTERFACE_HRN",
27     "SFA_REGISTRY_ROOT_AUTH",
28     "SFA_REGISTRY_HOST", 
29     "SFA_AGGREGATE_HOST",
30     "SFA_SM_HOST",
31     "SFA_DB_HOST",
32     ]
33
34 flavour_xml_section_hash = { \
35     'pl':'sfa_plc',
36     'openstack':'sfa_nova',
37     'fd':'sfa_federica',
38     'nitos':'sfa_nitos',
39     'dummy':'sfa_dummy',
40     }
41 configuration={ \
42     'name':'sfa',
43     'service':"sfa",
44     'usual_variables':usual_variables,
45     'config_dir':"/etc/sfa",
46     'validate_variables':{},
47     'validator':validator,
48     }
49
50
51 # GLOBAL VARIABLES
52 #
53 g_configuration=None
54 usual_variables=None
55 config_dir=None
56 service=None
57
58 def noop_validator(validated_variables):
59     pass
60
61 # historically we could also configure the devel pkg....
62 def init_configuration ():
63     global g_configuration
64     global usual_variables, config_dir, service
65
66     usual_variables=g_configuration["usual_variables"]
67     config_dir=g_configuration["config_dir"]
68     service=g_configuration["service"]
69
70     global def_default_config, def_site_config, def_consolidated_config
71     def_default_config= "%s/default_config.xml" % config_dir
72     def_site_config = "%s/configs/site_config" % config_dir
73     def_consolidated_config = "%s/%s_config" % (config_dir, service)
74
75     global mainloop_usage
76     mainloop_usage= """Available commands:
77  Uppercase versions give variables comments, when available
78  u/U\t\t\tEdit usual variables
79  w\t\t\tWrite
80  r\t\t\tRestart %(service)s service
81  R\t\t\tReload %(service)s service (rebuild config files for sh, python....)
82  q\t\t\tQuit (without saving)
83  h/?\t\t\tThis help
84 ---
85  l/L [<cat>|<var>]\tShow Locally modified variables/values
86  s/S [<cat>|<var>]\tShow variables/values (all, in category, single)
87  e/E [<cat>|<var>]\tEdit variables (all, in category, single)
88 ---
89  c\t\t\tList categories
90  v/V [<cat>|<var>]\tList Variables (all, in category, single)
91 ---
92 Typical usage involves: u, [l,] w, r, q
93 """ % globals()
94
95 def usage ():
96     command_usage="%prog [options] [default-xml [site-xml [consolidated-xml]]]"
97     init_configuration ()
98     command_usage +="""
99 \t default-xml defaults to %s
100 \t site-xml defaults to %s
101 \t consolidated-xml defaults to %s""" % (def_default_config,def_site_config, def_consolidated_config)
102     return command_usage
103
104 ####################
105 variable_usage= """Edit Commands :
106 #\tShow variable comments
107 .\tStops prompting, return to mainloop
108 /\tCleans any site-defined value, reverts to default
109 =\tShows default value
110 >\tSkips to next category
111 ?\tThis help
112 """
113
114 ####################
115 def get_value (config,  category_id, variable_id):
116     value = config.get (category_id, variable_id)
117     return value
118
119 def get_type (config, category_id, variable_id):
120     value = config.get (category_id, variable_id)
121     #return variable['type']
122     return str
123
124 def get_current_value (cread, cwrite, category_id, variable_id):
125     # the value stored in cwrite, if present, is the one we want
126     try:
127         result=get_value (cwrite,category_id,variable_id)
128     except:
129         result=get_value (cread,category_id,variable_id)
130     return result
131
132 # refrain from using plc_config's _sanitize
133 def get_varname (config,  category_id, variable_id):
134     varname = category_id +"_"+ variable_id
135     config.locate_varname(varname)
136     return varname
137
138 # could not avoid using _sanitize here..
139 def get_name_comments (config, cid, vid):
140     try:
141         (category, variable) = config.get (cid, vid)
142         (id, name, value, comments) = config._sanitize_variable (cid,variable)
143         return (name,comments)
144     except:
145         return (None,[])
146
147 def print_name_comments (config, cid, vid):
148     (name,comments)=get_name_comments(config,cid,vid)
149     if name:
150         print "### %s" % name
151     if comments:
152         for line in comments:
153             print "# %s" % line
154     else:
155         print "!!! No comment associated to %s_%s" % (cid,vid)
156
157 ####################
158 def list_categories (config):
159     result=[]
160     for section in config.sections():
161         result += [section]
162     return result
163
164 def print_categories (config):
165     print "Known categories"
166     for cid in list_categories(config):
167         print "%s" % (cid.upper())
168
169 ####################
170 def list_category (config, cid):
171     result=[]
172     for section in config.sections():
173         if section == cid.lower():
174             for (name,value) in config.items(section):
175                 result += ["%s_%s" %(cid,name)]    
176     return result
177
178 def print_category (config, cid, show_comments=True):
179     cid=cid.lower()
180     CID=cid.upper()
181     vids=list_category(config,cid)
182     if (len(vids) == 0):
183         print "%s : no such category"%CID
184     else:
185         print "Category %s contains" %(CID)
186         for vid in vids:
187             print vid.upper()
188
189 ####################
190 def consolidate (default_config, site_config, consolidated_config):
191     global service
192     try:
193         conso = Config(default_config)
194         conso.load (site_config)
195         conso.save (consolidated_config)
196     except Exception, inst:
197         print "Could not consolidate, %s" % (str(inst))
198         return
199     print ("Merged\n\t%s\nand\t%s\ninto\t%s"%(default_config,site_config,
200                                               consolidated_config))
201
202 def reload_service ():
203     global service
204     os.system("set -x ; service %s reload" % service)
205
206 ####################
207 def restart_service ():
208     global service
209     print ("==================== Stopping %s" % service)
210     os.system("service %s stop" % service)
211     print ("==================== Starting %s" % service)
212     os.system("service %s start" % service)
213
214 ####################
215 def prompt_variable (cdef, cread, cwrite, category, variable,
216                      show_comments, support_next=False):
217
218
219     category_id = category
220     variable_id = variable
221
222     while True:
223         default_value = get_value(cdef,category_id,variable_id)
224         variable_type = get_type(cdef,category_id,variable_id)
225         current_value = get_current_value(cread,cwrite,category_id, variable_id)
226         varname = get_varname (cread,category_id, variable_id)
227
228         if show_comments :
229             print_name_comments (cdef, category_id, variable_id)
230         prompt = "== %s : [%s] " % (varname,current_value)
231         try:
232             answer = raw_input(prompt).strip()
233         except EOFError :
234             raise Exception ('BailOut')
235         except KeyboardInterrupt:
236             print "\n"
237             raise Exception ('BailOut')
238
239         # no change
240         if (answer == "") or (answer == current_value):
241             return None
242         elif (answer == "."):
243             raise Exception ('BailOut')
244         elif (answer == "#"):
245             print_name_comments(cread,category_id,variable_id)
246         elif (answer == "?"):
247             print variable_usage.strip()
248         elif (answer == "="):
249             print ("%s defaults to %s" %(varname,default_value))
250         # revert to default : remove from cwrite (i.e. site-config)
251         elif (answer == "/"):
252             cwrite.delete(category_id,variable_id)
253             print ("%s reverted to %s" %(varname,default_value))
254             return
255         elif (answer == ">"):
256             if support_next:
257                 raise Exception ('NextCategory')
258             else:
259                 print "No support for next category"
260         else:
261             if cdef.validate_type(variable_type, answer):
262                 cwrite.set(category_id, variable_id, answer)
263                 return
264             else:
265                 print "Not a valid value"
266
267 def prompt_variables_all (cdef, cread, cwrite, show_comments):
268     try:
269         for (category_id, (category, variables)) in cread.variables().iteritems():
270             print ("========== Category = %s" % category_id.upper())
271             for variable in variables.values():
272                 try:
273                     newvar = prompt_variable (cdef, cread, cwrite, category, variable,
274                                               show_comments, True)
275                 except Exception, inst:
276                     if (str(inst) == 'NextCategory'): break
277                     else: raise
278
279     except Exception, inst:
280         if (str(inst) == 'BailOut'): return
281         else: raise
282
283 def prompt_variables_category (cdef, cread, cwrite, cid, show_comments):
284     cid=cid.lower()
285     CID=cid.upper()
286     try:
287         print ("========== Category = %s" % CID)
288         for vid in list_category(cdef,cid):
289             (category,variable) = cdef.locate_varname(vid.upper())
290             newvar = prompt_variable (cdef, cread, cwrite, category, variable,
291                                       show_comments, False)
292     except Exception, inst:
293         if (str(inst) == 'BailOut'): return
294         else: raise
295
296 ####################
297 def show_variable (cdef, cread, cwrite,
298                    category, variable,show_value,show_comments):
299     assert category.has_key('id')
300     assert variable.has_key('id')
301
302     category_id = category ['id']
303     variable_id = variable['id']
304
305     default_value = get_value(cdef,category_id,variable_id)
306     current_value = get_current_value(cread,cwrite,category_id,variable_id)
307     varname = get_varname (cread,category_id, variable_id)
308     if show_comments :
309         print_name_comments (cdef, category_id, variable_id)
310     if show_value:
311         print "%s = %s" % (varname,current_value)
312     else:
313         print "%s" % (varname)
314
315 def show_variables_all (cdef, cread, cwrite, show_value, show_comments):
316     for (category_id, (category, variables)) in cread.variables().iteritems():
317         print ("========== Category = %s" % category_id.upper())
318         for variable in variables.values():
319             show_variable (cdef, cread, cwrite,
320                            category, variable,show_value,show_comments)
321
322 def show_variables_category (cdef, cread, cwrite, cid, show_value,show_comments):
323     cid=cid.lower()
324     CID=cid.upper()
325     print ("========== Category = %s" % CID)
326     for vid in list_category(cdef,cid):
327         (category,variable) = cdef.locate_varname(vid.upper())
328         show_variable (cdef, cread, cwrite, category, variable,
329                        show_value,show_comments)
330
331 ####################
332 re_mainloop_0arg="^(?P<command>[uUwrRqlLsSeEcvVhH\?])[ \t]*$"
333 re_mainloop_1arg="^(?P<command>[sSeEvV])[ \t]+(?P<arg>\w+)$"
334 matcher_mainloop_0arg=re.compile(re_mainloop_0arg)
335 matcher_mainloop_1arg=re.compile(re_mainloop_1arg)
336
337 def mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config):
338     global service
339     while True:
340         try:
341             answer = raw_input("Enter command (u for usual changes, w to save, ? for help) ").strip()
342         except EOFError:
343             answer =""
344         except KeyboardInterrupt:
345             print "\nBye"
346             sys.exit()
347
348         if (answer == "") or (answer in "?hH"):
349             print mainloop_usage
350             continue
351         groups_parse = matcher_mainloop_0arg.match(answer)
352         command=None
353         if (groups_parse):
354             command = groups_parse.group('command')
355             arg=None
356         else:
357             groups_parse = matcher_mainloop_1arg.match(answer)
358             if (groups_parse):
359                 command = groups_parse.group('command')
360                 arg=groups_parse.group('arg')
361         if not command:
362             print ("Unknown command >%s< -- use h for help" % answer)
363             continue
364
365         show_comments=command.isupper()
366
367         mode='ALL'
368         if arg:
369             mode=None
370             arg=arg.lower()
371             variables=list_category (cdef,arg)
372             if len(variables):
373                 # category_id as the category name
374                 # variables as the list of variable names
375                 mode='CATEGORY'
376                 category_id=arg
377             arg=arg.upper()
378             (category,variable)=cdef.locate_varname(arg)
379             if variable:
380                 # category/variable as output by locate_varname
381                 mode='VARIABLE'
382             if not mode:
383                 print "%s: no such category or variable" % arg
384                 continue
385
386         if command in "qQ":
387             # todo check confirmation
388             return
389         elif command == "w":
390             try:
391                 # Confirm that various constraints are met before saving file.
392                 validate_variables = g_configuration.get('validate_variables',{})
393                 validated_variables = cwrite.verify(cdef, cread, validate_variables)
394                 validator = g_configuration.get('validator',noop_validator)
395                 validator(validated_variables)
396                 cwrite.save(site_config)
397             except:
398                 print "Save failed due to a configuration exception:"
399                 print traceback.print_exc()
400                 print ("Could not save -- fix write access on %s" % site_config)
401                 break
402             print ("Wrote %s" % site_config)
403             consolidate(default_config, site_config, consolidated_config)
404             print ("You might want to type 'r' (restart %s), 'R' (reload %s) or 'q' (quit)" % \
405                    (service,service))
406         elif command in "uU":
407             global usual_variables
408             global flavour_xml_section_hash
409             try:
410                 for varname in usual_variables:
411                     (category,variable) = cdef.locate_varname(varname)
412                     if not (category is None and variable is None):
413                         prompt_variable(cdef, cread, cwrite, category, variable, False)
414                 
415                 # set the driver variable according to the already set flavour 
416                 generic_flavour = cwrite.items('sfa')[0][1]
417                 for section in cdef.sections():
418                      if generic_flavour in flavour_xml_section_hash and flavour_xml_section_hash[generic_flavour] == section:
419                          for item in cdef.items(section):
420                               category = section
421                               variable = item[0]
422                               prompt_variable(cdef, cread, cwrite, category, variable, False)
423                          break
424
425             except Exception, inst:
426                 if (str(inst) != 'BailOut'):
427                     raise
428         elif command == "r":
429             restart_service()
430         elif command == "R":
431             reload_service()
432         elif command == "c":
433             print_categories(cread)
434         elif command in "eE":
435             if mode == 'ALL':
436                 prompt_variables_all(cdef, cread, cwrite,show_comments)
437             elif mode == 'CATEGORY':
438                 prompt_variables_category(cdef,cread,cwrite,category_id,show_comments)
439             elif mode == 'VARIABLE':
440                 try:
441                     prompt_variable (cdef,cread,cwrite,category,variable,
442                                      show_comments,False)
443                 except Exception, inst:
444                     if str(inst) != 'BailOut':
445                         raise
446         elif command in "vVsSlL":
447             show_value=(command in "sSlL")
448             (c1,c2,c3) = (cdef, cread, cwrite)
449             if command in "lL":
450                 (c1,c2,c3) = (cwrite,cwrite,cwrite)
451             if mode == 'ALL':
452                 show_variables_all(c1,c2,c3,show_value,show_comments)
453             elif mode == 'CATEGORY':
454                 show_variables_category(c1,c2,c3,category_id,show_value,show_comments)
455             elif mode == 'VARIABLE':
456                 show_variable (c1,c2,c3,category,variable,show_value,show_comments)
457         else:
458             print ("Unknown command >%s< -- use h for help" % answer)
459
460
461 ####################
462 # creates directory for file if not yet existing
463 def check_dir (config_file):
464     dirname = os.path.dirname (config_file)
465     if (not os.path.exists (dirname)):
466         try:
467             os.makedirs(dirname,0755)
468         except OSError, e:
469             print "Cannot create dir %s due to %s - exiting" % (dirname,e)
470             sys.exit(1)
471
472         if (not os.path.exists (dirname)):
473             print "Cannot create dir %s - exiting" % dirname
474             sys.exit(1)
475         else:
476             print "Created directory %s" % dirname
477
478 ####################
479 def optParserSetup(configuration):
480     parser = OptionParser(usage=usage(), version="%prog " + version_tag )
481     parser.set_defaults(config_dir=configuration['config_dir'],
482                         service=configuration['service'],
483                         usual_variables=configuration['usual_variables'])
484     parser.add_option("","--configdir",dest="config_dir",help="specify configuration directory")
485     parser.add_option("","--service",dest="service",help="specify /etc/init.d style service name")
486     parser.add_option("","--usual_variable",dest="usual_variables",action="append", help="add a usual variable")
487     return parser
488
489 def main(command,argv,configuration):
490     global g_configuration
491     g_configuration=configuration
492
493     parser = optParserSetup(configuration)
494     (config,args) = parser.parse_args()
495     if len(args)>3:
496         parser.error("too many arguments")
497
498     configuration['service']=config.service
499     configuration['usual_variables']=config.usual_variables
500     configuration['config_dir']=config.config_dir
501     # add in new usual_variables defined on the command line
502     for usual_variable in config.usual_variables:
503         if usual_variable not in configuration['usual_variables']:
504             configuration['usual_variables'].append(usual_variable)
505
506     # intialize configuration
507     init_configuration()
508
509     (default_config,site_config,consolidated_config) = (def_default_config, def_site_config, def_consolidated_config)
510     if len(args) >= 1:
511         default_config=args[0]
512     if len(args) >= 2:
513         site_config=args[1]
514     if len(args) == 3:
515         consolidated_config=args[2]
516
517     for c in (default_config,site_config,consolidated_config):
518         check_dir (c)
519
520     try:
521         # the default settings only - read only
522         cdef = Config(default_config)
523
524         # in effect : default settings + local settings - read only
525         cread = Config(default_config)
526     except:
527         print traceback.print_exc()
528         print ("default config files %s not found, is myplc installed ?" % default_config)
529         return 1
530
531     # local settings only, will be modified & saved
532     config_filename = "%s/sfa_config" % config.config_dir
533     cwrite=Config(config_filename)
534     try:
535         cread.load(site_config)
536         cwrite.load(default_config)
537         cwrite.load(site_config)
538     except:
539         cwrite = Config()
540
541     mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config)
542     return 0   
543
544 if __name__ == '__main__':
545     command=sys.argv[0]
546     argv = sys.argv[1:]
547     main(command,argv,configuration)