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