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