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