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