dont forget to return after setting value in prompt_variable
[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                 return
262             else:
263                 print "Not a valid value"
264
265 def prompt_variables_all (cdef, cread, cwrite, show_comments):
266     try:
267         for (category_id, (category, variables)) in cread.variables().iteritems():
268             print ("========== Category = %s" % category_id.upper())
269             for variable in variables.values():
270                 try:
271                     newvar = prompt_variable (cdef, cread, cwrite, category, variable,
272                                               show_comments, True)
273                 except Exception, inst:
274                     if (str(inst) == 'NextCategory'): break
275                     else: raise
276
277     except Exception, inst:
278         if (str(inst) == 'BailOut'): return
279         else: raise
280
281 def prompt_variables_category (cdef, cread, cwrite, cid, show_comments):
282     cid=cid.lower()
283     CID=cid.upper()
284     try:
285         print ("========== Category = %s" % CID)
286         for vid in list_category(cdef,cid):
287             (category,variable) = cdef.locate_varname(vid.upper())
288             newvar = prompt_variable (cdef, cread, cwrite, category, variable,
289                                       show_comments, False)
290     except Exception, inst:
291         if (str(inst) == 'BailOut'): return
292         else: raise
293
294 ####################
295 def show_variable (cdef, cread, cwrite,
296                    category, variable,show_value,show_comments):
297     assert category.has_key('id')
298     assert variable.has_key('id')
299
300     category_id = category ['id']
301     variable_id = variable['id']
302
303     default_value = get_value(cdef,category_id,variable_id)
304     current_value = get_current_value(cread,cwrite,category_id,variable_id)
305     varname = get_varname (cread,category_id, variable_id)
306     if show_comments :
307         print_name_comments (cdef, category_id, variable_id)
308     if show_value:
309         print "%s = %s" % (varname,current_value)
310     else:
311         print "%s" % (varname)
312
313 def show_variables_all (cdef, cread, cwrite, show_value, show_comments):
314     for (category_id, (category, variables)) in cread.variables().iteritems():
315         print ("========== Category = %s" % category_id.upper())
316         for variable in variables.values():
317             show_variable (cdef, cread, cwrite,
318                            category, variable,show_value,show_comments)
319
320 def show_variables_category (cdef, cread, cwrite, cid, show_value,show_comments):
321     cid=cid.lower()
322     CID=cid.upper()
323     print ("========== Category = %s" % CID)
324     for vid in list_category(cdef,cid):
325         (category,variable) = cdef.locate_varname(vid.upper())
326         show_variable (cdef, cread, cwrite, category, variable,
327                        show_value,show_comments)
328
329 ####################
330 re_mainloop_0arg="^(?P<command>[uUwrRqlLsSeEcvVhH\?])[ \t]*$"
331 re_mainloop_1arg="^(?P<command>[sSeEvV])[ \t]+(?P<arg>\w+)$"
332 matcher_mainloop_0arg=re.compile(re_mainloop_0arg)
333 matcher_mainloop_1arg=re.compile(re_mainloop_1arg)
334
335 def mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config):
336     global service
337     while True:
338         try:
339             answer = raw_input("Enter command (u for usual changes, w to save, ? for help) ").strip()
340         except EOFError:
341             answer =""
342         except KeyboardInterrupt:
343             print "\nBye"
344             sys.exit()
345
346         if (answer == "") or (answer in "?hH"):
347             print mainloop_usage
348             continue
349         groups_parse = matcher_mainloop_0arg.match(answer)
350         command=None
351         if (groups_parse):
352             command = groups_parse.group('command')
353             arg=None
354         else:
355             groups_parse = matcher_mainloop_1arg.match(answer)
356             if (groups_parse):
357                 command = groups_parse.group('command')
358                 arg=groups_parse.group('arg')
359         if not command:
360             print ("Unknown command >%s< -- use h for help" % answer)
361             continue
362
363         show_comments=command.isupper()
364
365         mode='ALL'
366         if arg:
367             mode=None
368             arg=arg.lower()
369             variables=list_category (cdef,arg)
370             if len(variables):
371                 # category_id as the category name
372                 # variables as the list of variable names
373                 mode='CATEGORY'
374                 category_id=arg
375             arg=arg.upper()
376             (category,variable)=cdef.locate_varname(arg)
377             if variable:
378                 # category/variable as output by locate_varname
379                 mode='VARIABLE'
380             if not mode:
381                 print "%s: no such category or variable" % arg
382                 continue
383
384         if command in "qQ":
385             # todo check confirmation
386             return
387         elif command == "w":
388             try:
389                 # Confirm that various constraints are met before saving file.
390                 validate_variables = g_configuration.get('validate_variables',{})
391                 validated_variables = cwrite.verify(cdef, cread, validate_variables)
392                 validator = g_configuration.get('validator',noop_validator)
393                 validator(validated_variables)
394                 cwrite.save(site_config)
395             except:
396                 print "Save failed due to a configuration exception:"
397                 print traceback.print_exc()
398                 print ("Could not save -- fix write access on %s" % site_config)
399                 break
400             print ("Wrote %s" % site_config)
401             consolidate(default_config, site_config, consolidated_config)
402             print ("You might want to type 'r' (restart %s), 'R' (reload %s) or 'q' (quit)" % \
403                    (service,service))
404         elif command in "uU":
405             global usual_variables
406             try:
407                 for varname in usual_variables:
408                     (category,variable) = cdef.locate_varname(varname)
409                     if not (category is None and variable is None):
410                         prompt_variable(cdef, cread, cwrite, category, variable, False)
411             except Exception, inst:
412                 if (str(inst) != 'BailOut'):
413                     raise
414         elif command == "r":
415             restart_service()
416         elif command == "R":
417             reload_service()
418         elif command == "c":
419             print_categories(cread)
420         elif command in "eE":
421             if mode == 'ALL':
422                 prompt_variables_all(cdef, cread, cwrite,show_comments)
423             elif mode == 'CATEGORY':
424                 prompt_variables_category(cdef,cread,cwrite,category_id,show_comments)
425             elif mode == 'VARIABLE':
426                 try:
427                     prompt_variable (cdef,cread,cwrite,category,variable,
428                                      show_comments,False)
429                 except Exception, inst:
430                     if str(inst) != 'BailOut':
431                         raise
432         elif command in "vVsSlL":
433             show_value=(command in "sSlL")
434             (c1,c2,c3) = (cdef, cread, cwrite)
435             if command in "lL":
436                 (c1,c2,c3) = (cwrite,cwrite,cwrite)
437             if mode == 'ALL':
438                 show_variables_all(c1,c2,c3,show_value,show_comments)
439             elif mode == 'CATEGORY':
440                 show_variables_category(c1,c2,c3,category_id,show_value,show_comments)
441             elif mode == 'VARIABLE':
442                 show_variable (c1,c2,c3,category,variable,show_value,show_comments)
443         else:
444             print ("Unknown command >%s< -- use h for help" % answer)
445
446
447 ####################
448 # creates directory for file if not yet existing
449 def check_dir (config_file):
450     dirname = os.path.dirname (config_file)
451     if (not os.path.exists (dirname)):
452         try:
453             os.makedirs(dirname,0755)
454         except OSError, e:
455             print "Cannot create dir %s due to %s - exiting" % (dirname,e)
456             sys.exit(1)
457
458         if (not os.path.exists (dirname)):
459             print "Cannot create dir %s - exiting" % dirname
460             sys.exit(1)
461         else:
462             print "Created directory %s" % dirname
463
464 ####################
465 def optParserSetup(configuration):
466     parser = OptionParser(usage=usage(), version="%prog " + release_rev + release_url )
467     parser.set_defaults(config_dir=configuration['config_dir'],
468                         service=configuration['service'],
469                         usual_variables=configuration['usual_variables'])
470     parser.add_option("","--configdir",dest="config_dir",help="specify configuration directory")
471     parser.add_option("","--service",dest="service",help="specify /etc/init.d style service name")
472     parser.add_option("","--usual_variable",dest="usual_variables",action="append", help="add a usual variable")
473     return parser
474
475 def main(command,argv,configuration):
476     global g_configuration
477     g_configuration=configuration
478
479     parser = optParserSetup(configuration)
480     (config,args) = parser.parse_args()
481     if len(args)>3:
482         parser.error("too many arguments")
483
484     configuration['service']=config.service
485     configuration['usual_variables']=config.usual_variables
486     configuration['config_dir']=config.config_dir
487     # add in new usual_variables defined on the command line
488     for usual_variable in config.usual_variables:
489         if usual_variable not in configuration['usual_variables']:
490             configuration['usual_variables'].append(usual_variable)
491
492     # intialize configuration
493     init_configuration()
494
495     (default_config,site_config,consolidated_config) = (def_default_config, def_site_config, def_consolidated_config)
496     if len(args) >= 1:
497         default_config=args[0]
498     if len(args) >= 2:
499         site_config=args[1]
500     if len(args) == 3:
501         consolidated_config=args[2]
502
503     for c in (default_config,site_config,consolidated_config):
504         check_dir (c)
505
506     try:
507         # the default settings only - read only
508         cdef = Config(default_config)
509
510         # in effect : default settings + local settings - read only
511         cread = Config(default_config)
512     except:
513         print traceback.print_exc()
514         print ("default config files %s not found, is myplc installed ?" % default_config)
515         return 1
516
517     # local settings only, will be modified & saved
518     config_filename = "%s/sfa_config" % config.config_dir
519     try:
520         cwrite=Config(config_filename)
521     except ConfigParser.MissingSectionHeaderError:
522         # remove legacy config 
523         os.unlink(config_filename)
524         cwrite=Config(config_filename)
525
526     try:
527         cread.load(site_config)
528         cwrite.load(site_config)
529     except:
530         cwrite = Config()
531
532     mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config)
533     return 0   
534
535 if __name__ == '__main__':
536     command=sys.argv[0]
537     argv = sys.argv[1:]
538     main(command,argv,configuration)