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
19 import xml.dom.minidom
20 from xml.parsers.expat import ExpatError
21 from StringIO import StringIO
22 from optparse import OptionParser
24 class ConfigurationException(Exception): pass
26 class PLCConfiguration:
28 Configuration file store. Optionally instantiate with a file path
31 plc = PLCConfiguration()
32 plc = PLCConfiguration(fileobj)
33 plc = PLCConfiguration("/etc/planetlab/plc_config.xml")
35 You may load() additional files later, which will be merged into
36 the current configuration:
38 plc.load("/etc/planetlab/local.xml")
40 You may also save() the configuration. If a file path or object is
41 not specified, the configuration will be written to the file path
42 or object that was first loaded.
45 plc.save("/etc/planetlab/plc_config.xml")
48 def __init__(self, file = None):
49 impl = xml.dom.minidom.getDOMImplementation()
50 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
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)
88 def _set_text(self, node, data):
90 Set the text of a text node.
93 if node.firstChild and \
94 node.firstChild.nodeType == node.TEXT_NODE:
96 node.removeChild(node.firstChild)
98 node.firstChild.data = data
99 elif data is not None:
102 node.appendChild(text)
105 def _set_text_of_child(self, parent, name, data):
107 Set the text of a (direct) child text node.
110 for node in parent.childNodes:
111 if node.nodeType == node.ELEMENT_NODE and \
112 node.tagName == name:
113 self._set_text(node, data)
116 child = TrimTextElement(name)
117 self._set_text(child, data)
118 parent.appendChild(child)
121 def _category_element_to_dict(self, category_element):
123 Turn a <category> element into a dictionary of its attributes
124 and child text nodes.
128 category['id'] = category_element.getAttribute('id').lower()
129 for node in category_element.childNodes:
130 if node.nodeType == node.ELEMENT_NODE and \
131 node.tagName in ['name', 'description']:
132 category[node.tagName] = self._get_text_of_child(category_element, node.tagName)
133 category['element'] = category_element
138 def _variable_element_to_dict(self, variable_element):
140 Turn a <variable> element into a dictionary of its attributes
141 and child text nodes.
145 variable['id'] = variable_element.getAttribute('id').lower()
146 if variable_element.hasAttribute('type'):
147 variable['type'] = variable_element.getAttribute('type')
148 for node in variable_element.childNodes:
149 if node.nodeType == node.ELEMENT_NODE and \
150 node.tagName in ['name', 'value', 'description']:
151 variable[node.tagName] = self._get_text_of_child(variable_element, node.tagName)
152 variable['element'] = variable_element
157 def _group_element_to_dict(self, group_element):
159 Turn a <group> element into a dictionary of its attributes
160 and child text nodes.
164 for node in group_element.childNodes:
165 if node.nodeType == node.ELEMENT_NODE and \
166 node.tagName in ['id', 'name', 'default', 'description', 'uservisible']:
167 group[node.tagName] = self._get_text_of_child(group_element, node.tagName)
168 group['element'] = group_element
173 def _packagereq_element_to_dict(self, packagereq_element):
175 Turns a <packagereq> element into a dictionary of its attributes
176 and child text nodes.
180 if packagereq_element.hasAttribute('type'):
181 package['type'] = packagereq_element.getAttribute('type')
182 package['name'] = self._get_text(packagereq_element)
183 package['element'] = packagereq_element
188 def load(self, file = "/etc/planetlab/plc_config.xml"):
190 Merge file into configuration store.
194 dom = xml.dom.minidom.parse(file)
195 except ExpatError, e:
196 raise ConfigurationException, e
198 if type(file) in types.StringTypes:
199 self._files.append(os.path.abspath(file))
201 # Parse <variables> section
202 for variables_element in dom.getElementsByTagName('variables'):
203 for category_element in variables_element.getElementsByTagName('category'):
204 category = self._category_element_to_dict(category_element)
205 self.set(category, None)
207 for variablelist_element in category_element.getElementsByTagName('variablelist'):
208 for variable_element in variablelist_element.getElementsByTagName('variable'):
209 variable = self._variable_element_to_dict(variable_element)
210 self.set(category, variable)
212 # Parse <comps> section
213 for comps_element in dom.getElementsByTagName('comps'):
214 for group_element in comps_element.getElementsByTagName('group'):
215 group = self._group_element_to_dict(group_element)
216 self.add_package(group, None)
218 for packagereq_element in group_element.getElementsByTagName('packagereq'):
219 package = self._packagereq_element_to_dict(packagereq_element)
220 self.add_package(group, package)
223 def save(self, file = None):
225 Write configuration store to file.
230 file = self._files[0]
232 file = "/etc/planetlab/plc_config.xml"
234 if type(file) in types.StringTypes:
235 fileobj = open(file, 'w')
240 fileobj.write(self.output_xml())
245 def verify(self, default, read, verify_variables={}):
246 """ Confirm that the existing configuration is consistent
247 according to the checks below.
249 It looks for filled-in values in the order of, local object (self),
250 followed by cread (read values), and finally default values.
254 default configuration
256 list of category/variable tuples to validate in these configurations
260 dict of values for the category/variables passed in
261 If an exception is found, ConfigurationException is raised.
265 validated_variables = {}
266 for category_id, variable_id in verify_variables.iteritems():
267 category_id = category_id.lower()
268 variable_id = variable_id.lower()
269 variable_value = None
270 sources = (self, read, default)
271 for source in sources:
272 (category_value, variable_value) = source.get(category_id,variable_id)
273 if variable_value <> None:
274 entry = validated_variables.get(category_id,[])
275 entry.append(variable_value['value'])
276 validated_variables["%s_%s"%(category_id.upper(),variable_id.upper())]=entry
278 if variable_value == None:
279 raise ConfigurationException("Cannot find %s_%s)" % \
280 (category_id.upper(),
281 variable_id.upper()))
282 return validated_variables
284 def get(self, category_id, variable_id):
286 Get the specified variable in the specified category.
290 category_id = unique category identifier (e.g., 'plc_www')
291 variable_id = unique variable identifier (e.g., 'port')
295 variable = { 'id': "variable_identifier",
296 'type': "variable_type",
297 'value': "variable_value",
298 'name': "Variable name",
299 'description': "Variable description" }
302 if self._variables.has_key(category_id.lower()):
303 (category, variables) = self._variables[category_id]
304 if variables.has_key(variable_id.lower()):
305 variable = variables[variable_id]
312 return (category, variable)
315 def delete(self, category_id, variable_id):
317 Delete the specified variable from the specified category. If
318 variable_id is None, deletes all variables from the specified
319 category as well as the category itself.
323 category_id = unique category identifier (e.g., 'plc_www')
324 variable_id = unique variable identifier (e.g., 'port')
327 if self._variables.has_key(category_id.lower()):
328 (category, variables) = self._variables[category_id]
329 if variable_id is None:
330 category['element'].parentNode.removeChild(category['element'])
331 del self._variables[category_id]
332 elif variables.has_key(variable_id.lower()):
333 variable = variables[variable_id]
334 variable['element'].parentNode.removeChild(variable['element'])
335 del variables[variable_id]
338 def set(self, category, variable):
340 Add and/or update the specified variable. The 'id' fields are
341 mandatory. If a field is not specified and the category and/or
342 variable already exists, the field will not be updated. If
343 'variable' is None, only adds and/or updates the specified
348 category = { 'id': "category_identifier",
349 'name': "Category name",
350 'description': "Category description" }
352 variable = { 'id': "variable_identifier",
353 'type': "variable_type",
354 'value': "variable_value",
355 'name': "Variable name",
356 'description': "Variable description" }
359 if not category.has_key('id') or type(category['id']) not in types.StringTypes:
362 category_id = category['id'].lower()
364 if self._variables.has_key(category_id):
366 (old_category, variables) = self._variables[category_id]
368 # Merge category attributes
369 for tag in ['name', 'description']:
370 if category.has_key(tag):
371 old_category[tag] = category[tag]
372 self._set_text_of_child(old_category['element'], tag, category[tag])
374 category_element = old_category['element']
377 category_element = self._dom.createElement('category')
378 category_element.setAttribute('id', category_id)
379 for tag in ['name', 'description']:
380 if category.has_key(tag):
381 self._set_text_of_child(category_element, tag, category[tag])
383 if self._dom.documentElement.getElementsByTagName('variables'):
384 variables_element = self._dom.documentElement.getElementsByTagName('variables')[0]
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 not variable.has_key('id') or type(variable['id']) not in types.StringTypes:
398 variable_id = variable['id'].lower()
400 if variables.has_key(variable_id):
402 old_variable = variables[variable_id]
404 # Merge variable attributes
405 for attribute in ['type']:
406 if variable.has_key(attribute):
407 old_variable[attribute] = variable[attribute]
408 old_variable['element'].setAttribute(attribute, variable[attribute])
409 for tag in ['name', 'value', 'description']:
410 if variable.has_key(tag):
411 old_variable[tag] = variable[tag]
412 self._set_text_of_child(old_variable['element'], tag, variable[tag])
415 variable_element = self._dom.createElement('variable')
416 variable_element.setAttribute('id', variable_id)
417 for attribute in ['type']:
418 if variable.has_key(attribute):
419 variable_element.setAttribute(attribute, variable[attribute])
420 for tag in ['name', 'value', 'description']:
421 if variable.has_key(tag):
422 self._set_text_of_child(variable_element, tag, variable[tag])
424 if category_element.getElementsByTagName('variablelist'):
425 variablelist_element = category_element.getElementsByTagName('variablelist')[0]
427 variablelist_element = self._dom.createElement('variablelist')
428 category_element.appendChild(variablelist_element)
429 variablelist_element.appendChild(variable_element)
432 variable['element'] = variable_element
433 variables[variable_id] = variable
436 def locate_varname (self, varname):
438 Locates category and variable from a variable's (shell) name
441 (variable, category) when found
442 (None, None) otherwise
445 for (category_id, (category, variables)) in self._variables.iteritems():
446 for variable in variables.values():
447 (id, name, value, comments) = self._sanitize_variable(category_id, variable)
449 return (category,variable)
452 def get_package(self, group_id, package_name):
454 Get the specified package in the specified package group.
458 group_id - unique group id (e.g., 'plc')
459 package_name - unique package name (e.g., 'postgresql')
463 package = { 'name': "package_name",
464 'type': "mandatory|optional" }
467 if self._packages.has_key(group_id.lower()):
468 (group, packages) = self._packages[group_id]
469 if packages.has_key(package_name):
470 package = packages[package_name]
477 return (group, package)
480 def delete_package(self, group_id, package_name):
482 Deletes the specified variable from the specified category. If
483 variable_id is None, deletes all variables from the specified
484 category as well as the category itself.
488 group_id - unique group id (e.g., 'plc')
489 package_name - unique package name (e.g., 'postgresql')
492 if self._packages.has_key(group_id):
493 (group, packages) = self._packages[group_id]
494 if package_name is None:
495 group['element'].parentNode.removeChild(group['element'])
496 del self._packages[group_id]
497 elif packages.has_key(package_name.lower()):
498 package = packages[package_name]
499 package['element'].parentNode.removeChild(package['element'])
500 del packages[package_name]
503 def add_package(self, group, package):
505 Add and/or update the specified package. The 'id' and 'name'
506 fields are mandatory. If a field is not specified and the
507 package or group already exists, the field will not be
508 updated. If package is None, only adds/or updates the
513 group = { 'id': "group_identifier",
514 'name': "Group name",
515 'default': "true|false",
516 'description': "Group description",
517 'uservisible': "true|false" }
519 package = { 'name': "package_name",
520 'type': "mandatory|optional" }
523 if not group.has_key('id'):
526 group_id = group['id']
528 if self._packages.has_key(group_id):
530 (old_group, packages) = self._packages[group_id]
532 # Merge group attributes
533 for tag in ['id', 'name', 'default', 'description', 'uservisible']:
534 if group.has_key(tag):
535 old_group[tag] = group[tag]
536 self._set_text_of_child(old_group['element'], tag, group[tag])
538 group_element = old_group['element']
541 group_element = self._dom.createElement('group')
542 for tag in ['id', 'name', 'default', 'description', 'uservisible']:
543 if group.has_key(tag):
544 self._set_text_of_child(group_element, tag, group[tag])
546 if self._dom.documentElement.getElementsByTagName('comps'):
547 comps_element = self._dom.documentElement.getElementsByTagName('comps')[0]
549 comps_element = self._dom.createElement('comps')
550 self._dom.documentElement.appendChild(comps_element)
551 comps_element.appendChild(group_element)
554 group['element'] = group_element
556 self._packages[group_id] = (group, packages)
558 if package is None or not package.has_key('name'):
561 package_name = package['name']
562 if packages.has_key(package_name):
564 old_package = packages[package_name]
566 # Merge variable attributes
567 for attribute in ['type']:
568 if package.has_key(attribute):
569 old_package[attribute] = package[attribute]
570 old_package['element'].setAttribute(attribute, package[attribute])
573 packagereq_element = TrimTextElement('packagereq')
574 self._set_text(packagereq_element, package_name)
575 for attribute in ['type']:
576 if package.has_key(attribute):
577 packagereq_element.setAttribute(attribute, package[attribute])
579 if group_element.getElementsByTagName('packagelist'):
580 packagelist_element = group_element.getElementsByTagName('packagelist')[0]
582 packagelist_element = self._dom.createElement('packagelist')
583 group_element.appendChild(packagelist_element)
584 packagelist_element.appendChild(packagereq_element)
587 package['element'] = packagereq_element
588 packages[package_name] = package
593 Return all variables.
597 variables = { 'category_id': (category, variablelist) }
599 category = { 'id': "category_identifier",
600 'name': "Category name",
601 'description': "Category description" }
603 variablelist = { 'variable_id': variable }
605 variable = { 'id': "variable_identifier",
606 'type': "variable_type",
607 'value': "variable_value",
608 'name': "Variable name",
609 'description': "Variable description" }
612 return self._variables
621 packages = { 'group_id': (group, packagelist) }
623 group = { 'id': "group_identifier",
624 'name': "Group name",
625 'default': "true|false",
626 'description': "Group description",
627 'uservisible': "true|false" }
629 packagelist = { 'package_name': package }
631 package = { 'name': "package_name",
632 'type': "mandatory|optional" }
635 return self._packages
638 def _sanitize_variable(self, category_id, variable):
639 assert variable.has_key('id')
640 # Prepend variable name with category label
641 id = category_id + "_" + variable['id']
645 if variable.has_key('type'):
646 type = variable['type']
650 if variable.has_key('name'):
651 name = variable['name']
655 if variable.has_key('value') and variable['value'] is not None:
656 value = variable['value']
657 if type == "int" or type == "double":
658 # bash, Python, and PHP do not require that numbers be quoted
660 elif type == "boolean":
661 # bash, Python, and PHP can all agree on 0 and 1
667 # bash, Python, and PHP all support strong single quoting
668 value = "'" + value.replace("'", "\\'") + "'"
672 if variable.has_key('description') and variable['description'] is not None:
673 description = variable['description']
674 # Collapse consecutive whitespace
675 description = re.sub(r'\s+', ' ', description)
676 # Wrap comments at 70 columns
677 wrapper = textwrap.TextWrapper()
678 comments = wrapper.wrap(description)
682 return (id, name, value, comments)
687 DO NOT EDIT. This file was automatically generated at
691 """ % (time.asctime(), os.linesep.join(self._files))
693 # Get rid of the surrounding newlines
694 return header.strip().split(os.linesep)
697 def output_shell(self, show_comments = True, encoding = "utf-8"):
699 Return variables as a shell script.
702 buf = codecs.lookup(encoding)[3](StringIO())
703 buf.writelines(["# " + line + os.linesep for line in self._header()])
705 for (category_id, (category, variables)) in self._variables.iteritems():
706 for variable in variables.values():
707 (id, name, value, comments) = self._sanitize_variable(category_id, variable)
709 buf.write(os.linesep)
711 buf.write("# " + name + os.linesep)
712 if comments is not None:
713 buf.writelines(["# " + line + os.linesep for line in comments])
714 # bash does not have the concept of NULL
715 if value is not None:
716 buf.write(id + "=" + value + os.linesep)
718 return buf.getvalue()
721 def output_php(self, encoding = "utf-8"):
723 Return variables as a PHP script.
726 buf = codecs.lookup(encoding)[3](StringIO())
727 buf.write("<?php" + os.linesep)
728 buf.writelines(["// " + line + os.linesep for line in self._header()])
730 for (category_id, (category, variables)) in self._variables.iteritems():
731 for variable in variables.values():
732 (id, name, value, comments) = self._sanitize_variable(category_id, variable)
733 buf.write(os.linesep)
735 buf.write("// " + name + os.linesep)
736 if comments is not None:
737 buf.writelines(["// " + line + os.linesep for line in comments])
740 buf.write("define('%s', %s);" % (id, value) + os.linesep)
742 buf.write("?>" + os.linesep)
744 return buf.getvalue()
747 def output_xml(self, encoding = "utf-8"):
749 Return variables in original XML format.
752 buf = codecs.lookup(encoding)[3](StringIO())
753 self._dom.writexml(buf, addindent = " ", indent = "", newl = "\n", encoding = encoding)
755 return buf.getvalue()
758 def output_variables(self, encoding = "utf-8"):
760 Return list of all variable names.
763 buf = codecs.lookup(encoding)[3](StringIO())
765 for (category_id, (category, variables)) in self._variables.iteritems():
766 for variable in variables.values():
767 (id, name, value, comments) = self._sanitize_variable(category_id, variable)
768 buf.write(id + os.linesep)
770 return buf.getvalue()
773 def output_packages(self, encoding = "utf-8"):
775 Return list of all packages.
778 buf = codecs.lookup(encoding)[3](StringIO())
780 for (group, packages) in self._packages.values():
781 buf.write(os.linesep.join(packages.keys()))
784 buf.write(os.linesep)
786 return buf.getvalue()
789 def output_groups(self, encoding = "utf-8"):
791 Return list of all package group names.
794 buf = codecs.lookup(encoding)[3](StringIO())
796 for (group, packages) in self._packages.values():
797 buf.write(group['name'] + os.linesep)
799 return buf.getvalue()
802 def output_comps(self, encoding = "utf-8"):
804 Return <comps> section of configuration.
807 if self._dom is None or \
808 not self._dom.getElementsByTagName("comps"):
810 comps = self._dom.getElementsByTagName("comps")[0]
812 impl = xml.dom.minidom.getDOMImplementation()
813 doc = impl.createDocument(None, "comps", None)
815 buf = codecs.lookup(encoding)[3](StringIO())
817 # Pop it off the DOM temporarily
818 parent = comps.parentNode
819 parent.removeChild(comps)
821 doc.replaceChild(comps, doc.documentElement)
822 doc.writexml(buf, encoding = encoding)
825 parent.appendChild(comps)
827 return buf.getvalue()
829 def validate_type(self, variable_type, value):
831 # ideally we should use the "validate_*" methods in PLCAPI or
832 # even declare some checks along with the default
833 # configuration (using RELAX NG?) but this shall work for now.
834 def ip_validator(val):
837 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)
859 # xml.dom.minidom.Text.writexml adds surrounding whitespace to textual
860 # data when pretty-printing. Override this behavior.
861 class TrimText(xml.dom.minidom.Text):
862 def writexml(self, writer, indent="", addindent="", newl=""):
863 xml.dom.minidom.Text.writexml(self, writer, "", "", "")
866 class TrimTextElement(xml.dom.minidom.Element):
867 def writexml(self, writer, indent="", addindent="", newl=""):
869 xml.dom.minidom.Element.writexml(self, writer, "", "", "")
877 release_rev = "$Revision$"
878 release_url = "$URL$"
885 def noop_validator(validated_variables):
889 # historically we could also configure the devel pkg....
890 def init_configuration ():
891 global g_configuration
892 global usual_variables, config_dir, service
894 usual_variables=g_configuration["usual_variables"]
895 config_dir=g_configuration["config_dir"]
896 service=g_configuration["service"]
898 global def_default_config, def_site_config, def_consolidated_config
899 def_default_config= "%s/default_config.xml" % config_dir
900 def_site_config = "%s/configs/site.xml" % config_dir
901 def_consolidated_config = "%s/%s_config.xml" % (config_dir, service)
903 global mainloop_usage
904 mainloop_usage= """Available commands:
905 Uppercase versions give variables comments, when available
906 u/U\t\t\tEdit usual variables
908 r\t\t\tRestart %(service)s service
909 R\t\t\tReload %(service)s service (rebuild config files for sh, python....)
910 q\t\t\tQuit (without saving)
913 l/L [<cat>|<var>]\tShow Locally modified variables/values
914 s/S [<cat>|<var>]\tShow variables/values (all, in category, single)
915 e/E [<cat>|<var>]\tEdit variables (all, in category, single)
917 c\t\t\tList categories
918 v/V [<cat>|<var>]\tList Variables (all, in category, single)
920 Typical usage involves: u, [l,] w, r, q
924 command_usage="%prog [options] [default-xml [site-xml [consolidated-xml]]]"
925 init_configuration ()
927 \t default-xml defaults to %s
928 \t site-xml defaults to %s
929 \t consolidated-xml defaults to %s""" % (def_default_config,def_site_config, def_consolidated_config)
933 variable_usage= """Edit Commands :
934 #\tShow variable comments
935 .\tStops prompting, return to mainloop
936 /\tCleans any site-defined value, reverts to default
937 =\tShows default value
938 >\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']
947 def get_type (config, category_id, variable_id):
948 (category, variable) = config.get (category_id, variable_id)
949 return variable['type']
951 def get_current_value (cread, cwrite, category_id, variable_id):
952 # the value stored in cwrite, if present, is the one we want
954 result=get_value (cwrite,category_id,variable_id)
956 result=get_value (cread,category_id,variable_id)
959 # refrain from using plc_config's _sanitize
960 def get_varname (config, category_id, variable_id):
961 (category, variable) = config.get (category_id, variable_id)
962 return (category_id+"_"+variable['id']).upper()
964 # could not avoid using _sanitize here..
965 def get_name_comments (config, cid, vid):
967 (category, variable) = config.get (cid, vid)
968 (id, name, value, comments) = config._sanitize_variable (cid,variable)
969 return (name,comments)
973 def print_name_comments (config, cid, vid):
974 (name,comments)=get_name_comments(config,cid,vid)
976 print "### %s" % name
978 for line in comments:
981 print "!!! No comment associated to %s_%s" % (cid,vid)
984 def list_categories (config):
986 for (category_id, (category, variables)) in config.variables().iteritems():
987 result += [category_id]
990 def print_categories (config):
991 print "Known categories"
992 for cid in list_categories(config):
993 print "%s" % (cid.upper())
996 def list_category (config, cid):
998 for (category_id, (category, variables)) in config.variables().iteritems():
999 if (cid == category_id):
1000 for variable in variables.values():
1001 result += ["%s_%s" %(cid,variable['id'])]
1004 def print_category (config, cid, show_comments=True):
1007 vids=list_category(config,cid)
1008 if (len(vids) == 0):
1009 print "%s : no such category"%CID
1011 print "Category %s contains" %(CID)
1015 ####################
1016 def consolidate (default_config, site_config, consolidated_config):
1019 conso = PLCConfiguration (default_config)
1020 conso.load (site_config)
1021 conso.save (consolidated_config)
1022 except Exception, inst:
1023 print "Could not consolidate, %s" % (str(inst))
1025 print ("Merged\n\t%s\nand\t%s\ninto\t%s"%(default_config,site_config,
1026 consolidated_config))
1028 def reload_service ():
1030 os.system("set -x ; service %s reload" % service)
1032 ####################
1033 def restart_service ():
1035 print ("==================== Stopping %s" % service)
1036 os.system("service %s stop" % service)
1037 print ("==================== Starting %s" % service)
1038 os.system("service %s start" % service)
1040 ####################
1041 def prompt_variable (cdef, cread, cwrite, category, variable,
1042 show_comments, support_next=False):
1044 assert category.has_key('id')
1045 assert variable.has_key('id')
1047 category_id = category ['id']
1048 variable_id = variable['id']
1051 default_value = get_value(cdef,category_id,variable_id)
1052 variable_type = get_type(cdef,category_id,variable_id)
1053 current_value = get_current_value(cread,cwrite,category_id, variable_id)
1054 varname = get_varname (cread,category_id, variable_id)
1057 print_name_comments (cdef, category_id, variable_id)
1058 prompt = "== %s : [%s] " % (varname,current_value)
1060 answer = raw_input(prompt).strip()
1062 raise Exception ('BailOut')
1063 except KeyboardInterrupt:
1065 raise Exception ('BailOut')
1068 if (answer == "") or (answer == current_value):
1070 elif (answer == "."):
1071 raise Exception ('BailOut')
1072 elif (answer == "#"):
1073 print_name_comments(cread,category_id,variable_id)
1074 elif (answer == "?"):
1075 print variable_usage.strip()
1076 elif (answer == "="):
1077 print ("%s defaults to %s" %(varname,default_value))
1078 # revert to default : remove from cwrite (i.e. site-config)
1079 elif (answer == "/"):
1080 cwrite.delete(category_id,variable_id)
1081 print ("%s reverted to %s" %(varname,default_value))
1083 elif (answer == ">"):
1085 raise Exception ('NextCategory')
1087 print "No support for next category"
1089 if cdef.validate_type(variable_type, answer):
1090 variable['value'] = answer
1091 cwrite.set(category,variable)
1094 print "Not a valid value"
1096 def prompt_variables_all (cdef, cread, cwrite, show_comments):
1098 for (category_id, (category, variables)) in cread.variables().iteritems():
1099 print ("========== Category = %s" % category_id.upper())
1100 for variable in variables.values():
1102 newvar = prompt_variable (cdef, cread, cwrite, category, variable,
1103 show_comments, True)
1104 except Exception, inst:
1105 if (str(inst) == 'NextCategory'): break
1108 except Exception, inst:
1109 if (str(inst) == 'BailOut'): return
1112 def prompt_variables_category (cdef, cread, cwrite, cid, show_comments):
1116 print ("========== Category = %s" % CID)
1117 for vid in list_category(cdef,cid):
1118 (category,variable) = cdef.locate_varname(vid.upper())
1119 newvar = prompt_variable (cdef, cread, cwrite, category, variable,
1120 show_comments, False)
1121 except Exception, inst:
1122 if (str(inst) == 'BailOut'): return
1125 ####################
1126 def show_variable (cdef, cread, cwrite,
1127 category, variable,show_value,show_comments):
1128 assert category.has_key('id')
1129 assert variable.has_key('id')
1131 category_id = category ['id']
1132 variable_id = variable['id']
1134 default_value = get_value(cdef,category_id,variable_id)
1135 current_value = get_current_value(cread,cwrite,category_id,variable_id)
1136 varname = get_varname (cread,category_id, variable_id)
1138 print_name_comments (cdef, category_id, variable_id)
1140 print "%s = %s" % (varname,current_value)
1142 print "%s" % (varname)
1144 def show_variables_all (cdef, cread, cwrite, show_value, show_comments):
1145 for (category_id, (category, variables)) in cread.variables().iteritems():
1146 print ("========== Category = %s" % category_id.upper())
1147 for variable in variables.values():
1148 show_variable (cdef, cread, cwrite,
1149 category, variable,show_value,show_comments)
1151 def show_variables_category (cdef, cread, cwrite, cid, show_value,show_comments):
1154 print ("========== Category = %s" % CID)
1155 for vid in list_category(cdef,cid):
1156 (category,variable) = cdef.locate_varname(vid.upper())
1157 show_variable (cdef, cread, cwrite, category, variable,
1158 show_value,show_comments)
1160 ####################
1161 re_mainloop_0arg="^(?P<command>[uUwrRqlLsSeEcvVhH\?])[ \t]*$"
1162 re_mainloop_1arg="^(?P<command>[sSeEvV])[ \t]+(?P<arg>\w+)$"
1163 matcher_mainloop_0arg=re.compile(re_mainloop_0arg)
1164 matcher_mainloop_1arg=re.compile(re_mainloop_1arg)
1166 def mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config):
1170 answer = raw_input("Enter command (u for usual changes, w to save, ? for help) ").strip()
1173 except KeyboardInterrupt:
1177 if (answer == "") or (answer in "?hH"):
1178 print mainloop_usage
1180 groups_parse = matcher_mainloop_0arg.match(answer)
1183 command = groups_parse.group('command')
1186 groups_parse = matcher_mainloop_1arg.match(answer)
1188 command = groups_parse.group('command')
1189 arg=groups_parse.group('arg')
1191 print ("Unknown command >%s< -- use h for help" % answer)
1194 show_comments=command.isupper()
1200 variables=list_category (cdef,arg)
1202 # category_id as the category name
1203 # variables as the list of variable names
1207 (category,variable)=cdef.locate_varname(arg)
1209 # category/variable as output by locate_varname
1212 print "%s: no such category or variable" % arg
1216 # todo check confirmation
1218 elif command == "w":
1220 # Confirm that various constraints are met before saving file.
1221 validate_variables = g_configuration.get('validate_variables',{})
1222 validated_variables = cwrite.verify(cdef, cread, validate_variables)
1223 validator = g_configuration.get('validator',noop_validator)
1224 validator(validated_variables)
1225 cwrite.save(site_config)
1226 except ConfigurationException, e:
1227 print "Save failed due to a configuration exception: %s" % e
1230 print traceback.print_exc()
1231 print ("Could not save -- fix write access on %s" % site_config)
1233 print ("Wrote %s" % site_config)
1234 consolidate(default_config, site_config, consolidated_config)
1235 print ("You might want to type 'r' (restart %s), 'R' (reload %s) or 'q' (quit)" % \
1237 elif command in "uU":
1238 global usual_variables
1240 for varname in usual_variables:
1241 (category,variable) = cdef.locate_varname(varname)
1242 if not (category is None and variable is None):
1243 prompt_variable(cdef, cread, cwrite, category, variable, False)
1244 except Exception, inst:
1245 if (str(inst) != 'BailOut'):
1247 elif command == "r":
1249 elif command == "R":
1251 elif command == "c":
1252 print_categories(cread)
1253 elif command in "eE":
1255 prompt_variables_all(cdef, cread, cwrite,show_comments)
1256 elif mode == 'CATEGORY':
1257 prompt_variables_category(cdef,cread,cwrite,category_id,show_comments)
1258 elif mode == 'VARIABLE':
1260 prompt_variable (cdef,cread,cwrite,category,variable,
1261 show_comments,False)
1262 except Exception, inst:
1263 if str(inst) != 'BailOut':
1265 elif command in "vVsSlL":
1266 show_value=(command in "sSlL")
1267 (c1,c2,c3) = (cdef, cread, cwrite)
1269 (c1,c2,c3) = (cwrite,cwrite,cwrite)
1271 show_variables_all(c1,c2,c3,show_value,show_comments)
1272 elif mode == 'CATEGORY':
1273 show_variables_category(c1,c2,c3,category_id,show_value,show_comments)
1274 elif mode == 'VARIABLE':
1275 show_variable (c1,c2,c3,category,variable,show_value,show_comments)
1277 print ("Unknown command >%s< -- use h for help" % answer)
1279 ####################
1280 # creates directory for file if not yet existing
1281 def check_dir (config_file):
1282 dirname = os.path.dirname (config_file)
1283 if (not os.path.exists (dirname)):
1285 os.makedirs(dirname,0755)
1287 print "Cannot create dir %s due to %s - exiting" % (dirname,e)
1290 if (not os.path.exists (dirname)):
1291 print "Cannot create dir %s - exiting" % dirname
1294 print "Created directory %s" % dirname
1296 ####################
1297 def optParserSetup(configuration):
1298 parser = OptionParser(usage=usage(), version="%prog " + release_rev + release_url )
1299 parser.set_defaults(config_dir=configuration['config_dir'],
1300 service=configuration['service'],
1301 usual_variables=configuration['usual_variables'])
1302 parser.add_option("","--configdir",dest="config_dir",help="specify configuration directory")
1303 parser.add_option("","--service",dest="service",help="specify /etc/init.d style service name")
1304 parser.add_option("","--usual_variable",dest="usual_variables",action="append", help="add a usual variable")
1307 def main(command,argv,configuration):
1308 global g_configuration
1309 g_configuration=configuration
1311 parser = optParserSetup(configuration)
1312 (config,args) = parser.parse_args()
1314 parser.error("too many arguments")
1316 configuration['service']=config.service
1317 configuration['usual_variables']=config.usual_variables
1318 configuration['config_dir']=config.config_dir
1319 # add in new usual_variables defined on the command line
1320 for usual_variable in config.usual_variables:
1321 if usual_variable not in configuration['usual_variables']:
1322 configuration['usual_variables'].append(usual_variable)
1324 # intialize configuration
1325 init_configuration()
1327 (default_config,site_config,consolidated_config) = (def_default_config, def_site_config, def_consolidated_config)
1329 default_config=args[0]
1333 consolidated_config=args[2]
1335 for c in (default_config,site_config,consolidated_config):
1339 # the default settings only - read only
1340 cdef = PLCConfiguration(default_config)
1342 # in effect : default settings + local settings - read only
1343 cread = PLCConfiguration(default_config)
1345 except ConfigurationException, e:
1346 print ("Error %s in default config file %s" %(e,default_config))
1349 print traceback.print_exc()
1350 print ("default config files %s not found, is myplc installed ?" % default_config)
1354 # local settings only, will be modified & saved
1355 cwrite=PLCConfiguration()
1358 cread.load(site_config)
1359 cwrite.load(site_config)
1361 cwrite = PLCConfiguration()
1363 mainloop (cdef, cread, cwrite, default_config, site_config, consolidated_config)
1366 if __name__ == '__main__':
1368 if len(sys.argv) > 1 and sys.argv[1] in ['build', 'install', 'uninstall']:
1369 from distutils.core import setup
1370 setup(py_modules=["plc_config"])