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