#!/usr/bin/env python3 import os import sys import re import traceback import readline from optparse import OptionParser from sfa.util.version import version_tag from sfa.util.config import Config def validator(validated_variables): pass # maint_user = validated_variables["PLC_API_MAINTENANCE_USER"] # root_user = validated_variables["PLC_ROOT_USER"] # if maint_user == root_user: # errStr="PLC_API_MAINTENANCE_USER=%s cannot be the same as PLC_ROOT_USER=%s"%(maint_user, root_user) # raise plc_config.ConfigurationException(errStr) usual_variables = [ "SFA_GENERIC_FLAVOUR", "SFA_INTERFACE_HRN", "SFA_REGISTRY_ROOT_AUTH", "SFA_REGISTRY_HOST", "SFA_AGGREGATE_HOST", "SFA_DB_HOST", ] flavour_xml_section_hash = { 'pl': 'sfa_plc', 'dummy': 'sfa_dummy', } configuration = { 'name': 'sfa', 'service': "sfa", 'usual_variables': usual_variables, 'config_dir': "/etc/sfa", 'validate_variables': {}, 'validator': validator, } # GLOBAL VARIABLES # g_configuration = None usual_variables = None config_dir = None service = None def noop_validator(validated_variables): pass # historically we could also configure the devel pkg.... def init_configuration(): global g_configuration global usual_variables, config_dir, service usual_variables = g_configuration["usual_variables"] config_dir = g_configuration["config_dir"] service = g_configuration["service"] global def_default_config, def_site_config, def_consolidated_config def_default_config = "%s/default_config.xml" % config_dir def_site_config = "%s/configs/site_config" % config_dir def_consolidated_config = "%s/%s_config" % (config_dir, service) global mainloop_usage mainloop_usage = """Available commands: Uppercase versions give variables comments, when available u/U\t\t\tEdit usual variables w\t\t\tWrite r\t\t\tRestart %(service)s service R\t\t\tReload %(service)s service (rebuild config files for sh, python....) q\t\t\tQuit (without saving) h/?\t\t\tThis help --- l/L [|]\tShow Locally modified variables/values s/S [|]\tShow variables/values (all, in category, single) e/E [|]\tEdit variables (all, in category, single) --- c\t\t\tList categories v/V [|]\tList Variables (all, in category, single) --- Typical usage involves: u, [l,] w, r, q """ % globals() def usage(): command_usage = "%prog [options] [default-xml [site-xml [consolidated-xml]]]" init_configuration() command_usage += """ \t default-xml defaults to %s \t site-xml defaults to %s \t consolidated-xml defaults to %s""" % (def_default_config, def_site_config, def_consolidated_config) return command_usage #################### variable_usage = """Edit Commands : #\tShow variable comments .\tStops prompting, return to mainloop /\tCleans any site-defined value, reverts to default =\tShows default value >\tSkips to next category ?\tThis help """ #################### def get_value(config, category_id, variable_id): value = config.get(category_id, variable_id) return value def get_type(config, category_id, variable_id): value = config.get(category_id, variable_id) # return variable['type'] return str def get_current_value(cread, cwrite, category_id, variable_id): # the value stored in cwrite, if present, is the one we want try: result = get_value(cwrite, category_id, variable_id) except: result = get_value(cread, category_id, variable_id) return result # refrain from using plc_config's _sanitize def get_varname(config, category_id, variable_id): varname = category_id + "_" + variable_id config.locate_varname(varname) return varname # could not avoid using _sanitize here.. def get_name_comments(config, cid, vid): try: (category, variable) = config.get(cid, vid) (id, name, value, comments) = config._sanitize_variable(cid, variable) return (name, comments) except: return (None, []) def print_name_comments(config, cid, vid): name, comments = get_name_comments(config, cid, vid) if name: print("### %s" % name) if comments: for line in comments: print("# %s" % line) else: print("!!! No comment associated to %s_%s" % (cid, vid)) #################### def list_categories(config): result = [] for section in config.sections(): result += [section] return result def print_categories(config): print("Known categories") for cid in list_categories(config): print("%s" % (cid.upper())) #################### def list_category(config, cid): result = [] for section in config.sections(): if section == cid.lower(): for (name, value) in config.items(section): result += ["%s_%s" % (cid, name)] return result def print_category(config, cid, show_comments=True): cid = cid.lower() CID = cid.upper() vids = list_category(config, cid) if (len(vids) == 0): print("%s : no such category" % CID) else: print("Category %s contains" % (CID)) for vid in vids: print(vid.upper()) #################### def consolidate(default_config, site_config, consolidated_config): global service try: conso = Config(default_config) conso.load(site_config) conso.save(consolidated_config) except Exception as inst: print("Could not consolidate, %s" % (str(inst))) return print(("Merged\n\t%s\nand\t%s\ninto\t%s" % (default_config, site_config, consolidated_config))) def reload_service(): reload = "sfa-setup.sh reload" print(("Running: {}".format(reload))) os.system(reload) #################### def restart_service(): services = ('sfa-db', 'sfa-aggregate', 'sfa-registry') for service in services: restart = ("systemctl -q is-active {s} && " "{{ echo restarting {s} ; systemctl restart {s}; }}" .format(s=service)) os.system(restart) #################### def prompt_variable(cdef, cread, cwrite, category, variable, show_comments, support_next=False): category_id = category variable_id = variable while True: default_value = get_value(cdef, category_id, variable_id) variable_type = get_type(cdef, category_id, variable_id) current_value = get_current_value( cread, cwrite, category_id, variable_id) varname = get_varname(cread, category_id, variable_id) if show_comments: print_name_comments(cdef, category_id, variable_id) prompt = "== %s : [%s] " % (varname, current_value) try: answer = input(prompt).strip() except EOFError: raise Exception('BailOut') except KeyboardInterrupt: print("\n") raise Exception('BailOut') # no change if (answer == "") or (answer == current_value): return None elif (answer == "."): raise Exception('BailOut') elif (answer == "#"): print_name_comments(cread, category_id, variable_id) elif (answer == "?"): print(variable_usage.strip()) elif (answer == "="): print(("%s defaults to %s" % (varname, default_value))) # revert to default : remove from cwrite (i.e. site-config) elif (answer == "/"): cwrite.delete(category_id, variable_id) print(("%s reverted to %s" % (varname, default_value))) return elif (answer == ">"): if support_next: raise Exception('NextCategory') else: print("No support for next category") else: if cdef.validate_type(variable_type, answer): cwrite.set(category_id, variable_id, answer) return else: print("Not a valid value") def prompt_variables_all(cdef, cread, cwrite, show_comments): try: for (category_id, (category, variables)) in cread.variables().items(): print(("========== Category = %s" % category_id.upper())) for variable in list(variables.values()): try: newvar = prompt_variable(cdef, cread, cwrite, category, variable, show_comments, True) except Exception as inst: if (str(inst) == 'NextCategory'): break else: raise except Exception as inst: if (str(inst) == 'BailOut'): return else: raise def prompt_variables_category(cdef, cread, cwrite, cid, show_comments): cid = cid.lower() CID = cid.upper() try: print(("========== Category = %s" % CID)) for vid in list_category(cdef, cid): (category, variable) = cdef.locate_varname(vid.upper()) newvar = prompt_variable(cdef, cread, cwrite, category, variable, show_comments, False) except Exception as inst: if (str(inst) == 'BailOut'): return else: raise #################### def show_variable(cdef, cread, cwrite, category, variable, show_value, show_comments): assert 'id' in category assert 'id' in variable category_id = category['id'] variable_id = variable['id'] default_value = get_value(cdef, category_id, variable_id) current_value = get_current_value(cread, cwrite, category_id, variable_id) varname = get_varname(cread, category_id, variable_id) if show_comments: print_name_comments(cdef, category_id, variable_id) if show_value: print("%s = %s" % (varname, current_value)) else: print("%s" % (varname)) def show_variables_all(cdef, cread, cwrite, show_value, show_comments): for (category_id, (category, variables)) in cread.variables().items(): print(("========== Category = %s" % category_id.upper())) for variable in list(variables.values()): show_variable(cdef, cread, cwrite, category, variable, show_value, show_comments) def show_variables_category(cdef, cread, cwrite, cid, show_value, show_comments): cid = cid.lower() CID = cid.upper() print(("========== Category = %s" % CID)) for vid in list_category(cdef, cid): (category, variable) = cdef.locate_varname(vid.upper()) show_variable(cdef, cread, cwrite, category, variable, show_value, show_comments) #################### re_mainloop_0arg = "^(?P[uUwrRqlLsSeEcvVhH\?])[ \t]*$" re_mainloop_1arg = "^(?P[sSeEvV])[ \t]+(?P\w+)$" matcher_mainloop_0arg = re.compile(re_mainloop_0arg) matcher_mainloop_1arg = re.compile(re_mainloop_1arg) def mainloop(cdef, cread, cwrite, default_config, site_config, consolidated_config): global service while True: try: answer = input( "Enter command (u for usual changes, w to save, ? for help) ").strip() except EOFError: answer = "" except KeyboardInterrupt: print("\nBye") sys.exit() if (answer == "") or (answer in "?hH"): print(mainloop_usage) continue groups_parse = matcher_mainloop_0arg.match(answer) command = None if (groups_parse): command = groups_parse.group('command') arg = None else: groups_parse = matcher_mainloop_1arg.match(answer) if (groups_parse): command = groups_parse.group('command') arg = groups_parse.group('arg') if not command: print(("Unknown command >%s< -- use h for help" % answer)) continue show_comments = command.isupper() mode = 'ALL' if arg: mode = None arg = arg.lower() variables = list_category(cdef, arg) if len(variables): # category_id as the category name # variables as the list of variable names mode = 'CATEGORY' category_id = arg arg = arg.upper() (category, variable) = cdef.locate_varname(arg) if variable: # category/variable as output by locate_varname mode = 'VARIABLE' if not mode: print("%s: no such category or variable" % arg) continue if command in "qQ": # todo check confirmation return elif command == "w": try: # Confirm that various constraints are met before saving file. validate_variables = g_configuration.get( 'validate_variables', {}) validated_variables = cwrite.verify( cdef, cread, validate_variables) validator = g_configuration.get('validator', noop_validator) validator(validated_variables) cwrite.save(site_config) except: print("Save failed due to a configuration exception:") print(traceback.print_exc()) print(("Could not save -- fix write access on %s" % site_config)) break print(("Wrote %s" % site_config)) consolidate(default_config, site_config, consolidated_config) print(("You might want to type 'r' (restart %s), 'R' (reload %s) or 'q' (quit)" % (service, service))) elif command in "uU": global usual_variables global flavour_xml_section_hash try: for varname in usual_variables: (category, variable) = cdef.locate_varname(varname) if not (category is None and variable is None): prompt_variable(cdef, cread, cwrite, category, variable, False) # set the driver variable according to the already set flavour generic_flavour = cwrite.items('sfa')[0][1] for section in cdef.sections(): if generic_flavour in flavour_xml_section_hash and flavour_xml_section_hash[generic_flavour] == section: for item in cdef.items(section): category = section variable = item[0] prompt_variable(cdef, cread, cwrite, category, variable, False) break except Exception as inst: if (str(inst) != 'BailOut'): raise elif command == "r": restart_service() elif command == "R": reload_service() elif command == "c": print_categories(cread) elif command in "eE": if mode == 'ALL': prompt_variables_all(cdef, cread, cwrite, show_comments) elif mode == 'CATEGORY': prompt_variables_category( cdef, cread, cwrite, category_id, show_comments) elif mode == 'VARIABLE': try: prompt_variable(cdef, cread, cwrite, category, variable, show_comments, False) except Exception as inst: if str(inst) != 'BailOut': raise elif command in "vVsSlL": show_value = (command in "sSlL") (c1, c2, c3) = (cdef, cread, cwrite) if command in "lL": (c1, c2, c3) = (cwrite, cwrite, cwrite) if mode == 'ALL': show_variables_all(c1, c2, c3, show_value, show_comments) elif mode == 'CATEGORY': show_variables_category( c1, c2, c3, category_id, show_value, show_comments) elif mode == 'VARIABLE': show_variable(c1, c2, c3, category, variable, show_value, show_comments) else: print(("Unknown command >%s< -- use h for help" % answer)) #################### # creates directory for file if not yet existing def check_dir(config_file): dirname = os.path.dirname(config_file) if (not os.path.exists(dirname)): try: os.makedirs(dirname, 0o755) except OSError as e: print("Cannot create dir %s due to %s - exiting" % (dirname, e)) sys.exit(1) if (not os.path.exists(dirname)): print("Cannot create dir %s - exiting" % dirname) sys.exit(1) else: print("Created directory %s" % dirname) #################### def optParserSetup(configuration): parser = OptionParser(usage=usage(), version="%prog " + version_tag) parser.set_defaults(config_dir=configuration['config_dir'], service=configuration['service'], usual_variables=configuration['usual_variables']) parser.add_option("", "--configdir", dest="config_dir", help="specify configuration directory") parser.add_option("", "--service", dest="service", help="specify /etc/init.d style service name") parser.add_option("", "--usual_variable", dest="usual_variables", action="append", help="add a usual variable") return parser def main(command, argv, configuration): global g_configuration g_configuration = configuration parser = optParserSetup(configuration) (config, args) = parser.parse_args() if len(args) > 3: parser.error("too many arguments") configuration['service'] = config.service configuration['usual_variables'] = config.usual_variables configuration['config_dir'] = config.config_dir # add in new usual_variables defined on the command line for usual_variable in config.usual_variables: if usual_variable not in configuration['usual_variables']: configuration['usual_variables'].append(usual_variable) # intialize configuration init_configuration() default_config, site_config, consolidated_config = \ def_default_config, def_site_config, def_consolidated_config if len(args) >= 1: default_config = args[0] if len(args) >= 2: site_config = args[1] if len(args) == 3: consolidated_config = args[2] for c in (default_config, site_config, consolidated_config): check_dir(c) try: # the default settings only - read only cdef = Config(default_config) # in effect : default settings + local settings - read only cread = Config(default_config) except: print(traceback.print_exc()) print(("default config files %s not found, is myplc installed ?" % default_config)) return 1 # local settings only, will be modified & saved config_filename = "%s/sfa_config" % config.config_dir cwrite = Config(config_filename) try: cread.load(site_config) cwrite.load(default_config) cwrite.load(site_config) except: cwrite = Config() mainloop(cdef, cread, cwrite, default_config, site_config, consolidated_config) return 0 if __name__ == '__main__': command = sys.argv[0] argv = sys.argv[1:] main(command, argv, configuration)