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