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