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
18 import xml.dom.minidom
19 from xml.parsers.expat import ExpatError
20 from io import StringIO
21 from optparse import OptionParser
24 class ConfigurationException(Exception):
28 class PLCConfiguration:
30 Configuration file store. Optionally instantiate with a file path
33 plc = PLCConfiguration()
34 plc = PLCConfiguration(fileobj)
35 plc = PLCConfiguration("/etc/planetlab/plc_config.xml")
37 You may load() additional files later, which will be merged into
38 the current configuration:
40 plc.load("/etc/planetlab/local.xml")
42 You may also save() the configuration. If a file path or object is
43 not specified, the configuration will be written to the file path
44 or object that was first loaded.
47 plc.save("/etc/planetlab/plc_config.xml")
50 def __init__(self, file=None):
51 impl = xml.dom.minidom.getDOMImplementation()
52 self._dom = impl.createDocument(None, "configuration", None)
60 def _get_text(self, node):
62 Get the text of a text node.
65 if node.firstChild and \
66 node.firstChild.nodeType == node.TEXT_NODE:
67 if node.firstChild.data is None:
68 # Interpret simple presence of node as "", not NULL
71 return node.firstChild.data
75 def _get_text_of_child(self, parent, name):
77 Get the text of a (direct) child text node.
80 for node in parent.childNodes:
81 if node.nodeType == node.ELEMENT_NODE and \
83 return self._get_text(node)
87 def _set_text(self, node, data):
89 Set the text of a text node.
92 if node.firstChild and \
93 node.firstChild.nodeType == node.TEXT_NODE:
95 node.removeChild(node.firstChild)
97 node.firstChild.data = data
98 elif data is not None:
101 node.appendChild(text)
103 def _set_text_of_child(self, parent, name, data):
105 Set the text of a (direct) child text node.
108 for node in parent.childNodes:
109 if node.nodeType == node.ELEMENT_NODE and \
110 node.tagName == name:
111 self._set_text(node, data)
114 child = TrimTextElement(name)
115 self._set_text(child, data)
116 parent.appendChild(child)
118 def _category_element_to_dict(self, category_element):
120 Turn a <category> element into a dictionary of its attributes
121 and child text nodes.
125 category['id'] = category_element.getAttribute('id').lower()
126 for node in category_element.childNodes:
127 if node.nodeType == node.ELEMENT_NODE and \
128 node.tagName in ['name', 'description']:
129 category[node.tagName] = self._get_text_of_child(
130 category_element, node.tagName)
131 category['element'] = category_element
135 def _variable_element_to_dict(self, variable_element):
137 Turn a <variable> element into a dictionary of its attributes
138 and child text nodes.
142 variable['id'] = variable_element.getAttribute('id').lower()
143 if variable_element.hasAttribute('type'):
144 variable['type'] = variable_element.getAttribute('type')
145 for node in variable_element.childNodes:
146 if node.nodeType == node.ELEMENT_NODE and \
147 node.tagName in ['name', 'value', 'description']:
148 variable[node.tagName] = self._get_text_of_child(
149 variable_element, node.tagName)
150 variable['element'] = variable_element
154 def _group_element_to_dict(self, group_element):
156 Turn a <group> element into a dictionary of its attributes
157 and child text nodes.
161 for node in group_element.childNodes:
162 if node.nodeType == node.ELEMENT_NODE and \
163 node.tagName in ['id', 'name', 'default', 'description', 'uservisible']:
164 group[node.tagName] = self._get_text_of_child(
165 group_element, node.tagName)
166 group['element'] = group_element
170 def _packagereq_element_to_dict(self, packagereq_element):
172 Turns a <packagereq> element into a dictionary of its attributes
173 and child text nodes.
177 if packagereq_element.hasAttribute('type'):
178 package['type'] = packagereq_element.getAttribute('type')
179 package['name'] = self._get_text(packagereq_element)
180 package['element'] = packagereq_element
184 def load(self, file="/etc/planetlab/plc_config.xml"):
186 Merge file into configuration store.
190 dom = xml.dom.minidom.parse(file)
191 except ExpatError as e:
192 raise ConfigurationException(e)
194 if isinstance(file, str):
195 self._files.append(os.path.abspath(file))
197 # Parse <variables> section
198 for variables_element in dom.getElementsByTagName('variables'):
199 for category_element in variables_element.getElementsByTagName('category'):
200 category = self._category_element_to_dict(category_element)
201 self.set(category, None)
203 for variablelist_element in category_element.getElementsByTagName('variablelist'):
204 for variable_element in variablelist_element.getElementsByTagName('variable'):
205 variable = self._variable_element_to_dict(
207 self.set(category, variable)
209 # Parse <comps> section
210 for comps_element in dom.getElementsByTagName('comps'):
211 for group_element in comps_element.getElementsByTagName('group'):
212 group = self._group_element_to_dict(group_element)
213 self.add_package(group, None)
215 for packagereq_element in group_element.getElementsByTagName('packagereq'):
216 package = self._packagereq_element_to_dict(
218 self.add_package(group, package)
220 def save(self, file=None):
222 Write configuration store to file.
227 file = self._files[0]
229 file = "/etc/planetlab/plc_config.xml"
231 if isinstance(file, str):
232 fileobj = open(file, 'w')
237 fileobj.write(self.output_xml())
242 def verify(self, default, read, verify_variables={}):
243 """ Confirm that the existing configuration is consistent
244 according to the checks below.
246 It looks for filled-in values in the order of, local object (self),
247 followed by cread (read values), and finally default values.
251 default configuration
253 list of category/variable tuples to validate in these configurations
257 dict of values for the category/variables passed in
258 If an exception is found, ConfigurationException is raised.
262 validated_variables = {}
263 for category_id, variable_id in verify_variables.items():
264 category_id = category_id.lower()
265 variable_id = variable_id.lower()
266 variable_value = None
267 sources = (self, read, default)
268 for source in sources:
269 (category_value, variable_value) = source.get(
270 category_id, variable_id)
271 if variable_value != None:
272 entry = validated_variables.get(category_id, [])
273 entry.append(variable_value['value'])
274 validated_variables["%s_%s" % (
275 category_id.upper(), variable_id.upper())] = entry
277 if variable_value == None:
278 raise ConfigurationException("Cannot find %s_%s)" %
279 (category_id.upper(),
280 variable_id.upper()))
281 return validated_variables
283 def get(self, category_id, variable_id):
285 Get the specified variable in the specified category.
289 category_id = unique category identifier (e.g., 'plc_www')
290 variable_id = unique variable identifier (e.g., 'port')
294 variable = { 'id': "variable_identifier",
295 'type': "variable_type",
296 'value': "variable_value",
297 'name': "Variable name",
298 'description': "Variable description" }
301 if category_id.lower() in self._variables:
302 (category, variables) = self._variables[category_id]
303 if variable_id.lower() in variables:
304 variable = variables[variable_id]
311 return (category, variable)
313 def delete(self, category_id, variable_id):
315 Delete the specified variable from the specified category. If
316 variable_id is None, deletes all variables from the specified
317 category as well as the category itself.
321 category_id = unique category identifier (e.g., 'plc_www')
322 variable_id = unique variable identifier (e.g., 'port')
325 if category_id.lower() in self._variables:
326 (category, variables) = self._variables[category_id]
327 if variable_id is None:
328 category['element'].parentNode.removeChild(category['element'])
329 del self._variables[category_id]
330 elif variable_id.lower() in variables:
331 variable = variables[variable_id]
332 variable['element'].parentNode.removeChild(variable['element'])
333 del variables[variable_id]
335 def set(self, category, variable):
337 Add and/or update the specified variable. The 'id' fields are
338 mandatory. If a field is not specified and the category and/or
339 variable already exists, the field will not be updated. If
340 'variable' is None, only adds and/or updates the specified
345 category = { 'id': "category_identifier",
346 'name': "Category name",
347 'description': "Category description" }
349 variable = { 'id': "variable_identifier",
350 'type': "variable_type",
351 'value': "variable_value",
352 'name': "Variable name",
353 'description': "Variable description" }
356 if ('id' not in category
357 or not isinstance(category['id'], str)):
360 category_id = category['id'].lower()
362 if category_id in self._variables:
364 (old_category, variables) = self._variables[category_id]
366 # Merge category attributes
367 for tag in ['name', 'description']:
369 old_category[tag] = category[tag]
370 self._set_text_of_child(
371 old_category['element'], tag, category[tag])
373 category_element = old_category['element']
376 category_element = self._dom.createElement('category')
377 category_element.setAttribute('id', category_id)
378 for tag in ['name', 'description']:
380 self._set_text_of_child(
381 category_element, tag, category[tag])
383 if self._dom.documentElement.getElementsByTagName('variables'):
384 variables_element = self._dom.documentElement.getElementsByTagName('variables')[
387 variables_element = self._dom.createElement('variables')
388 self._dom.documentElement.appendChild(variables_element)
389 variables_element.appendChild(category_element)
392 category['element'] = category_element
394 self._variables[category_id] = (category, variables)
396 if (variable is None or 'id' not in variable
397 or not isinstance(variable['id'], str)):
400 variable_id = variable['id'].lower()
402 if variable_id in variables:
404 old_variable = variables[variable_id]
406 # Merge variable attributes
407 for attribute in ['type']:
408 if attribute in variable:
409 old_variable[attribute] = variable[attribute]
410 old_variable['element'].setAttribute(
411 attribute, variable[attribute])
412 for tag in ['name', 'value', 'description']:
414 old_variable[tag] = variable[tag]
415 self._set_text_of_child(
416 old_variable['element'], tag, variable[tag])
419 variable_element = self._dom.createElement('variable')
420 variable_element.setAttribute('id', variable_id)
421 for attribute in ['type']:
422 if attribute in variable:
423 variable_element.setAttribute(
424 attribute, variable[attribute])
425 for tag in ['name', 'value', 'description']:
427 self._set_text_of_child(
428 variable_element, tag, variable[tag])
430 if category_element.getElementsByTagName('variablelist'):
431 variablelist_element = category_element.getElementsByTagName('variablelist')[
434 variablelist_element = self._dom.createElement('variablelist')
435 category_element.appendChild(variablelist_element)
436 variablelist_element.appendChild(variable_element)
439 variable['element'] = variable_element
440 variables[variable_id] = variable
442 def locate_varname(self, varname):
444 Locates category and variable from a variable's (shell) name
447 (variable, category) when found
448 (None, None) otherwise
451 for (category_id, (category, variables)) in self._variables.items():
452 for variable in list(variables.values()):
453 (id, name, value, comments) = self._sanitize_variable(
454 category_id, variable)
456 return (category, variable)
459 def get_package(self, group_id, package_name):
461 Get the specified package in the specified package group.
465 group_id - unique group id (e.g., 'plc')
466 package_name - unique package name (e.g., 'postgresql')
470 package = { 'name': "package_name",
471 'type': "mandatory|optional" }
474 if group_id.lower() in self._packages:
475 (group, packages) = self._packages[group_id]
476 if package_name in packages:
477 package = packages[package_name]
484 return (group, package)
486 def delete_package(self, group_id, package_name):
488 Deletes the specified variable from the specified category. If
489 variable_id is None, deletes all variables from the specified
490 category as well as the category itself.
494 group_id - unique group id (e.g., 'plc')
495 package_name - unique package name (e.g., 'postgresql')
498 if group_id in self._packages:
499 (group, packages) = self._packages[group_id]
500 if package_name is None:
501 group['element'].parentNode.removeChild(group['element'])
502 del self._packages[group_id]
503 elif package_name.lower() in packages:
504 package = packages[package_name]
505 package['element'].parentNode.removeChild(package['element'])
506 del packages[package_name]
508 def add_package(self, group, package):
510 Add and/or update the specified package. The 'id' and 'name'
511 fields are mandatory. If a field is not specified and the
512 package or group already exists, the field will not be
513 updated. If package is None, only adds/or updates the
518 group = { 'id': "group_identifier",
519 'name': "Group name",
520 'default': "true|false",
521 'description': "Group description",
522 'uservisible': "true|false" }
524 package = { 'name': "package_name",
525 'type': "mandatory|optional" }
528 if 'id' not in group:
531 group_id = group['id']
533 if group_id in self._packages:
535 (old_group, packages) = self._packages[group_id]
537 # Merge group attributes
538 for tag in ['id', 'name', 'default', 'description', 'uservisible']:
540 old_group[tag] = group[tag]
541 self._set_text_of_child(
542 old_group['element'], tag, group[tag])
544 group_element = old_group['element']
547 group_element = self._dom.createElement('group')
548 for tag in ['id', 'name', 'default', 'description', 'uservisible']:
550 self._set_text_of_child(group_element, tag, group[tag])
552 if self._dom.documentElement.getElementsByTagName('comps'):
553 comps_element = self._dom.documentElement.getElementsByTagName('comps')[
556 comps_element = self._dom.createElement('comps')
557 self._dom.documentElement.appendChild(comps_element)
558 comps_element.appendChild(group_element)
561 group['element'] = group_element
563 self._packages[group_id] = (group, packages)
565 if package is None or 'name' not in package:
568 package_name = package['name']
569 if package_name in packages:
571 old_package = packages[package_name]
573 # Merge variable attributes
574 for attribute in ['type']:
575 if attribute in package:
576 old_package[attribute] = package[attribute]
577 old_package['element'].setAttribute(
578 attribute, package[attribute])
581 packagereq_element = TrimTextElement('packagereq')
582 self._set_text(packagereq_element, package_name)
583 for attribute in ['type']:
584 if attribute in package:
585 packagereq_element.setAttribute(
586 attribute, package[attribute])
588 if group_element.getElementsByTagName('packagelist'):
589 packagelist_element = group_element.getElementsByTagName('packagelist')[
592 packagelist_element = self._dom.createElement('packagelist')
593 group_element.appendChild(packagelist_element)
594 packagelist_element.appendChild(packagereq_element)
597 package['element'] = packagereq_element
598 packages[package_name] = package
602 Return all variables.
606 variables = { 'category_id': (category, variablelist) }
608 category = { 'id': "category_identifier",
609 'name': "Category name",
610 'description': "Category description" }
612 variablelist = { 'variable_id': variable }
614 variable = { 'id': "variable_identifier",
615 'type': "variable_type",
616 'value': "variable_value",
617 'name': "Variable name",
618 'description': "Variable description" }
621 return self._variables
629 packages = { 'group_id': (group, packagelist) }
631 group = { 'id': "group_identifier",
632 'name': "Group name",
633 'default': "true|false",
634 'description': "Group description",
635 'uservisible': "true|false" }
637 packagelist = { 'package_name': package }
639 package = { 'name': "package_name",
640 'type': "mandatory|optional" }
643 return self._packages
645 def _sanitize_variable(self, category_id, variable):
646 assert 'id' in variable
647 # Prepend variable name with category label
648 id = category_id + "_" + variable['id']
652 if 'type' in variable:
653 type = variable['type']
657 if 'name' in variable:
658 name = variable['name']
662 if 'value' in variable and variable['value'] is not None:
663 value = variable['value']
664 if type == "int" or type == "double":
665 # bash, Python, and PHP do not require that numbers be quoted
667 elif type == "boolean":
668 # bash, Python, and PHP can all agree on 0 and 1
674 # bash, Python, and PHP all support strong single quoting
675 value = "'" + value.replace("'", "\\'") + "'"
679 if 'description' in variable and variable['description'] is not None:
680 description = variable['description']
681 # Collapse consecutive whitespace
682 description = re.sub(r'\s+', ' ', description)
683 # Wrap comments at 70 columns
684 wrapper = textwrap.TextWrapper()
685 comments = wrapper.wrap(description)
689 return (id, name, value, comments)
693 DO NOT EDIT. This file was automatically generated at
697 """ % (time.asctime(), os.linesep.join(self._files))
699 # Get rid of the surrounding newlines
700 return header.strip().split(os.linesep)
702 def output_shell(self, show_comments=True, encoding="utf-8"):
704 Return variables as a shell script.
707 buf = codecs.lookup(encoding)[3](StringIO())
708 buf.writelines(["# " + line + os.linesep for line in self._header()])
710 for (category_id, (category, variables)) in self._variables.items():
711 for variable in list(variables.values()):
712 (id, name, value, comments) = self._sanitize_variable(
713 category_id, variable)
715 buf.write(os.linesep)
717 buf.write("# " + name + os.linesep)
718 if comments is not None:
720 ["# " + line + os.linesep for line in comments])
721 # bash does not have the concept of NULL
722 if value is not None:
723 buf.write(id + "=" + value + os.linesep)
725 return buf.getvalue()
727 def output_php(self, encoding="utf-8"):
729 Return variables as a PHP script.
732 buf = codecs.lookup(encoding)[3](StringIO())
733 buf.write("<?php" + os.linesep)
734 buf.writelines(["// " + line + os.linesep for line in self._header()])
736 for (category_id, (category, variables)) in self._variables.items():
737 for variable in list(variables.values()):
738 (id, name, value, comments) = self._sanitize_variable(
739 category_id, variable)
740 buf.write(os.linesep)
742 buf.write("// " + name + os.linesep)
743 if comments is not None:
745 ["// " + line + os.linesep for line in comments])
748 buf.write("define('%s', %s);" % (id, value) + os.linesep)
750 buf.write("?>" + os.linesep)
752 return buf.getvalue()
754 def output_xml(self, encoding="utf-8"):
756 Return variables in original XML format.
759 buf = codecs.lookup(encoding)[3](StringIO())
760 self._dom.writexml(buf, addindent=" ", indent="",
761 newl="\n", encoding=encoding)
763 return buf.getvalue()
765 def output_variables(self, encoding="utf-8"):
767 Return list of all variable names.
770 buf = codecs.lookup(encoding)[3](StringIO())
772 for (category_id, (category, variables)) in self._variables.items():
773 for variable in list(variables.values()):
774 (id, name, value, comments) = self._sanitize_variable(
775 category_id, variable)
776 buf.write(id + os.linesep)
778 return buf.getvalue()
780 def output_packages(self, encoding="utf-8"):
782 Return list of all packages.
785 buf = codecs.lookup(encoding)[3](StringIO())
787 for (group, packages) in list(self._packages.values()):
788 buf.write(os.linesep.join(list(packages.keys())))
791 buf.write(os.linesep)
793 return buf.getvalue()
795 def output_groups(self, encoding="utf-8"):
797 Return list of all package group names.
800 buf = codecs.lookup(encoding)[3](StringIO())
802 for (group, packages) in list(self._packages.values()):
803 buf.write(group['name'] + os.linesep)
805 return buf.getvalue()
807 def output_comps(self, encoding="utf-8"):
809 Return <comps> section of configuration.
812 if self._dom is None or \
813 not self._dom.getElementsByTagName("comps"):
815 comps = self._dom.getElementsByTagName("comps")[0]
817 impl = xml.dom.minidom.getDOMImplementation()
818 doc = impl.createDocument(None, "comps", None)
820 buf = codecs.lookup(encoding)[3](StringIO())
822 # Pop it off the DOM temporarily
823 parent = comps.parentNode
824 parent.removeChild(comps)
826 doc.replaceChild(comps, doc.documentElement)
827 doc.writexml(buf, encoding=encoding)
830 parent.appendChild(comps)
832 return buf.getvalue()
834 def validate_type(self, variable_type, value):
836 # ideally we should use the "validate_*" methods in PLCAPI or
837 # even declare some checks along with the default
838 # configuration (using RELAX NG?) but this shall work for now.
839 def ip_validator(val):
842 socket.inet_aton(val)
847 def email_validator(val):
848 return re.match('\A[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9._\-]+\.[a-zA-Z]+\Z', val)
850 def boolean_validator(val):
851 return val in ['true', 'false']
854 'email': email_validator,
856 'boolean': boolean_validator,
859 # validate it if not a know type.
860 validator = validators.get(variable_type, lambda x: True)
861 return validator(value)
864 # xml.dom.minidom.Text.writexml adds surrounding whitespace to textual
865 # data when pretty-printing. Override this behavior.
866 class TrimText(xml.dom.minidom.Text):
867 def writexml(self, writer, indent="", addindent="", newl=""):
868 xml.dom.minidom.Text.writexml(self, writer, "", "", "")
871 class TrimTextElement(xml.dom.minidom.Element):
872 def writexml(self, writer, indent="", addindent="", newl=""):
874 xml.dom.minidom.Element.writexml(self, writer, "", "", "")
881 g_configuration = None
882 usual_variables = None
887 def noop_validator(validated_variables):
891 # historically we could also configure the devel pkg....
892 def init_configuration():
893 global g_configuration
894 global usual_variables, config_dir, service
896 usual_variables = g_configuration["usual_variables"]
897 config_dir = g_configuration["config_dir"]
898 service = g_configuration["service"]
900 global def_default_config, def_site_config, def_consolidated_config
901 def_default_config = "%s/default_config.xml" % config_dir
902 def_site_config = "%s/configs/site.xml" % config_dir
903 def_consolidated_config = "%s/%s_config.xml" % (config_dir, service)
905 global mainloop_usage
906 mainloop_usage = """Available commands:
907 Uppercase versions give variables comments, when available
908 u/U\t\t\tEdit usual variables
910 r\t\t\tRestart %(service)s service
911 R\t\t\tReload %(service)s service (rebuild config files for sh, python....)
912 q\t\t\tQuit (without saving)
915 l/L [<cat>|<var>]\tShow Locally modified variables/values
916 s/S [<cat>|<var>]\tShow variables/values (all, in category, single)
917 e/E [<cat>|<var>]\tEdit variables (all, in category, single)
919 c\t\t\tList categories
920 v/V [<cat>|<var>]\tList Variables (all, in category, single)
922 Typical usage involves: u, [l,] w, r, q
927 command_usage = "%prog [options] [default-xml [site-xml [consolidated-xml]]]"
930 \t default-xml defaults to %s
931 \t site-xml defaults to %s
932 \t consolidated-xml defaults to %s""" % (def_default_config, def_site_config, def_consolidated_config)
937 variable_usage = """Edit Commands :
938 #\tShow variable comments
939 .\tStops prompting, return to mainloop
940 /\tCleans any site-defined value, reverts to default
941 =\tShows default value
942 >\tSkips to next category
949 def get_value(config, category_id, variable_id):
950 (category, variable) = config.get(category_id, variable_id)
951 return variable['value']
954 def get_type(config, category_id, variable_id):
955 (category, variable) = config.get(category_id, variable_id)
956 return variable['type']
959 def get_current_value(cread, cwrite, category_id, variable_id):
960 # the value stored in cwrite, if present, is the one we want
962 result = get_value(cwrite, category_id, variable_id)
964 result = get_value(cread, category_id, variable_id)
967 # refrain from using plc_config's _sanitize
970 def get_varname(config, category_id, variable_id):
971 (category, variable) = config.get(category_id, variable_id)
972 return (category_id+"_"+variable['id']).upper()
974 # could not avoid using _sanitize here..
977 def get_name_comments(config, cid, vid):
979 (category, variable) = config.get(cid, vid)
980 (id, name, value, comments) = config._sanitize_variable(cid, variable)
981 return (name, comments)
986 def print_name_comments(config, cid, vid):
987 (name, comments) = get_name_comments(config, cid, vid)
989 print("### %s" % name)
991 for line in comments:
994 print("!!! No comment associated to %s_%s" % (cid, vid))
999 def list_categories(config):
1001 for (category_id, (category, variables)) in config.variables().items():
1002 result += [category_id]
1006 def print_categories(config):
1007 print("Known categories")
1008 for cid in list_categories(config):
1009 print("%s" % (cid.upper()))
1011 ####################
1014 def list_category(config, cid):
1016 for (category_id, (category, variables)) in config.variables().items():
1017 if (cid == category_id):
1018 for variable in list(variables.values()):
1019 result += ["%s_%s" % (cid, variable['id'])]
1023 def print_category(config, cid, show_comments=True):
1026 vids = list_category(config, cid)
1027 if (len(vids) == 0):
1028 print("%s : no such category" % CID)
1030 print("Category %s contains" % (CID))
1034 ####################
1037 def consolidate(default_config, site_config, consolidated_config):
1040 conso = PLCConfiguration(default_config)
1041 conso.load(site_config)
1042 conso.save(consolidated_config)
1043 except Exception as inst:
1044 print("Could not consolidate, %s" % (str(inst)))
1046 print(("Merged\n\t%s\nand\t%s\ninto\t%s" % (default_config, site_config,
1047 consolidated_config)))
1050 def reload_service():
1052 os.system("set -x ; systemctl reload %s" % service)
1054 ####################
1057 def restart_service():
1059 print(("==================== Stopping %s" % service))
1060 os.system("systemctl stop %s" % service)
1061 print(("==================== Starting %s" % service))
1062 os.system("systemctl start %s" % service)
1064 ####################
1067 def prompt_variable(cdef, cread, cwrite, category, variable,
1068 show_comments, support_next=False):
1070 assert 'id' in category
1071 assert 'id' in variable
1073 category_id = category['id']
1074 variable_id = variable['id']
1077 default_value = get_value(cdef, category_id, variable_id)
1078 variable_type = get_type(cdef, category_id, variable_id)
1079 current_value = get_current_value(
1080 cread, cwrite, category_id, variable_id)
1081 varname = get_varname(cread, category_id, variable_id)
1084 print_name_comments(cdef, category_id, variable_id)
1085 prompt = "== %s : [%s] " % (varname, current_value)
1087 answer = input(prompt).strip()
1089 raise Exception('BailOut')
1090 except KeyboardInterrupt:
1092 raise Exception('BailOut')
1095 if (answer == "") or (answer == current_value):
1097 elif (answer == "."):
1098 raise Exception('BailOut')
1099 elif (answer == "#"):
1100 print_name_comments(cread, category_id, variable_id)
1101 elif (answer == "?"):
1102 print(variable_usage.strip())
1103 elif (answer == "="):
1104 print(("%s defaults to %s" % (varname, default_value)))
1105 # revert to default : remove from cwrite (i.e. site-config)
1106 elif (answer == "/"):
1107 cwrite.delete(category_id, variable_id)
1108 print(("%s reverted to %s" % (varname, default_value)))
1110 elif (answer == ">"):
1112 raise Exception('NextCategory')
1114 print("No support for next category")
1116 if cdef.validate_type(variable_type, answer):
1117 variable['value'] = answer
1118 cwrite.set(category, variable)
1121 print("Not a valid value")
1124 def prompt_variables_all(cdef, cread, cwrite, show_comments):
1126 for (category_id, (category, variables)) in cread.variables().items():
1127 print(("========== Category = %s" % category_id.upper()))
1128 for variable in list(variables.values()):
1130 newvar = prompt_variable(cdef, cread, cwrite, category, variable,
1131 show_comments, True)
1132 except Exception as inst:
1133 if (str(inst) == 'NextCategory'):
1138 except Exception as inst:
1139 if (str(inst) == 'BailOut'):
1145 def prompt_variables_category(cdef, cread, cwrite, cid, show_comments):
1149 print(("========== Category = %s" % CID))
1150 for vid in list_category(cdef, cid):
1151 (category, variable) = cdef.locate_varname(vid.upper())
1152 newvar = prompt_variable(cdef, cread, cwrite, category, variable,
1153 show_comments, False)
1154 except Exception as inst:
1155 if (str(inst) == 'BailOut'):
1160 ####################
1163 def show_variable(cdef, cread, cwrite,
1164 category, variable, show_value, show_comments):
1165 assert 'id' in category
1166 assert 'id' in variable
1168 category_id = category['id']
1169 variable_id = variable['id']
1171 default_value = get_value(cdef, category_id, variable_id)
1172 current_value = get_current_value(cread, cwrite, category_id, variable_id)
1173 varname = get_varname(cread, category_id, variable_id)
1175 print_name_comments(cdef, category_id, variable_id)
1177 print("%s = %s" % (varname, current_value))
1179 print("%s" % (varname))
1182 def show_variables_all(cdef, cread, cwrite, show_value, show_comments):
1183 for (category_id, (category, variables)) in cread.variables().items():
1184 print(("========== Category = %s" % category_id.upper()))
1185 for variable in list(variables.values()):
1186 show_variable(cdef, cread, cwrite,
1187 category, variable, show_value, show_comments)
1190 def show_variables_category(cdef, cread, cwrite, cid, show_value, show_comments):
1193 print(("========== Category = %s" % CID))
1194 for vid in list_category(cdef, cid):
1195 (category, variable) = cdef.locate_varname(vid.upper())
1196 show_variable(cdef, cread, cwrite, category, variable,
1197 show_value, show_comments)
1200 ####################
1201 re_mainloop_0arg = "^(?P<command>[uUwrRqlLsSeEcvVhH\?])[ \t]*$"
1202 re_mainloop_1arg = "^(?P<command>[sSeEvV])[ \t]+(?P<arg>\w+)$"
1203 matcher_mainloop_0arg = re.compile(re_mainloop_0arg)
1204 matcher_mainloop_1arg = re.compile(re_mainloop_1arg)
1207 def mainloop(cdef, cread, cwrite, default_config, site_config, consolidated_config):
1212 "Enter command (u for usual changes, w to save, ? for help) ").strip()
1215 except KeyboardInterrupt:
1219 if (answer == "") or (answer in "?hH"):
1220 print(mainloop_usage)
1222 groups_parse = matcher_mainloop_0arg.match(answer)
1225 command = groups_parse.group('command')
1228 groups_parse = matcher_mainloop_1arg.match(answer)
1230 command = groups_parse.group('command')
1231 arg = groups_parse.group('arg')
1233 print(("Unknown command >%s< -- use h for help" % answer))
1236 show_comments = command.isupper()
1242 variables = list_category(cdef, arg)
1244 # category_id as the category name
1245 # variables as the list of variable names
1249 (category, variable) = cdef.locate_varname(arg)
1251 # category/variable as output by locate_varname
1254 print("%s: no such category or variable" % arg)
1258 # todo check confirmation
1260 elif command == "w":
1262 # Confirm that various constraints are met before saving file.
1263 validate_variables = g_configuration.get(
1264 'validate_variables', {})
1265 validated_variables = cwrite.verify(
1266 cdef, cread, validate_variables)
1267 validator = g_configuration.get('validator', noop_validator)
1268 validator(validated_variables)
1269 cwrite.save(site_config)
1270 except ConfigurationException as e:
1271 print("Save failed due to a configuration exception: %s" % e)
1274 print(traceback.print_exc())
1275 print(("Could not save -- fix write access on %s" % site_config))
1277 print(("Wrote %s" % site_config))
1278 consolidate(default_config, site_config, consolidated_config)
1279 print(("You might want to type 'r' (restart %s), 'R' (reload %s) or 'q' (quit)" %
1280 (service, service)))
1281 elif command in "uU":
1282 global usual_variables
1284 for varname in usual_variables:
1285 (category, variable) = cdef.locate_varname(varname)
1286 if not (category is None and variable is None):
1287 prompt_variable(cdef, cread, cwrite,
1288 category, variable, False)
1289 except Exception as inst:
1290 if (str(inst) != 'BailOut'):
1292 elif command == "r":
1294 elif command == "R":
1296 elif command == "c":
1297 print_categories(cread)
1298 elif command in "eE":
1300 prompt_variables_all(cdef, cread, cwrite, show_comments)
1301 elif mode == 'CATEGORY':
1302 prompt_variables_category(
1303 cdef, cread, cwrite, category_id, show_comments)
1304 elif mode == 'VARIABLE':
1306 prompt_variable(cdef, cread, cwrite, category, variable,
1307 show_comments, False)
1308 except Exception as inst:
1309 if str(inst) != 'BailOut':
1311 elif command in "vVsSlL":
1312 show_value = (command in "sSlL")
1313 (c1, c2, c3) = (cdef, cread, cwrite)
1315 (c1, c2, c3) = (cwrite, cwrite, cwrite)
1317 show_variables_all(c1, c2, c3, show_value, show_comments)
1318 elif mode == 'CATEGORY':
1319 show_variables_category(
1320 c1, c2, c3, category_id, show_value, show_comments)
1321 elif mode == 'VARIABLE':
1322 show_variable(c1, c2, c3, category, variable,
1323 show_value, show_comments)
1325 print(("Unknown command >%s< -- use h for help" % answer))
1327 ####################
1328 # creates directory for file if not yet existing
1331 def check_dir(config_file):
1332 dirname = os.path.dirname(config_file)
1333 if (not os.path.exists(dirname)):
1335 os.makedirs(dirname, 0o755)
1336 except OSError as e:
1337 print("Cannot create dir %s due to %s - exiting" % (dirname, e))
1340 if (not os.path.exists(dirname)):
1341 print("Cannot create dir %s - exiting" % dirname)
1344 print("Created directory %s" % dirname)
1346 ####################
1349 def optParserSetup(configuration):
1350 parser = OptionParser(usage=usage())
1351 parser.set_defaults(config_dir=configuration['config_dir'],
1352 service=configuration['service'],
1353 usual_variables=configuration['usual_variables'])
1354 parser.add_option("", "--configdir", dest="config_dir",
1355 help="specify configuration directory")
1356 parser.add_option("", "--service", dest="service",
1357 help="specify /etc/init.d style service name")
1358 parser.add_option("", "--usual_variable", dest="usual_variables",
1359 action="append", help="add a usual variable")
1363 def main(command, argv, configuration):
1364 global g_configuration
1365 g_configuration = configuration
1367 parser = optParserSetup(configuration)
1368 (config, args) = parser.parse_args()
1370 parser.error("too many arguments")
1372 configuration['service'] = config.service
1373 configuration['usual_variables'] = config.usual_variables
1374 configuration['config_dir'] = config.config_dir
1375 # add in new usual_variables defined on the command line
1376 for usual_variable in config.usual_variables:
1377 if usual_variable not in configuration['usual_variables']:
1378 configuration['usual_variables'].append(usual_variable)
1380 # intialize configuration
1381 init_configuration()
1383 (default_config, site_config, consolidated_config) = (
1384 def_default_config, def_site_config, def_consolidated_config)
1386 default_config = args[0]
1388 site_config = args[1]
1390 consolidated_config = args[2]
1392 for c in (default_config, site_config, consolidated_config):
1396 # the default settings only - read only
1397 cdef = PLCConfiguration(default_config)
1399 # in effect : default settings + local settings - read only
1400 cread = PLCConfiguration(default_config)
1402 except ConfigurationException as e:
1403 print(("Error %s in default config file %s" % (e, default_config)))
1406 print(traceback.print_exc())
1407 print(("default config files %s not found, is myplc installed ?" %
1411 # local settings only, will be modified & saved
1412 cwrite = PLCConfiguration()
1415 cread.load(site_config)
1416 cwrite.load(site_config)
1418 cwrite = PLCConfiguration()
1420 mainloop(cdef, cread, cwrite, default_config,
1421 site_config, consolidated_config)
1425 if __name__ == '__main__':
1427 if len(sys.argv) > 1 and sys.argv[1] in ['build', 'install', 'uninstall']:
1428 from distutils.core import setup
1429 setup(py_modules=["plc_config"])