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