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