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