#!/usr/bin/env python3 # # Merge PlanetLab Central (PLC) configuration files into a variety of # output formats. These files represent the global configuration for a # PLC installation. # # Mark Huang # Copyright (C) 2006 The Trustees of Princeton University # import os import re import sys import textwrap import time import traceback import xml.dom.minidom from xml.parsers.expat import ExpatError from io import StringIO from optparse import OptionParser class ConfigurationException(Exception): pass class PLCConfiguration: """ Configuration file store. Optionally instantiate with a file path or object: plc = PLCConfiguration() plc = PLCConfiguration(fileobj) plc = PLCConfiguration("/etc/planetlab/plc_config.xml") You may load() additional files later, which will be merged into the current configuration: plc.load("/etc/planetlab/local.xml") You may also save() the configuration. If a file path or object is not specified, the configuration will be written to the file path or object that was first loaded. plc.save() plc.save("/etc/planetlab/plc_config.xml") """ def __init__(self, file=None): impl = xml.dom.minidom.getDOMImplementation() self._dom = impl.createDocument(None, "configuration", None) self._variables = {} self._packages = {} self._files = [] if file is not None: self.load(file) def _get_text(self, node): """ Get the text of a text node. """ if node.firstChild and \ node.firstChild.nodeType == node.TEXT_NODE: if node.firstChild.data is None: # Interpret simple presence of node as "", not NULL return "" else: return node.firstChild.data return None def _get_text_of_child(self, parent, name): """ Get the text of a (direct) child text node. """ for node in parent.childNodes: if node.nodeType == node.ELEMENT_NODE and \ node.tagName == name: return self._get_text(node) return None def _set_text(self, node, data): """ Set the text of a text node. """ if node.firstChild and \ node.firstChild.nodeType == node.TEXT_NODE: if data is None: node.removeChild(node.firstChild) else: node.firstChild.data = data elif data is not None: text = TrimText() text.data = data node.appendChild(text) def _set_text_of_child(self, parent, name, data): """ Set the text of a (direct) child text node. """ for node in parent.childNodes: if node.nodeType == node.ELEMENT_NODE and \ node.tagName == name: self._set_text(node, data) return child = TrimTextElement(name) self._set_text(child, data) parent.appendChild(child) def _category_element_to_dict(self, category_element): """ Turn a element into a dictionary of its attributes and child text nodes. """ category = {} category['id'] = category_element.getAttribute('id').lower() for node in category_element.childNodes: if node.nodeType == node.ELEMENT_NODE and \ node.tagName in ['name', 'description']: category[node.tagName] = self._get_text_of_child( category_element, node.tagName) category['element'] = category_element return category def _variable_element_to_dict(self, variable_element): """ Turn a element into a dictionary of its attributes and child text nodes. """ variable = {} variable['id'] = variable_element.getAttribute('id').lower() if variable_element.hasAttribute('type'): variable['type'] = variable_element.getAttribute('type') for node in variable_element.childNodes: if node.nodeType == node.ELEMENT_NODE and \ node.tagName in ['name', 'value', 'description']: variable[node.tagName] = self._get_text_of_child( variable_element, node.tagName) variable['element'] = variable_element return variable def _group_element_to_dict(self, group_element): """ Turn a element into a dictionary of its attributes and child text nodes. """ group = {} for node in group_element.childNodes: if node.nodeType == node.ELEMENT_NODE and \ node.tagName in ['id', 'name', 'default', 'description', 'uservisible']: group[node.tagName] = self._get_text_of_child( group_element, node.tagName) group['element'] = group_element return group def _packagereq_element_to_dict(self, packagereq_element): """ Turns a element into a dictionary of its attributes and child text nodes. """ package = {} if packagereq_element.hasAttribute('type'): package['type'] = packagereq_element.getAttribute('type') package['name'] = self._get_text(packagereq_element) package['element'] = packagereq_element return package def load(self, file="/etc/planetlab/plc_config.xml"): """ Merge file into configuration store. """ try: dom = xml.dom.minidom.parse(file) except ExpatError as e: raise ConfigurationException(e) if isinstance(file, str): self._files.append(os.path.abspath(file)) # Parse section for variables_element in dom.getElementsByTagName('variables'): for category_element in variables_element.getElementsByTagName('category'): category = self._category_element_to_dict(category_element) self.set(category, None) for variablelist_element in category_element.getElementsByTagName('variablelist'): for variable_element in variablelist_element.getElementsByTagName('variable'): variable = self._variable_element_to_dict( variable_element) self.set(category, variable) # Parse section for comps_element in dom.getElementsByTagName('comps'): for group_element in comps_element.getElementsByTagName('group'): group = self._group_element_to_dict(group_element) self.add_package(group, None) for packagereq_element in group_element.getElementsByTagName('packagereq'): package = self._packagereq_element_to_dict( packagereq_element) self.add_package(group, package) def save(self, file=None): """ Write configuration store to file. """ if file is None: if self._files: file = self._files[0] else: file = "/etc/planetlab/plc_config.xml" if isinstance(file, str): fileobj = open(file, 'w') else: fileobj = file fileobj.seek(0) fileobj.write(self.output_xml()) fileobj.truncate() fileobj.close() def verify(self, default, read, verify_variables={}): """ Confirm that the existing configuration is consistent according to the checks below. It looks for filled-in values in the order of, local object (self), followed by cread (read values), and finally default values. Arguments: default configuration site configuration list of category/variable tuples to validate in these configurations Returns: dict of values for the category/variables passed in If an exception is found, ConfigurationException is raised. """ validated_variables = {} for category_id, variable_id in verify_variables.items(): category_id = category_id.lower() variable_id = variable_id.lower() variable_value = None sources = (self, read, default) for source in sources: (category_value, variable_value) = source.get( category_id, variable_id) if variable_value != None: entry = validated_variables.get(category_id, []) entry.append(variable_value['value']) validated_variables["%s_%s" % ( category_id.upper(), variable_id.upper())] = entry break if variable_value == None: raise ConfigurationException("Cannot find %s_%s)" % (category_id.upper(), variable_id.upper())) return validated_variables def get(self, category_id, variable_id): """ Get the specified variable in the specified category. Arguments: category_id = unique category identifier (e.g., 'plc_www') variable_id = unique variable identifier (e.g., 'port') Returns: variable = { 'id': "variable_identifier", 'type': "variable_type", 'value': "variable_value", 'name': "Variable name", 'description': "Variable description" } """ if category_id.lower() in self._variables: (category, variables) = self._variables[category_id] if variable_id.lower() in variables: variable = variables[variable_id] else: variable = None else: category = None variable = None return (category, variable) def delete(self, category_id, variable_id): """ Delete the specified variable from the specified category. If variable_id is None, deletes all variables from the specified category as well as the category itself. Arguments: category_id = unique category identifier (e.g., 'plc_www') variable_id = unique variable identifier (e.g., 'port') """ if category_id.lower() in self._variables: (category, variables) = self._variables[category_id] if variable_id is None: category['element'].parentNode.removeChild(category['element']) del self._variables[category_id] elif variable_id.lower() in variables: variable = variables[variable_id] variable['element'].parentNode.removeChild(variable['element']) del variables[variable_id] def set(self, category, variable): """ Add and/or update the specified variable. The 'id' fields are mandatory. If a field is not specified and the category and/or variable already exists, the field will not be updated. If 'variable' is None, only adds and/or updates the specified category. Arguments: category = { 'id': "category_identifier", 'name': "Category name", 'description': "Category description" } variable = { 'id': "variable_identifier", 'type': "variable_type", 'value': "variable_value", 'name': "Variable name", 'description': "Variable description" } """ if ('id' not in category or not isinstance(category['id'], str)): return category_id = category['id'].lower() if category_id in self._variables: # Existing category (old_category, variables) = self._variables[category_id] # Merge category attributes for tag in ['name', 'description']: if tag in category: old_category[tag] = category[tag] self._set_text_of_child( old_category['element'], tag, category[tag]) category_element = old_category['element'] else: # Merge into DOM category_element = self._dom.createElement('category') category_element.setAttribute('id', category_id) for tag in ['name', 'description']: if tag in category: self._set_text_of_child( category_element, tag, category[tag]) if self._dom.documentElement.getElementsByTagName('variables'): variables_element = self._dom.documentElement.getElementsByTagName('variables')[ 0] else: variables_element = self._dom.createElement('variables') self._dom.documentElement.appendChild(variables_element) variables_element.appendChild(category_element) # Cache it category['element'] = category_element variables = {} self._variables[category_id] = (category, variables) if (variable is None or 'id' not in variable or not isinstance(variable['id'], str)): return variable_id = variable['id'].lower() if variable_id in variables: # Existing variable old_variable = variables[variable_id] # Merge variable attributes for attribute in ['type']: if attribute in variable: old_variable[attribute] = variable[attribute] old_variable['element'].setAttribute( attribute, variable[attribute]) for tag in ['name', 'value', 'description']: if tag in variable: old_variable[tag] = variable[tag] self._set_text_of_child( old_variable['element'], tag, variable[tag]) else: # Merge into DOM variable_element = self._dom.createElement('variable') variable_element.setAttribute('id', variable_id) for attribute in ['type']: if attribute in variable: variable_element.setAttribute( attribute, variable[attribute]) for tag in ['name', 'value', 'description']: if tag in variable: self._set_text_of_child( variable_element, tag, variable[tag]) if category_element.getElementsByTagName('variablelist'): variablelist_element = category_element.getElementsByTagName('variablelist')[ 0] else: variablelist_element = self._dom.createElement('variablelist') category_element.appendChild(variablelist_element) variablelist_element.appendChild(variable_element) # Cache it variable['element'] = variable_element variables[variable_id] = variable def locate_varname(self, varname): """ Locates category and variable from a variable's (shell) name Returns: (variable, category) when found (None, None) otherwise """ for (category_id, (category, variables)) in self._variables.items(): for variable in list(variables.values()): (id, name, value, comments) = self._sanitize_variable( category_id, variable) if (id == varname): return (category, variable) return (None, None) def get_package(self, group_id, package_name): """ Get the specified package in the specified package group. Arguments: group_id - unique group id (e.g., 'plc') package_name - unique package name (e.g., 'postgresql') Returns: package = { 'name': "package_name", 'type': "mandatory|optional" } """ if group_id.lower() in self._packages: (group, packages) = self._packages[group_id] if package_name in packages: package = packages[package_name] else: package = None else: group = None package = None return (group, package) def delete_package(self, group_id, package_name): """ Deletes the specified variable from the specified category. If variable_id is None, deletes all variables from the specified category as well as the category itself. Arguments: group_id - unique group id (e.g., 'plc') package_name - unique package name (e.g., 'postgresql') """ if group_id in self._packages: (group, packages) = self._packages[group_id] if package_name is None: group['element'].parentNode.removeChild(group['element']) del self._packages[group_id] elif package_name.lower() in packages: package = packages[package_name] package['element'].parentNode.removeChild(package['element']) del packages[package_name] def add_package(self, group, package): """ Add and/or update the specified package. The 'id' and 'name' fields are mandatory. If a field is not specified and the package or group already exists, the field will not be updated. If package is None, only adds/or updates the specified group. Arguments: group = { 'id': "group_identifier", 'name': "Group name", 'default': "true|false", 'description': "Group description", 'uservisible': "true|false" } package = { 'name': "package_name", 'type': "mandatory|optional" } """ if 'id' not in group: return group_id = group['id'] if group_id in self._packages: # Existing group (old_group, packages) = self._packages[group_id] # Merge group attributes for tag in ['id', 'name', 'default', 'description', 'uservisible']: if tag in group: old_group[tag] = group[tag] self._set_text_of_child( old_group['element'], tag, group[tag]) group_element = old_group['element'] else: # Merge into DOM group_element = self._dom.createElement('group') for tag in ['id', 'name', 'default', 'description', 'uservisible']: if tag in group: self._set_text_of_child(group_element, tag, group[tag]) if self._dom.documentElement.getElementsByTagName('comps'): comps_element = self._dom.documentElement.getElementsByTagName('comps')[ 0] else: comps_element = self._dom.createElement('comps') self._dom.documentElement.appendChild(comps_element) comps_element.appendChild(group_element) # Cache it group['element'] = group_element packages = {} self._packages[group_id] = (group, packages) if package is None or 'name' not in package: return package_name = package['name'] if package_name in packages: # Existing package old_package = packages[package_name] # Merge variable attributes for attribute in ['type']: if attribute in package: old_package[attribute] = package[attribute] old_package['element'].setAttribute( attribute, package[attribute]) else: # Merge into DOM packagereq_element = TrimTextElement('packagereq') self._set_text(packagereq_element, package_name) for attribute in ['type']: if attribute in package: packagereq_element.setAttribute( attribute, package[attribute]) if group_element.getElementsByTagName('packagelist'): packagelist_element = group_element.getElementsByTagName('packagelist')[ 0] else: packagelist_element = self._dom.createElement('packagelist') group_element.appendChild(packagelist_element) packagelist_element.appendChild(packagereq_element) # Cache it package['element'] = packagereq_element packages[package_name] = package def variables(self): """ Return all variables. Returns: variables = { 'category_id': (category, variablelist) } category = { 'id': "category_identifier", 'name': "Category name", 'description': "Category description" } variablelist = { 'variable_id': variable } variable = { 'id': "variable_identifier", 'type': "variable_type", 'value': "variable_value", 'name': "Variable name", 'description': "Variable description" } """ return self._variables def packages(self): """ Return all packages. Returns: packages = { 'group_id': (group, packagelist) } group = { 'id': "group_identifier", 'name': "Group name", 'default': "true|false", 'description': "Group description", 'uservisible': "true|false" } packagelist = { 'package_name': package } package = { 'name': "package_name", 'type': "mandatory|optional" } """ return self._packages def _sanitize_variable(self, category_id, variable): assert 'id' in variable # Prepend variable name with category label id = category_id + "_" + variable['id'] # And uppercase it id = id.upper() if 'type' in variable: type = variable['type'] else: type = None if 'name' in variable: name = variable['name'] else: name = None if 'value' in variable and variable['value'] is not None: value = variable['value'] if type == "int" or type == "double": # bash, Python, and PHP do not require that numbers be quoted pass elif type == "boolean": # bash, Python, and PHP can all agree on 0 and 1 if value == "true": value = "1" else: value = "0" else: # bash, Python, and PHP all support strong single quoting value = "'" + value.replace("'", "\\'") + "'" else: value = None if 'description' in variable and variable['description'] is not None: description = variable['description'] # Collapse consecutive whitespace description = re.sub(r'\s+', ' ', description) # Wrap comments at 70 columns wrapper = textwrap.TextWrapper() comments = wrapper.wrap(description) else: comments = None return (id, name, value, comments) def _header(self): header = """ DO NOT EDIT. This file was automatically generated at %s from: %s """ % (time.asctime(), os.linesep.join(self._files)) # Get rid of the surrounding newlines return header.strip().split(os.linesep) def output_shell(self, show_comments=True): """ Return variables as a shell script. """ buf = StringIO() buf.writelines(["# " + line + os.linesep for line in self._header()]) for (category_id, (category, variables)) in self._variables.items(): for variable in list(variables.values()): (id, name, value, comments) = self._sanitize_variable( category_id, variable) if show_comments: buf.write(os.linesep) if name is not None: buf.write("# " + name + os.linesep) if comments is not None: buf.writelines( ["# " + line + os.linesep for line in comments]) # bash does not have the concept of NULL if value is not None: buf.write(id + "=" + value + os.linesep) return buf.getvalue() def output_php(self): """ Return variables as a PHP script. """ buf = StringIO() buf.write("" + os.linesep) return buf.getvalue() def output_xml(self): """ Return variables in original XML format. """ return self._dom.toxml() def output_variables(self): """ Return list of all variable names. """ buf = StringIO() for (category_id, (category, variables)) in self._variables.items(): for variable in list(variables.values()): (id, name, value, comments) = self._sanitize_variable( category_id, variable) buf.write(id + os.linesep) return buf.getvalue() def output_packages(self): """ Return list of all packages. """ buf = StringIO() for (group, packages) in list(self._packages.values()): buf.write(os.linesep.join(list(packages.keys()))) if buf.tell(): buf.write(os.linesep) return buf.getvalue() def output_groups(self): """ Return list of all package group names. """ buf = StringIO() for (group, packages) in list(self._packages.values()): buf.write(group['name'] + os.linesep) return buf.getvalue() def output_comps(self): """ Return section of configuration. """ if (self._dom is None or not self._dom.getElementsByTagName("comps")): return comps = self._dom.getElementsByTagName("comps")[0] impl = xml.dom.minidom.getDOMImplementation() doc = impl.createDocument(None, "comps", None) buf = StringIO() # Pop it off the DOM temporarily parent = comps.parentNode parent.removeChild(comps) doc.replaceChild(comps, doc.documentElement) doc.writexml(buf) # Put it back parent.appendChild(comps) return buf.getvalue() def validate_type(self, variable_type, value): # ideally we should use the "validate_*" methods in PLCAPI or # even declare some checks along with the default # configuration (using RELAX NG?) but this shall work for now. def ip_validator(val): import socket try: socket.inet_aton(val) return True except: return False def email_validator(val): return re.match('\A[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9._\-]+\.[a-zA-Z]+\Z', val) def boolean_validator(val): return val in ['true', 'false'] validators = { 'email': email_validator, 'ip': ip_validator, 'boolean': boolean_validator, } # validate it if not a know type. validator = validators.get(variable_type, lambda x: True) return validator(value) # xml.dom.minidom.Text.writexml adds surrounding whitespace to textual # data when pretty-printing. Override this behavior. class TrimText(xml.dom.minidom.Text): def writexml(self, writer, indent="", addindent="", newl=""): xml.dom.minidom.Text.writexml(self, writer, "", "", "") class TrimTextElement(xml.dom.minidom.Element): def writexml(self, writer, indent="", addindent="", newl=""): writer.write(indent) xml.dom.minidom.Element.writexml(self, writer, "", "", "") writer.write(newl) #################### # 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.xml" % config_dir def_consolidated_config = "%s/%s_config.xml" % (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): (category, variable) = config.get(category_id, variable_id) return variable['value'] def get_type(config, category_id, variable_id): (category, variable) = config.get(category_id, variable_id) return variable['type'] 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): (category, variable) = config.get(category_id, variable_id) return (category_id+"_"+variable['id']).upper() # 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 (category_id, (category, variables)) in config.variables().items(): result += [category_id] 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 (category_id, (category, variables)) in config.variables().items(): if (cid == category_id): for variable in list(variables.values()): result += ["%s_%s" % (cid, variable['id'])] 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 = PLCConfiguration(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(): global service os.system("set -x ; systemctl reload %s" % service) #################### def restart_service(): global service print(("==================== Stopping %s" % service)) os.system("systemctl stop %s" % service) print(("==================== Starting %s" % service)) os.system("systemctl start %s" % service) #################### def prompt_variable(cdef, cread, cwrite, category, variable, show_comments, support_next=False): assert 'id' in category assert 'id' in variable category_id = category['id'] variable_id = variable['id'] 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): variable['value'] = answer cwrite.set(category, variable) 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 ConfigurationException as e: print("Save failed due to a configuration exception: %s" % e) break except: 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 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) 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()) 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 = PLCConfiguration(default_config) # in effect : default settings + local settings - read only cread = PLCConfiguration(default_config) except ConfigurationException as e: print(("Error %s in default config file %s" % (e, default_config))) return 1 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 cwrite = PLCConfiguration() try: cread.load(site_config) cwrite.load(site_config) except: cwrite = PLCConfiguration() mainloop(cdef, cread, cwrite, default_config, site_config, consolidated_config) return 0 if __name__ == '__main__': import sys if len(sys.argv) > 1 and sys.argv[1] in ['build', 'install', 'uninstall']: from distutils.core import setup setup(py_modules=["plc_config"])