3 # Merge PlanetLab Central (PLC) configuration files into a variety of
4 # output formats. These files represent the global configuration for a
7 # Mark Huang <mlhuang@cs.princeton.edu>
8 # Copyright (C) 2006 The Trustees of Princeton University
17 import xml.dom.minidom
18 from xml.parsers.expat import ExpatError
19 from io import StringIO
20 from optparse import OptionParser
23 class ConfigurationException(Exception):
27 class PLCConfiguration:
29 Configuration file store. Optionally instantiate with a file path
32 plc = PLCConfiguration()
33 plc = PLCConfiguration(fileobj)
34 plc = PLCConfiguration("/etc/planetlab/plc_config.xml")
36 You may load() additional files later, which will be merged into
37 the current configuration:
39 plc.load("/etc/planetlab/local.xml")
41 You may also save() the configuration. If a file path or object is
42 not specified, the configuration will be written to the file path
43 or object that was first loaded.
46 plc.save("/etc/planetlab/plc_config.xml")
49 def __init__(self, file=None):
50 impl = xml.dom.minidom.getDOMImplementation()
51 self._dom = impl.createDocument(None, "configuration", None)
59 def _get_text(self, node):
61 Get the text of a text node.
64 if node.firstChild and \
65 node.firstChild.nodeType == node.TEXT_NODE:
66 if node.firstChild.data is None:
67 # Interpret simple presence of node as "", not NULL
70 return node.firstChild.data
74 def _get_text_of_child(self, parent, name):
76 Get the text of a (direct) child text node.
79 for node in parent.childNodes:
80 if node.nodeType == node.ELEMENT_NODE and \
82 return self._get_text(node)
86 def _set_text(self, node, data):
88 Set the text of a text node.
91 if node.firstChild and \
92 node.firstChild.nodeType == node.TEXT_NODE:
94 node.removeChild(node.firstChild)
96 node.firstChild.data = data
97 elif data is not None:
100 node.appendChild(text)
102 def _set_text_of_child(self, parent, name, data):
104 Set the text of a (direct) child text node.
107 for node in parent.childNodes:
108 if node.nodeType == node.ELEMENT_NODE and \
109 node.tagName == name:
110 self._set_text(node, data)
113 child = TrimTextElement(name)
114 self._set_text(child, data)
115 parent.appendChild(child)
117 def _category_element_to_dict(self, category_element):
119 Turn a <category> element into a dictionary of its attributes
120 and child text nodes.
124 category['id'] = category_element.getAttribute('id').lower()
125 for node in category_element.childNodes:
126 if node.nodeType == node.ELEMENT_NODE and \
127 node.tagName in ['name', 'description']:
128 category[node.tagName] = self._get_text_of_child(
129 category_element, node.tagName)
130 category['element'] = category_element
134 def _variable_element_to_dict(self, variable_element):
136 Turn a <variable> element into a dictionary of its attributes
137 and child text nodes.
141 variable['id'] = variable_element.getAttribute('id').lower()
142 if variable_element.hasAttribute('type'):
143 variable['type'] = variable_element.getAttribute('type')
144 for node in variable_element.childNodes:
145 if node.nodeType == node.ELEMENT_NODE and \
146 node.tagName in ['name', 'value', 'description']:
147 variable[node.tagName] = self._get_text_of_child(
148 variable_element, node.tagName)
149 variable['element'] = variable_element
153 def _group_element_to_dict(self, group_element):
155 Turn a <group> element into a dictionary of its attributes
156 and child text nodes.
160 for node in group_element.childNodes:
161 if node.nodeType == node.ELEMENT_NODE and \
162 node.tagName in ['id', 'name', 'default', 'description', 'uservisible']:
163 group[node.tagName] = self._get_text_of_child(
164 group_element, node.tagName)
165 group['element'] = group_element
169 def _packagereq_element_to_dict(self, packagereq_element):
171 Turns a <packagereq> element into a dictionary of its attributes
172 and child text nodes.
176 if packagereq_element.hasAttribute('type'):
177 package['type'] = packagereq_element.getAttribute('type')
178 package['name'] = self._get_text(packagereq_element)
179 package['element'] = packagereq_element
183 def load(self, file="/etc/planetlab/plc_config.xml"):
185 Merge file into configuration store.
189 dom = xml.dom.minidom.parse(file)
190 except ExpatError as e:
191 raise ConfigurationException(e)
193 if isinstance(file, str):
194 self._files.append(os.path.abspath(file))
196 # Parse <variables> section
197 for variables_element in dom.getElementsByTagName('variables'):
198 for category_element in variables_element.getElementsByTagName('category'):
199 category = self._category_element_to_dict(category_element)
200 self.set(category, None)
202 for variablelist_element in category_element.getElementsByTagName('variablelist'):
203 for variable_element in variablelist_element.getElementsByTagName('variable'):
204 variable = self._variable_element_to_dict(
206 self.set(category, variable)
208 # Parse <comps> section
209 for comps_element in dom.getElementsByTagName('comps'):
210 for group_element in comps_element.getElementsByTagName('group'):
211 group = self._group_element_to_dict(group_element)
212 self.add_package(group, None)
214 for packagereq_element in group_element.getElementsByTagName('packagereq'):
215 package = self._packagereq_element_to_dict(
217 self.add_package(group, package)
219 def save(self, file=None):
221 Write configuration store to file.
226 file = self._files[0]
228 file = "/etc/planetlab/plc_config.xml"
230 if isinstance(file, str):
231 fileobj = open(file, 'w')
236 fileobj.write(self.output_xml())
241 def verify(self, default, read, verify_variables={}):
242 """ Confirm that the existing configuration is consistent
243 according to the checks below.
245 It looks for filled-in values in the order of, local object (self),
246 followed by cread (read values), and finally default values.
250 default configuration
252 list of category/variable tuples to validate in these configurations
256 dict of values for the category/variables passed in
257 If an exception is found, ConfigurationException is raised.
261 validated_variables = {}
262 for category_id, variable_id in verify_variables.items():
263 category_id = category_id.lower()
264 variable_id = variable_id.lower()
265 variable_value = None
266 sources = (self, read, default)
267 for source in sources:
268 (category_value, variable_value) = source.get(
269 category_id, variable_id)
270 if variable_value != None:
271 entry = validated_variables.get(category_id, [])
272 entry.append(variable_value['value'])
273 validated_variables["%s_%s" % (
274 category_id.upper(), variable_id.upper())] = entry
276 if variable_value == None:
277 raise ConfigurationException("Cannot find %s_%s)" %
278 (category_id.upper(),
279 variable_id.upper()))
280 return validated_variables
282 def get(self, category_id, variable_id):
284 Get the specified variable in the specified category.
288 category_id = unique category identifier (e.g., 'plc_www')
289 variable_id = unique variable identifier (e.g., 'port')
293 variable = { 'id': "variable_identifier",
294 'type': "variable_type",
295 'value': "variable_value",
296 'name': "Variable name",
297 'description': "Variable description" }
300 if category_id.lower() in self._variables:
301 (category, variables) = self._variables[category_id]
302 if variable_id.lower() in variables:
303 variable = variables[variable_id]
310 return (category, variable)
312 def delete(self, category_id, variable_id):
314 Delete the specified variable from the specified category. If
315 variable_id is None, deletes all variables from the specified
316 category as well as the category itself.
320 category_id = unique category identifier (e.g., 'plc_www')
321 variable_id = unique variable identifier (e.g., 'port')
324 if category_id.lower() in self._variables:
325 (category, variables) = self._variables[category_id]
326 if variable_id is None:
327 category['element'].parentNode.removeChild(category['element'])
328 del self._variables[category_id]
329 elif variable_id.lower() in variables:
330 variable = variables[variable_id]
331 variable['element'].parentNode.removeChild(variable['element'])
332 del variables[variable_id]
334 def set(self, category, variable):
336 Add and/or update the specified variable. The 'id' fields are
337 mandatory. If a field is not specified and the category and/or
338 variable already exists, the field will not be updated. If
339 'variable' is None, only adds and/or updates the specified
344 category = { 'id': "category_identifier",
345 'name': "Category name",
346 'description': "Category description" }
348 variable = { 'id': "variable_identifier",
349 'type': "variable_type",
350 'value': "variable_value",
351 'name': "Variable name",
352 'description': "Variable description" }
355 if ('id' not in category
356 or not isinstance(category['id'], str)):
359 category_id = category['id'].lower()
361 if category_id in self._variables:
363 (old_category, variables) = self._variables[category_id]
365 # Merge category attributes
366 for tag in ['name', 'description']:
368 old_category[tag] = category[tag]
369 self._set_text_of_child(
370 old_category['element'], tag, category[tag])
372 category_element = old_category['element']
375 category_element = self._dom.createElement('category')
376 category_element.setAttribute('id', category_id)
377 for tag in ['name', 'description']:
379 self._set_text_of_child(
380 category_element, tag, category[tag])
382 if self._dom.documentElement.getElementsByTagName('variables'):
383 variables_element = self._dom.documentElement.getElementsByTagName('variables')[
386 variables_element = self._dom.createElement('variables')
387 self._dom.documentElement.appendChild(variables_element)
388 variables_element.appendChild(category_element)
391 category['element'] = category_element
393 self._variables[category_id] = (category, variables)
395 if (variable is None or 'id' not in variable
396 or not isinstance(variable['id'], str)):
399 variable_id = variable['id'].lower()
401 if variable_id in variables:
403 old_variable = variables[variable_id]
405 # Merge variable attributes
406 for attribute in ['type']:
407 if attribute in variable:
408 old_variable[attribute] = variable[attribute]
409 old_variable['element'].setAttribute(
410 attribute, variable[attribute])
411 for tag in ['name', 'value', 'description']:
413 old_variable[tag] = variable[tag]
414 self._set_text_of_child(
415 old_variable['element'], tag, variable[tag])
418 variable_element = self._dom.createElement('variable')
419 variable_element.setAttribute('id', variable_id)
420 for attribute in ['type']:
421 if attribute in variable:
422 variable_element.setAttribute(
423 attribute, variable[attribute])
424 for tag in ['name', 'value', 'description']:
426 self._set_text_of_child(
427 variable_element, tag, variable[tag])
429 if category_element.getElementsByTagName('variablelist'):
430 variablelist_element = category_element.getElementsByTagName('variablelist')[
433 variablelist_element = self._dom.createElement('variablelist')
434 category_element.appendChild(variablelist_element)
435 variablelist_element.appendChild(variable_element)
438 variable['element'] = variable_element
439 variables[variable_id] = variable
441 def locate_varname(self, varname):
443 Locates category and variable from a variable's (shell) name
446 (variable, category) when found
447 (None, None) otherwise
450 for (category_id, (category, variables)) in self._variables.items():
451 for variable in list(variables.values()):
452 (id, name, value, comments) = self._sanitize_variable(
453 category_id, variable)
455 return (category, variable)
458 def get_package(self, group_id, package_name):
460 Get the specified package in the specified package group.
464 group_id - unique group id (e.g., 'plc')
465 package_name - unique package name (e.g., 'postgresql')
469 package = { 'name': "package_name",
470 'type': "mandatory|optional" }
473 if group_id.lower() in self._packages:
474 (group, packages) = self._packages[group_id]
475 if package_name in packages:
476 package = packages[package_name]
483 return (group, package)
485 def delete_package(self, group_id, package_name):
487 Deletes the specified variable from the specified category. If
488 variable_id is None, deletes all variables from the specified
489 category as well as the category itself.
493 group_id - unique group id (e.g., 'plc')
494 package_name - unique package name (e.g., 'postgresql')
497 if group_id in self._packages:
498 (group, packages) = self._packages[group_id]
499 if package_name is None:
500 group['element'].parentNode.removeChild(group['element'])
501 del self._packages[group_id]
502 elif package_name.lower() in packages:
503 package = packages[package_name]
504 package['element'].parentNode.removeChild(package['element'])
505 del packages[package_name]
507 def add_package(self, group, package):
509 Add and/or update the specified package. The 'id' and 'name'
510 fields are mandatory. If a field is not specified and the
511 package or group already exists, the field will not be
512 updated. If package is None, only adds/or updates the
517 group = { 'id': "group_identifier",
518 'name': "Group name",
519 'default': "true|false",
520 'description': "Group description",
521 'uservisible': "true|false" }
523 package = { 'name': "package_name",
524 'type': "mandatory|optional" }
527 if 'id' not in group:
530 group_id = group['id']
532 if group_id in self._packages:
534 (old_group, packages) = self._packages[group_id]
536 # Merge group attributes
537 for tag in ['id', 'name', 'default', 'description', 'uservisible']:
539 old_group[tag] = group[tag]
540 self._set_text_of_child(
541 old_group['element'], tag, group[tag])
543 group_element = old_group['element']
546 group_element = self._dom.createElement('group')
547 for tag in ['id', 'name', 'default', 'description', 'uservisible']:
549 self._set_text_of_child(group_element, tag, group[tag])
551 if self._dom.documentElement.getElementsByTagName('comps'):
552 comps_element = self._dom.documentElement.getElementsByTagName('comps')[
555 comps_element = self._dom.createElement('comps')
556 self._dom.documentElement.appendChild(comps_element)
557 comps_element.appendChild(group_element)
560 group['element'] = group_element
562 self._packages[group_id] = (group, packages)
564 if package is None or 'name' not in package:
567 package_name = package['name']
568 if package_name in packages:
570 old_package = packages[package_name]
572 # Merge variable attributes
573 for attribute in ['type']:
574 if attribute in package:
575 old_package[attribute] = package[attribute]
576 old_package['element'].setAttribute(
577 attribute, package[attribute])
580 packagereq_element = TrimTextElement('packagereq')
581 self._set_text(packagereq_element, package_name)
582 for attribute in ['type']:
583 if attribute in package:
584 packagereq_element.setAttribute(
585 attribute, package[attribute])
587 if group_element.getElementsByTagName('packagelist'):
588 packagelist_element = group_element.getElementsByTagName('packagelist')[
591 packagelist_element = self._dom.createElement('packagelist')
592 group_element.appendChild(packagelist_element)
593 packagelist_element.appendChild(packagereq_element)
596 package['element'] = packagereq_element
597 packages[package_name] = package
601 Return all variables.
605 variables = { 'category_id': (category, variablelist) }
607 category = { 'id': "category_identifier",
608 'name': "Category name",
609 'description': "Category description" }
611 variablelist = { 'variable_id': variable }
613 variable = { 'id': "variable_identifier",
614 'type': "variable_type",
615 'value': "variable_value",
616 'name': "Variable name",
617 'description': "Variable description" }
620 return self._variables
628 packages = { 'group_id': (group, packagelist) }
630 group = { 'id': "group_identifier",
631 'name': "Group name",
632 'default': "true|false",
633 'description': "Group description",
634 'uservisible': "true|false" }
636 packagelist = { 'package_name': package }
638 package = { 'name': "package_name",
639 'type': "mandatory|optional" }
642 return self._packages
644 def _sanitize_variable(self, category_id, variable):
645 assert 'id' in variable
646 # Prepend variable name with category label
647 id = category_id + "_" + variable['id']
651 if 'type' in variable:
652 type = variable['type']
656 if 'name' in variable:
657 name = variable['name']
661 if 'value' in variable and variable['value'] is not None:
662 value = variable['value']
663 if type == "int" or type == "double":
664 # bash, Python, and PHP do not require that numbers be quoted
666 elif type == "boolean":
667 # bash, Python, and PHP can all agree on 0 and 1
673 # bash, Python, and PHP all support strong single quoting
674 value = "'" + value.replace("'", "\\'") + "'"
678 if 'description' in variable and variable['description'] is not None:
679 description = variable['description']
680 # Collapse consecutive whitespace
681 description = re.sub(r'\s+', ' ', description)
682 # Wrap comments at 70 columns
683 wrapper = textwrap.TextWrapper()
684 comments = wrapper.wrap(description)
688 return (id, name, value, comments)
692 DO NOT EDIT. This file was automatically generated at
696 """ % (time.asctime(), os.linesep.join(self._files))
698 # Get rid of the surrounding newlines
699 return header.strip().split(os.linesep)
701 def output_shell(self, show_comments=True):
703 Return variables as a shell script.
707 buf.writelines(["# " + line + os.linesep for line in self._header()])
709 for (category_id, (category, variables)) in self._variables.items():
710 for variable in list(variables.values()):
711 (id, name, value, comments) = self._sanitize_variable(
712 category_id, variable)
714 buf.write(os.linesep)
716 buf.write("# " + name + os.linesep)
717 if comments is not None:
719 ["# " + line + os.linesep for line in comments])
720 # bash does not have the concept of NULL
721 if value is not None:
722 buf.write(id + "=" + value + os.linesep)
724 return buf.getvalue()
726 def output_php(self):
728 Return variables as a PHP script.
732 buf.write("<?php" + os.linesep)
733 buf.writelines(["// " + line + os.linesep for line in self._header()])
735 for (category_id, (category, variables)) in self._variables.items():
736 for variable in list(variables.values()):
737 (id, name, value, comments) = self._sanitize_variable(
738 category_id, variable)
739 buf.write(os.linesep)
741 buf.write("// " + name + os.linesep)
742 if comments is not None:
744 ["// " + line + os.linesep for line in comments])
747 buf.write("define('%s', %s);" % (id, value) + os.linesep)
749 buf.write("?>" + os.linesep)
751 return buf.getvalue()
753 def output_xml(self):
755 Return variables in original XML format.
758 return self._dom.toxml()
760 def output_variables(self):
762 Return list of all variable names.
767 for (category_id, (category, variables)) in self._variables.items():
768 for variable in list(variables.values()):
769 (id, name, value, comments) = self._sanitize_variable(
770 category_id, variable)
771 buf.write(id + os.linesep)
773 return buf.getvalue()
775 def output_packages(self):
777 Return list of all packages.
782 for (group, packages) in list(self._packages.values()):
783 buf.write(os.linesep.join(list(packages.keys())))
786 buf.write(os.linesep)
788 return buf.getvalue()
790 def output_groups(self):
792 Return list of all package group names.
797 for (group, packages) in list(self._packages.values()):
798 buf.write(group['name'] + os.linesep)
800 return buf.getvalue()
802 def output_comps(self):
804 Return <comps> section of configuration.
807 if (self._dom is None or not self._dom.getElementsByTagName("comps")):
809 comps = self._dom.getElementsByTagName("comps")[0]
811 impl = xml.dom.minidom.getDOMImplementation()
812 doc = impl.createDocument(None, "comps", None)
816 # Pop it off the DOM temporarily
817 parent = comps.parentNode
818 parent.removeChild(comps)
820 doc.replaceChild(comps, doc.documentElement)
824 parent.appendChild(comps)
826 return buf.getvalue()
828 def validate_type(self, variable_type, value):
830 # ideally we should use the "validate_*" methods in PLCAPI or
831 # even declare some checks along with the default
832 # configuration (using RELAX NG?) but this shall work for now.
833 def ip_validator(val):
836 socket.inet_aton(val)
841 def email_validator(val):
842 return re.match('\A[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9._\-]+\.[a-zA-Z]+\Z', val)
844 def boolean_validator(val):
845 return val in ['true', 'false']
848 'email': email_validator,
850 'boolean': boolean_validator,
853 # validate it if not a know type.
854 validator = validators.get(variable_type, lambda x: True)
855 return validator(value)
858 # xml.dom.minidom.Text.writexml adds surrounding whitespace to textual
859 # data when pretty-printing. Override this behavior.
860 class TrimText(xml.dom.minidom.Text):
861 def writexml(self, writer, indent="", addindent="", newl=""):
862 xml.dom.minidom.Text.writexml(self, writer, "", "", "")
865 class TrimTextElement(xml.dom.minidom.Element):
866 def writexml(self, writer, indent="", addindent="", newl=""):
868 xml.dom.minidom.Element.writexml(self, writer, "", "", "")
875 g_configuration = None
876 usual_variables = None
881 def noop_validator(validated_variables):
885 # historically we could also configure the devel pkg....
886 def init_configuration():
887 global g_configuration
888 global usual_variables, config_dir, service
890 usual_variables = g_configuration["usual_variables"]
891 config_dir = g_configuration["config_dir"]
892 service = g_configuration["service"]
894 global def_default_config, def_site_config, def_consolidated_config
895 def_default_config = "%s/default_config.xml" % config_dir
896 def_site_config = "%s/configs/site.xml" % config_dir
897 def_consolidated_config = "%s/%s_config.xml" % (config_dir, service)
899 global mainloop_usage
900 mainloop_usage = """Available commands:
901 Uppercase versions give variables comments, when available
902 u/U\t\t\tEdit usual variables
904 r\t\t\tRestart %(service)s service
905 R\t\t\tReload %(service)s service (rebuild config files for sh, python....)
906 q\t\t\tQuit (without saving)
909 l/L [<cat>|<var>]\tShow Locally modified variables/values
910 s/S [<cat>|<var>]\tShow variables/values (all, in category, single)
911 e/E [<cat>|<var>]\tEdit variables (all, in category, single)
913 c\t\t\tList categories
914 v/V [<cat>|<var>]\tList Variables (all, in category, single)
916 Typical usage involves: u, [l,] w, r, q
921 command_usage = "%prog [options] [default-xml [site-xml [consolidated-xml]]]"
924 \t default-xml defaults to %s
925 \t site-xml defaults to %s
926 \t consolidated-xml defaults to %s""" % (def_default_config, def_site_config, def_consolidated_config)
931 variable_usage = """Edit Commands :
932 #\tShow variable comments
933 .\tStops prompting, return to mainloop
934 /\tCleans any site-defined value, reverts to default
935 =\tShows default value
936 >\tSkips to next category
943 def get_value(config, category_id, variable_id):
944 (category, variable) = config.get(category_id, variable_id)
945 return variable['value']
948 def get_type(config, category_id, variable_id):
949 (category, variable) = config.get(category_id, variable_id)
950 return variable['type']
953 def get_current_value(cread, cwrite, category_id, variable_id):
954 # the value stored in cwrite, if present, is the one we want
956 result = get_value(cwrite, category_id, variable_id)
958 result = get_value(cread, category_id, variable_id)
961 # refrain from using plc_config's _sanitize
964 def get_varname(config, category_id, variable_id):
965 (category, variable) = config.get(category_id, variable_id)
966 return (category_id+"_"+variable['id']).upper()
968 # could not avoid using _sanitize here..
971 def get_name_comments(config, cid, vid):
973 (category, variable) = config.get(cid, vid)
974 (id, name, value, comments) = config._sanitize_variable(cid, variable)
975 return (name, comments)
980 def print_name_comments(config, cid, vid):
981 (name, comments) = get_name_comments(config, cid, vid)
983 print("### %s" % name)
985 for line in comments:
988 print("!!! No comment associated to %s_%s" % (cid, vid))
993 def list_categories(config):
995 for (category_id, (category, variables)) in config.variables().items():
996 result += [category_id]
1000 def print_categories(config):
1001 print("Known categories")
1002 for cid in list_categories(config):
1003 print("%s" % (cid.upper()))
1005 ####################
1008 def list_category(config, cid):
1010 for (category_id, (category, variables)) in config.variables().items():
1011 if (cid == category_id):
1012 for variable in list(variables.values()):
1013 result += ["%s_%s" % (cid, variable['id'])]
1017 def print_category(config, cid, show_comments=True):
1020 vids = list_category(config, cid)
1021 if (len(vids) == 0):
1022 print("%s : no such category" % CID)
1024 print("Category %s contains" % (CID))
1028 ####################
1031 def consolidate(default_config, site_config, consolidated_config):
1034 conso = PLCConfiguration(default_config)
1035 conso.load(site_config)
1036 conso.save(consolidated_config)
1037 except Exception as inst:
1038 print("Could not consolidate, %s" % (str(inst)))
1040 print(("Merged\n\t%s\nand\t%s\ninto\t%s" % (default_config, site_config,
1041 consolidated_config)))
1044 def reload_service():
1046 os.system("set -x ; systemctl reload %s" % service)
1048 ####################
1051 def restart_service():
1053 print(("==================== Stopping %s" % service))
1054 os.system("systemctl stop %s" % service)
1055 print(("==================== Starting %s" % service))
1056 os.system("systemctl start %s" % service)
1058 ####################
1061 def prompt_variable(cdef, cread, cwrite, category, variable,
1062 show_comments, support_next=False):
1064 assert 'id' in category
1065 assert 'id' in variable
1067 category_id = category['id']
1068 variable_id = variable['id']
1071 default_value = get_value(cdef, category_id, variable_id)
1072 variable_type = get_type(cdef, category_id, variable_id)
1073 current_value = get_current_value(
1074 cread, cwrite, category_id, variable_id)
1075 varname = get_varname(cread, category_id, variable_id)
1078 print_name_comments(cdef, category_id, variable_id)
1079 prompt = "== %s : [%s] " % (varname, current_value)
1081 answer = input(prompt).strip()
1083 raise Exception('BailOut')
1084 except KeyboardInterrupt:
1086 raise Exception('BailOut')
1089 if (answer == "") or (answer == current_value):
1091 elif (answer == "."):
1092 raise Exception('BailOut')
1093 elif (answer == "#"):
1094 print_name_comments(cread, category_id, variable_id)
1095 elif (answer == "?"):
1096 print(variable_usage.strip())
1097 elif (answer == "="):
1098 print(("%s defaults to %s" % (varname, default_value)))
1099 # revert to default : remove from cwrite (i.e. site-config)
1100 elif (answer == "/"):
1101 cwrite.delete(category_id, variable_id)
1102 print(("%s reverted to %s" % (varname, default_value)))
1104 elif (answer == ">"):
1106 raise Exception('NextCategory')
1108 print("No support for next category")
1110 if cdef.validate_type(variable_type, answer):
1111 variable['value'] = answer
1112 cwrite.set(category, variable)
1115 print("Not a valid value")
1118 def prompt_variables_all(cdef, cread, cwrite, show_comments):
1120 for (category_id, (category, variables)) in cread.variables().items():
1121 print(("========== Category = %s" % category_id.upper()))
1122 for variable in list(variables.values()):
1124 newvar = prompt_variable(cdef, cread, cwrite, category, variable,
1125 show_comments, True)
1126 except Exception as inst:
1127 if (str(inst) == 'NextCategory'):
1132 except Exception as inst:
1133 if (str(inst) == 'BailOut'):
1139 def prompt_variables_category(cdef, cread, cwrite, cid, show_comments):
1143 print(("========== Category = %s" % CID))
1144 for vid in list_category(cdef, cid):
1145 (category, variable) = cdef.locate_varname(vid.upper())
1146 newvar = prompt_variable(cdef, cread, cwrite, category, variable,
1147 show_comments, False)
1148 except Exception as inst:
1149 if (str(inst) == 'BailOut'):
1154 ####################
1157 def show_variable(cdef, cread, cwrite,
1158 category, variable, show_value, show_comments):
1159 assert 'id' in category
1160 assert 'id' in variable
1162 category_id = category['id']
1163 variable_id = variable['id']
1165 default_value = get_value(cdef, category_id, variable_id)
1166 current_value = get_current_value(cread, cwrite, category_id, variable_id)
1167 varname = get_varname(cread, category_id, variable_id)
1169 print_name_comments(cdef, category_id, variable_id)
1171 print("%s = %s" % (varname, current_value))
1173 print("%s" % (varname))
1176 def show_variables_all(cdef, cread, cwrite, show_value, show_comments):
1177 for (category_id, (category, variables)) in cread.variables().items():
1178 print(("========== Category = %s" % category_id.upper()))
1179 for variable in list(variables.values()):
1180 show_variable(cdef, cread, cwrite,
1181 category, variable, show_value, show_comments)
1184 def show_variables_category(cdef, cread, cwrite, cid, show_value, show_comments):
1187 print(("========== Category = %s" % CID))
1188 for vid in list_category(cdef, cid):
1189 (category, variable) = cdef.locate_varname(vid.upper())
1190 show_variable(cdef, cread, cwrite, category, variable,
1191 show_value, show_comments)
1194 ####################
1195 re_mainloop_0arg = "^(?P<command>[uUwrRqlLsSeEcvVhH\?])[ \t]*$"
1196 re_mainloop_1arg = "^(?P<command>[sSeEvV])[ \t]+(?P<arg>\w+)$"
1197 matcher_mainloop_0arg = re.compile(re_mainloop_0arg)
1198 matcher_mainloop_1arg = re.compile(re_mainloop_1arg)
1201 def mainloop(cdef, cread, cwrite, default_config, site_config, consolidated_config):
1206 "Enter command (u for usual changes, w to save, ? for help) ").strip()
1209 except KeyboardInterrupt:
1213 if (answer == "") or (answer in "?hH"):
1214 print(mainloop_usage)
1216 groups_parse = matcher_mainloop_0arg.match(answer)
1219 command = groups_parse.group('command')
1222 groups_parse = matcher_mainloop_1arg.match(answer)
1224 command = groups_parse.group('command')
1225 arg = groups_parse.group('arg')
1227 print(("Unknown command >%s< -- use h for help" % answer))
1230 show_comments = command.isupper()
1236 variables = list_category(cdef, arg)
1238 # category_id as the category name
1239 # variables as the list of variable names
1243 (category, variable) = cdef.locate_varname(arg)
1245 # category/variable as output by locate_varname
1248 print("%s: no such category or variable" % arg)
1252 # todo check confirmation
1254 elif command == "w":
1256 # Confirm that various constraints are met before saving file.
1257 validate_variables = g_configuration.get(
1258 'validate_variables', {})
1259 validated_variables = cwrite.verify(
1260 cdef, cread, validate_variables)
1261 validator = g_configuration.get('validator', noop_validator)
1262 validator(validated_variables)
1263 cwrite.save(site_config)
1264 except ConfigurationException as e:
1265 print("Save failed due to a configuration exception: %s" % e)
1268 print(traceback.print_exc())
1269 print(("Could not save -- fix write access on %s" % site_config))
1271 print(("Wrote %s" % site_config))
1272 consolidate(default_config, site_config, consolidated_config)
1273 print(("You might want to type 'r' (restart %s), 'R' (reload %s) or 'q' (quit)" %
1274 (service, service)))
1275 elif command in "uU":
1276 global usual_variables
1278 for varname in usual_variables:
1279 (category, variable) = cdef.locate_varname(varname)
1280 if not (category is None and variable is None):
1281 prompt_variable(cdef, cread, cwrite,
1282 category, variable, False)
1283 except Exception as inst:
1284 if (str(inst) != 'BailOut'):
1286 elif command == "r":
1288 elif command == "R":
1290 elif command == "c":
1291 print_categories(cread)
1292 elif command in "eE":
1294 prompt_variables_all(cdef, cread, cwrite, show_comments)
1295 elif mode == 'CATEGORY':
1296 prompt_variables_category(
1297 cdef, cread, cwrite, category_id, show_comments)
1298 elif mode == 'VARIABLE':
1300 prompt_variable(cdef, cread, cwrite, category, variable,
1301 show_comments, False)
1302 except Exception as inst:
1303 if str(inst) != 'BailOut':
1305 elif command in "vVsSlL":
1306 show_value = (command in "sSlL")
1307 (c1, c2, c3) = (cdef, cread, cwrite)
1309 (c1, c2, c3) = (cwrite, cwrite, cwrite)
1311 show_variables_all(c1, c2, c3, show_value, show_comments)
1312 elif mode == 'CATEGORY':
1313 show_variables_category(
1314 c1, c2, c3, category_id, show_value, show_comments)
1315 elif mode == 'VARIABLE':
1316 show_variable(c1, c2, c3, category, variable,
1317 show_value, show_comments)
1319 print(("Unknown command >%s< -- use h for help" % answer))
1321 ####################
1322 # creates directory for file if not yet existing
1325 def check_dir(config_file):
1326 dirname = os.path.dirname(config_file)
1327 if (not os.path.exists(dirname)):
1329 os.makedirs(dirname, 0o755)
1330 except OSError as e:
1331 print("Cannot create dir %s due to %s - exiting" % (dirname, e))
1334 if (not os.path.exists(dirname)):
1335 print("Cannot create dir %s - exiting" % dirname)
1338 print("Created directory %s" % dirname)
1340 ####################
1343 def optParserSetup(configuration):
1344 parser = OptionParser(usage=usage())
1345 parser.set_defaults(config_dir=configuration['config_dir'],
1346 service=configuration['service'],
1347 usual_variables=configuration['usual_variables'])
1348 parser.add_option("", "--configdir", dest="config_dir",
1349 help="specify configuration directory")
1350 parser.add_option("", "--service", dest="service",
1351 help="specify /etc/init.d style service name")
1352 parser.add_option("", "--usual_variable", dest="usual_variables",
1353 action="append", help="add a usual variable")
1357 def main(command, argv, configuration):
1358 global g_configuration
1359 g_configuration = configuration
1361 parser = optParserSetup(configuration)
1362 (config, args) = parser.parse_args()
1364 parser.error("too many arguments")
1366 configuration['service'] = config.service
1367 configuration['usual_variables'] = config.usual_variables
1368 configuration['config_dir'] = config.config_dir
1369 # add in new usual_variables defined on the command line
1370 for usual_variable in config.usual_variables:
1371 if usual_variable not in configuration['usual_variables']:
1372 configuration['usual_variables'].append(usual_variable)
1374 # intialize configuration
1375 init_configuration()
1377 (default_config, site_config, consolidated_config) = (
1378 def_default_config, def_site_config, def_consolidated_config)
1380 default_config = args[0]
1382 site_config = args[1]
1384 consolidated_config = args[2]
1386 for c in (default_config, site_config, consolidated_config):
1390 # the default settings only - read only
1391 cdef = PLCConfiguration(default_config)
1393 # in effect : default settings + local settings - read only
1394 cread = PLCConfiguration(default_config)
1396 except ConfigurationException as e:
1397 print(("Error %s in default config file %s" % (e, default_config)))
1400 print(traceback.print_exc())
1401 print(("default config files %s not found, is myplc installed ?" %
1405 # local settings only, will be modified & saved
1406 cwrite = PLCConfiguration()
1409 cread.load(site_config)
1410 cwrite.load(site_config)
1412 cwrite = PLCConfiguration()
1414 mainloop(cdef, cread, cwrite, default_config,
1415 site_config, consolidated_config)
1419 if __name__ == '__main__':
1421 if len(sys.argv) > 1 and sys.argv[1] in ['build', 'install', 'uninstall']:
1422 from distutils.core import setup
1423 setup(py_modules=["plc_config"])