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