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