-#!/usr/bin/python
+#!/usr/bin/env python3
#
# Merge PlanetLab Central (PLC) configuration files into a variety of
# output formats. These files represent the global configuration for a
# Mark Huang <mlhuang@cs.princeton.edu>
# Copyright (C) 2006 The Trustees of Princeton University
#
-# $Id$
-#
-import xml.dom.minidom
-from StringIO import StringIO
-import time
+import os
import re
+import sys
import textwrap
-import codecs
-import os
-import types
+import time
+import traceback
+import xml.dom.minidom
+from xml.parsers.expat import ExpatError
+from io import StringIO
+from optparse import OptionParser
+
+class ConfigurationException(Exception):
+ pass
-class ConfigurationException(Exception): pass
class PLCConfiguration:
"""
You may also save() the configuration. If a file path or object is
not specified, the configuration will be written to the file path
or object that was first loaded.
-
+
plc.save()
plc.save("/etc/planetlab/plc_config.xml")
"""
- def __init__(self, file = None):
+ def __init__(self, file=None):
impl = xml.dom.minidom.getDOMImplementation()
self._dom = impl.createDocument(None, "configuration", None)
self._variables = {}
if file is not None:
self.load(file)
-
def _get_text(self, node):
"""
Get the text of a text node.
return None
-
def _get_text_of_child(self, parent, name):
"""
Get the text of a (direct) child text node.
return None
-
def _set_text(self, node, data):
"""
Set the text of a text node.
text.data = data
node.appendChild(text)
-
def _set_text_of_child(self, parent, name, data):
"""
Set the text of a (direct) child text node.
self._set_text(child, data)
parent.appendChild(child)
-
def _category_element_to_dict(self, category_element):
"""
Turn a <category> element into a dictionary of its attributes
for node in category_element.childNodes:
if node.nodeType == node.ELEMENT_NODE and \
node.tagName in ['name', 'description']:
- category[node.tagName] = self._get_text_of_child(category_element, node.tagName)
+ category[node.tagName] = self._get_text_of_child(
+ category_element, node.tagName)
category['element'] = category_element
return category
-
def _variable_element_to_dict(self, variable_element):
"""
Turn a <variable> element into a dictionary of its attributes
for node in variable_element.childNodes:
if node.nodeType == node.ELEMENT_NODE and \
node.tagName in ['name', 'value', 'description']:
- variable[node.tagName] = self._get_text_of_child(variable_element, node.tagName)
+ variable[node.tagName] = self._get_text_of_child(
+ variable_element, node.tagName)
variable['element'] = variable_element
return variable
-
def _group_element_to_dict(self, group_element):
"""
Turn a <group> element into a dictionary of its attributes
for node in group_element.childNodes:
if node.nodeType == node.ELEMENT_NODE and \
node.tagName in ['id', 'name', 'default', 'description', 'uservisible']:
- group[node.tagName] = self._get_text_of_child(group_element, node.tagName)
+ group[node.tagName] = self._get_text_of_child(
+ group_element, node.tagName)
group['element'] = group_element
return group
-
def _packagereq_element_to_dict(self, packagereq_element):
"""
Turns a <packagereq> element into a dictionary of its attributes
return package
-
- def load(self, file = "/etc/planetlab/plc_config.xml"):
+ def load(self, file="/etc/planetlab/plc_config.xml"):
"""
Merge file into configuration store.
"""
- dom = xml.dom.minidom.parse(file)
- if type(file) in types.StringTypes:
+ try:
+ dom = xml.dom.minidom.parse(file)
+ except ExpatError as e:
+ raise ConfigurationException(e)
+
+ if isinstance(file, str):
self._files.append(os.path.abspath(file))
# Parse <variables> section
for variablelist_element in category_element.getElementsByTagName('variablelist'):
for variable_element in variablelist_element.getElementsByTagName('variable'):
- variable = self._variable_element_to_dict(variable_element)
+ variable = self._variable_element_to_dict(
+ variable_element)
self.set(category, variable)
# Parse <comps> section
self.add_package(group, None)
for packagereq_element in group_element.getElementsByTagName('packagereq'):
- package = self._packagereq_element_to_dict(packagereq_element)
+ package = self._packagereq_element_to_dict(
+ packagereq_element)
self.add_package(group, package)
-
- def save(self, file = None):
+ def save(self, file=None):
"""
Write configuration store to file.
"""
else:
file = "/etc/planetlab/plc_config.xml"
- if type(file) in types.StringTypes:
+ if isinstance(file, str):
fileobj = open(file, 'w')
else:
fileobj = file
fileobj.close()
- def verify(self, default, read):
- """ Confirm that the existing configuration is consistent according to
- the checks below.
+ def verify(self, default, read, verify_variables={}):
+ """ Confirm that the existing configuration is consistent
+ according to the checks below.
It looks for filled-in values in the order of, local object (self),
followed by cread (read values), and finally default values.
- Arguments:
+ Arguments:
- None
+ default configuration
+ site configuration
+ list of category/variable tuples to validate in these configurations
Returns:
- None. If an exception is found, ConfigurationException is raised.
+ dict of values for the category/variables passed in
+ If an exception is found, ConfigurationException is raised.
"""
- maint_user = self.get('plc_api', 'maintenance_user')
- if maint_user == (None, None):
- maint_user = read.get('plc_api', 'maintenance_user')
- if maint_user == (None, None):
- maint_user = default.get('plc_api', 'maintenance_user')
-
- root_user = self.get('plc', 'root_user')
- if root_user == (None, None):
- root_user = read.get('plc', 'root_user')
- if root_user == (None, None):
- root_user = default.get('plc', 'root_user')
-
- muser= maint_user[1]['value']
- ruser= root_user[1]['value']
-
- if muser == ruser:
- raise ConfigurationException("The Maintenance Account email address cannot be the same as the Root User email address")
- return
-
+ validated_variables = {}
+ for category_id, variable_id in verify_variables.items():
+ category_id = category_id.lower()
+ variable_id = variable_id.lower()
+ variable_value = None
+ sources = (self, read, default)
+ for source in sources:
+ (category_value, variable_value) = source.get(
+ category_id, variable_id)
+ if variable_value != None:
+ entry = validated_variables.get(category_id, [])
+ entry.append(variable_value['value'])
+ validated_variables["%s_%s" % (
+ category_id.upper(), variable_id.upper())] = entry
+ break
+ if variable_value == None:
+ raise ConfigurationException("Cannot find %s_%s)" %
+ (category_id.upper(),
+ variable_id.upper()))
+ return validated_variables
def get(self, category_id, variable_id):
"""
'description': "Variable description" }
"""
- if self._variables.has_key(category_id.lower()):
+ if category_id.lower() in self._variables:
(category, variables) = self._variables[category_id]
- if variables.has_key(variable_id.lower()):
+ if variable_id.lower() in variables:
variable = variables[variable_id]
else:
variable = None
return (category, variable)
-
def delete(self, category_id, variable_id):
"""
Delete the specified variable from the specified category. If
variable_id = unique variable identifier (e.g., 'port')
"""
- if self._variables.has_key(category_id.lower()):
+ if category_id.lower() in self._variables:
(category, variables) = self._variables[category_id]
if variable_id is None:
category['element'].parentNode.removeChild(category['element'])
del self._variables[category_id]
- elif variables.has_key(variable_id.lower()):
+ elif variable_id.lower() in variables:
variable = variables[variable_id]
variable['element'].parentNode.removeChild(variable['element'])
del variables[variable_id]
-
def set(self, category, variable):
"""
Add and/or update the specified variable. The 'id' fields are
'description': "Variable description" }
"""
- if not category.has_key('id') or type(category['id']) not in types.StringTypes:
+ if ('id' not in category
+ or not isinstance(category['id'], str)):
return
-
+
category_id = category['id'].lower()
- if self._variables.has_key(category_id):
+ if category_id in self._variables:
# Existing category
(old_category, variables) = self._variables[category_id]
# Merge category attributes
for tag in ['name', 'description']:
- if category.has_key(tag):
+ if tag in category:
old_category[tag] = category[tag]
- self._set_text_of_child(old_category['element'], tag, category[tag])
+ self._set_text_of_child(
+ old_category['element'], tag, category[tag])
category_element = old_category['element']
else:
category_element = self._dom.createElement('category')
category_element.setAttribute('id', category_id)
for tag in ['name', 'description']:
- if category.has_key(tag):
- self._set_text_of_child(category_element, tag, category[tag])
+ if tag in category:
+ self._set_text_of_child(
+ category_element, tag, category[tag])
if self._dom.documentElement.getElementsByTagName('variables'):
- variables_element = self._dom.documentElement.getElementsByTagName('variables')[0]
+ variables_element = self._dom.documentElement.getElementsByTagName('variables')[
+ 0]
else:
variables_element = self._dom.createElement('variables')
self._dom.documentElement.appendChild(variables_element)
variables = {}
self._variables[category_id] = (category, variables)
- if variable is None or not variable.has_key('id') or type(variable['id']) not in types.StringTypes:
+ if (variable is None or 'id' not in variable
+ or not isinstance(variable['id'], str)):
return
variable_id = variable['id'].lower()
- if variables.has_key(variable_id):
+ if variable_id in variables:
# Existing variable
old_variable = variables[variable_id]
# Merge variable attributes
for attribute in ['type']:
- if variable.has_key(attribute):
+ if attribute in variable:
old_variable[attribute] = variable[attribute]
- old_variable['element'].setAttribute(attribute, variable[attribute])
+ old_variable['element'].setAttribute(
+ attribute, variable[attribute])
for tag in ['name', 'value', 'description']:
- if variable.has_key(tag):
+ if tag in variable:
old_variable[tag] = variable[tag]
- self._set_text_of_child(old_variable['element'], tag, variable[tag])
+ self._set_text_of_child(
+ old_variable['element'], tag, variable[tag])
else:
# Merge into DOM
variable_element = self._dom.createElement('variable')
variable_element.setAttribute('id', variable_id)
for attribute in ['type']:
- if variable.has_key(attribute):
- variable_element.setAttribute(attribute, variable[attribute])
+ if attribute in variable:
+ variable_element.setAttribute(
+ attribute, variable[attribute])
for tag in ['name', 'value', 'description']:
- if variable.has_key(tag):
- self._set_text_of_child(variable_element, tag, variable[tag])
-
+ if tag in variable:
+ self._set_text_of_child(
+ variable_element, tag, variable[tag])
+
if category_element.getElementsByTagName('variablelist'):
- variablelist_element = category_element.getElementsByTagName('variablelist')[0]
+ variablelist_element = category_element.getElementsByTagName('variablelist')[
+ 0]
else:
variablelist_element = self._dom.createElement('variablelist')
category_element.appendChild(variablelist_element)
variable['element'] = variable_element
variables[variable_id] = variable
-
- def locate_varname (self, varname):
+ def locate_varname(self, varname):
"""
Locates category and variable from a variable's (shell) name
(variable, category) when found
(None, None) otherwise
"""
-
- for (category_id, (category, variables)) in self._variables.iteritems():
- for variable in variables.values():
- (id, name, value, comments) = self._sanitize_variable(category_id, variable)
+
+ for (category_id, (category, variables)) in self._variables.items():
+ for variable in list(variables.values()):
+ (id, name, value, comments) = self._sanitize_variable(
+ category_id, variable)
if (id == varname):
- return (category,variable)
- return (None,None)
+ return (category, variable)
+ return (None, None)
def get_package(self, group_id, package_name):
"""
'type': "mandatory|optional" }
"""
- if self._packages.has_key(group_id.lower()):
+ if group_id.lower() in self._packages:
(group, packages) = self._packages[group_id]
- if packages.has_key(package_name):
+ if package_name in packages:
package = packages[package_name]
else:
package = None
return (group, package)
-
def delete_package(self, group_id, package_name):
"""
Deletes the specified variable from the specified category. If
package_name - unique package name (e.g., 'postgresql')
"""
- if self._packages.has_key(group_id):
+ if group_id in self._packages:
(group, packages) = self._packages[group_id]
if package_name is None:
group['element'].parentNode.removeChild(group['element'])
del self._packages[group_id]
- elif packages.has_key(package_name.lower()):
+ elif package_name.lower() in packages:
package = packages[package_name]
package['element'].parentNode.removeChild(package['element'])
del packages[package_name]
-
def add_package(self, group, package):
"""
Add and/or update the specified package. The 'id' and 'name'
'type': "mandatory|optional" }
"""
- if not group.has_key('id'):
+ if 'id' not in group:
return
group_id = group['id']
- if self._packages.has_key(group_id):
+ if group_id in self._packages:
# Existing group
(old_group, packages) = self._packages[group_id]
# Merge group attributes
for tag in ['id', 'name', 'default', 'description', 'uservisible']:
- if group.has_key(tag):
+ if tag in group:
old_group[tag] = group[tag]
- self._set_text_of_child(old_group['element'], tag, group[tag])
+ self._set_text_of_child(
+ old_group['element'], tag, group[tag])
group_element = old_group['element']
else:
# Merge into DOM
group_element = self._dom.createElement('group')
for tag in ['id', 'name', 'default', 'description', 'uservisible']:
- if group.has_key(tag):
+ if tag in group:
self._set_text_of_child(group_element, tag, group[tag])
if self._dom.documentElement.getElementsByTagName('comps'):
- comps_element = self._dom.documentElement.getElementsByTagName('comps')[0]
+ comps_element = self._dom.documentElement.getElementsByTagName('comps')[
+ 0]
else:
comps_element = self._dom.createElement('comps')
self._dom.documentElement.appendChild(comps_element)
packages = {}
self._packages[group_id] = (group, packages)
- if package is None or not package.has_key('name'):
+ if package is None or 'name' not in package:
return
package_name = package['name']
- if packages.has_key(package_name):
+ if package_name in packages:
# Existing package
old_package = packages[package_name]
# Merge variable attributes
for attribute in ['type']:
- if package.has_key(attribute):
+ if attribute in package:
old_package[attribute] = package[attribute]
- old_package['element'].setAttribute(attribute, package[attribute])
+ old_package['element'].setAttribute(
+ attribute, package[attribute])
else:
# Merge into DOM
packagereq_element = TrimTextElement('packagereq')
self._set_text(packagereq_element, package_name)
for attribute in ['type']:
- if package.has_key(attribute):
- packagereq_element.setAttribute(attribute, package[attribute])
-
+ if attribute in package:
+ packagereq_element.setAttribute(
+ attribute, package[attribute])
+
if group_element.getElementsByTagName('packagelist'):
- packagelist_element = group_element.getElementsByTagName('packagelist')[0]
+ packagelist_element = group_element.getElementsByTagName('packagelist')[
+ 0]
else:
packagelist_element = self._dom.createElement('packagelist')
group_element.appendChild(packagelist_element)
package['element'] = packagereq_element
packages[package_name] = package
-
def variables(self):
"""
Return all variables.
return self._variables
-
def packages(self):
"""
Return all packages.
return self._packages
-
def _sanitize_variable(self, category_id, variable):
- assert variable.has_key('id')
+ assert 'id' in variable
# Prepend variable name with category label
id = category_id + "_" + variable['id']
# And uppercase it
id = id.upper()
- if variable.has_key('type'):
+ if 'type' in variable:
type = variable['type']
else:
type = None
- if variable.has_key('name'):
+ if 'name' in variable:
name = variable['name']
else:
name = None
- if variable.has_key('value') and variable['value'] is not None:
+ if 'value' in variable and variable['value'] is not None:
value = variable['value']
if type == "int" or type == "double":
# bash, Python, and PHP do not require that numbers be quoted
else:
value = None
- if variable.has_key('description') and variable['description'] is not None:
+ if 'description' in variable and variable['description'] is not None:
description = variable['description']
# Collapse consecutive whitespace
description = re.sub(r'\s+', ' ', description)
return (id, name, value, comments)
-
def _header(self):
header = """
DO NOT EDIT. This file was automatically generated at
# Get rid of the surrounding newlines
return header.strip().split(os.linesep)
-
- def output_shell(self, show_comments = True, encoding = "utf-8"):
+ def output_shell(self, show_comments=True):
"""
Return variables as a shell script.
"""
- buf = codecs.lookup(encoding)[3](StringIO())
+ buf = StringIO()
buf.writelines(["# " + line + os.linesep for line in self._header()])
- for (category_id, (category, variables)) in self._variables.iteritems():
- for variable in variables.values():
- (id, name, value, comments) = self._sanitize_variable(category_id, variable)
+ for (category_id, (category, variables)) in self._variables.items():
+ for variable in list(variables.values()):
+ (id, name, value, comments) = self._sanitize_variable(
+ category_id, variable)
if show_comments:
buf.write(os.linesep)
if name is not None:
buf.write("# " + name + os.linesep)
if comments is not None:
- buf.writelines(["# " + line + os.linesep for line in comments])
+ buf.writelines(
+ ["# " + line + os.linesep for line in comments])
# bash does not have the concept of NULL
if value is not None:
buf.write(id + "=" + value + os.linesep)
return buf.getvalue()
-
- def output_php(self, encoding = "utf-8"):
+ def output_php(self):
"""
Return variables as a PHP script.
"""
- buf = codecs.lookup(encoding)[3](StringIO())
+ buf = StringIO()
buf.write("<?php" + os.linesep)
buf.writelines(["// " + line + os.linesep for line in self._header()])
- for (category_id, (category, variables)) in self._variables.iteritems():
- for variable in variables.values():
- (id, name, value, comments) = self._sanitize_variable(category_id, variable)
+ for (category_id, (category, variables)) in self._variables.items():
+ for variable in list(variables.values()):
+ (id, name, value, comments) = self._sanitize_variable(
+ category_id, variable)
buf.write(os.linesep)
if name is not None:
buf.write("// " + name + os.linesep)
if comments is not None:
- buf.writelines(["// " + line + os.linesep for line in comments])
+ buf.writelines(
+ ["// " + line + os.linesep for line in comments])
if value is None:
value = 'NULL'
buf.write("define('%s', %s);" % (id, value) + os.linesep)
return buf.getvalue()
-
- def output_xml(self, encoding = "utf-8"):
+ def output_xml(self):
"""
Return variables in original XML format.
"""
- buf = codecs.lookup(encoding)[3](StringIO())
- self._dom.writexml(buf, addindent = " ", indent = "", newl = "\n", encoding = encoding)
-
- return buf.getvalue()
-
+ return self._dom.toxml()
- def output_variables(self, encoding = "utf-8"):
+ def output_variables(self):
"""
Return list of all variable names.
"""
- buf = codecs.lookup(encoding)[3](StringIO())
+ buf = StringIO()
- for (category_id, (category, variables)) in self._variables.iteritems():
- for variable in variables.values():
- (id, name, value, comments) = self._sanitize_variable(category_id, variable)
+ for (category_id, (category, variables)) in self._variables.items():
+ for variable in list(variables.values()):
+ (id, name, value, comments) = self._sanitize_variable(
+ category_id, variable)
buf.write(id + os.linesep)
return buf.getvalue()
-
- def output_packages(self, encoding = "utf-8"):
+ def output_packages(self):
"""
Return list of all packages.
"""
- buf = codecs.lookup(encoding)[3](StringIO())
+ buf = StringIO()
- for (group, packages) in self._packages.values():
- buf.write(os.linesep.join(packages.keys()))
+ for (group, packages) in list(self._packages.values()):
+ buf.write(os.linesep.join(list(packages.keys())))
if buf.tell():
buf.write(os.linesep)
return buf.getvalue()
-
- def output_groups(self, encoding = "utf-8"):
+ def output_groups(self):
"""
Return list of all package group names.
"""
- buf = codecs.lookup(encoding)[3](StringIO())
+ buf = StringIO()
- for (group, packages) in self._packages.values():
+ for (group, packages) in list(self._packages.values()):
buf.write(group['name'] + os.linesep)
return buf.getvalue()
-
- def output_comps(self, encoding = "utf-8"):
+ def output_comps(self):
"""
Return <comps> section of configuration.
"""
- if self._dom is None or \
- not self._dom.getElementsByTagName("comps"):
+ if (self._dom is None or not self._dom.getElementsByTagName("comps")):
return
comps = self._dom.getElementsByTagName("comps")[0]
impl = xml.dom.minidom.getDOMImplementation()
doc = impl.createDocument(None, "comps", None)
- buf = codecs.lookup(encoding)[3](StringIO())
+ buf = StringIO()
# Pop it off the DOM temporarily
parent = comps.parentNode
parent.removeChild(comps)
doc.replaceChild(comps, doc.documentElement)
- doc.writexml(buf, encoding = encoding)
+ doc.writexml(buf)
# Put it back
parent.appendChild(comps)
return buf.getvalue()
+ def validate_type(self, variable_type, value):
+
+ # ideally we should use the "validate_*" methods in PLCAPI or
+ # even declare some checks along with the default
+ # configuration (using RELAX NG?) but this shall work for now.
+ def ip_validator(val):
+ import socket
+ try:
+ socket.inet_aton(val)
+ return True
+ except:
+ return False
+
+ def email_validator(val):
+ return re.match('\A[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9._\-]+\.[a-zA-Z]+\Z', val)
+
+ def boolean_validator(val):
+ return val in ['true', 'false']
+
+ validators = {
+ 'email': email_validator,
+ 'ip': ip_validator,
+ 'boolean': boolean_validator,
+ }
+
+ # validate it if not a know type.
+ validator = validators.get(variable_type, lambda x: True)
+ return validator(value)
+
# xml.dom.minidom.Text.writexml adds surrounding whitespace to textual
# data when pretty-printing. Override this behavior.
writer.write(newl)
+####################
+# GLOBAL VARIABLES
+#
+g_configuration = None
+usual_variables = None
+config_dir = None
+service = None
+
+
+def noop_validator(validated_variables):
+ pass
+
+
+# historically we could also configure the devel pkg....
+def init_configuration():
+ global g_configuration
+ global usual_variables, config_dir, service
+
+ usual_variables = g_configuration["usual_variables"]
+ config_dir = g_configuration["config_dir"]
+ service = g_configuration["service"]
+
+ global def_default_config, def_site_config, def_consolidated_config
+ def_default_config = "%s/default_config.xml" % config_dir
+ def_site_config = "%s/configs/site.xml" % config_dir
+ def_consolidated_config = "%s/%s_config.xml" % (config_dir, service)
+
+ global mainloop_usage
+ mainloop_usage = """Available commands:
+ Uppercase versions give variables comments, when available
+ u/U\t\t\tEdit usual variables
+ w\t\t\tWrite
+ r\t\t\tRestart %(service)s service
+ R\t\t\tReload %(service)s service (rebuild config files for sh, python....)
+ q\t\t\tQuit (without saving)
+ h/?\t\t\tThis help
+---
+ l/L [<cat>|<var>]\tShow Locally modified variables/values
+ s/S [<cat>|<var>]\tShow variables/values (all, in category, single)
+ e/E [<cat>|<var>]\tEdit variables (all, in category, single)
+---
+ c\t\t\tList categories
+ v/V [<cat>|<var>]\tList Variables (all, in category, single)
+---
+Typical usage involves: u, [l,] w, r, q
+""" % globals()
+
+
+def usage():
+ command_usage = "%prog [options] [default-xml [site-xml [consolidated-xml]]]"
+ init_configuration()
+ command_usage += """
+\t default-xml defaults to %s
+\t site-xml defaults to %s
+\t consolidated-xml defaults to %s""" % (def_default_config, def_site_config, def_consolidated_config)
+ return command_usage
+
+
+####################
+variable_usage = """Edit Commands :
+#\tShow variable comments
+.\tStops prompting, return to mainloop
+/\tCleans any site-defined value, reverts to default
+=\tShows default value
+>\tSkips to next category
+?\tThis help
+"""
+
+####################
+
+
+def get_value(config, category_id, variable_id):
+ (category, variable) = config.get(category_id, variable_id)
+ return variable['value']
+
+
+def get_type(config, category_id, variable_id):
+ (category, variable) = config.get(category_id, variable_id)
+ return variable['type']
+
+
+def get_current_value(cread, cwrite, category_id, variable_id):
+ # the value stored in cwrite, if present, is the one we want
+ try:
+ result = get_value(cwrite, category_id, variable_id)
+ except:
+ result = get_value(cread, category_id, variable_id)
+ return result
+
+# refrain from using plc_config's _sanitize
+
+
+def get_varname(config, category_id, variable_id):
+ (category, variable) = config.get(category_id, variable_id)
+ return (category_id+"_"+variable['id']).upper()
+
+# could not avoid using _sanitize here..
+
+
+def get_name_comments(config, cid, vid):
+ try:
+ (category, variable) = config.get(cid, vid)
+ (id, name, value, comments) = config._sanitize_variable(cid, variable)
+ return (name, comments)
+ except:
+ return (None, [])
+
+
+def print_name_comments(config, cid, vid):
+ (name, comments) = get_name_comments(config, cid, vid)
+ if name:
+ print("### %s" % name)
+ if comments:
+ for line in comments:
+ print("# %s" % line)
+ else:
+ print("!!! No comment associated to %s_%s" % (cid, vid))
+
+####################
+
+
+def list_categories(config):
+ result = []
+ for (category_id, (category, variables)) in config.variables().items():
+ result += [category_id]
+ return result
+
+
+def print_categories(config):
+ print("Known categories")
+ for cid in list_categories(config):
+ print("%s" % (cid.upper()))
+
+####################
+
+
+def list_category(config, cid):
+ result = []
+ for (category_id, (category, variables)) in config.variables().items():
+ if (cid == category_id):
+ for variable in list(variables.values()):
+ result += ["%s_%s" % (cid, variable['id'])]
+ return result
+
+
+def print_category(config, cid, show_comments=True):
+ cid = cid.lower()
+ CID = cid.upper()
+ vids = list_category(config, cid)
+ if (len(vids) == 0):
+ print("%s : no such category" % CID)
+ else:
+ print("Category %s contains" % (CID))
+ for vid in vids:
+ print(vid.upper())
+
+####################
+
+
+def consolidate(default_config, site_config, consolidated_config):
+ global service
+ try:
+ conso = PLCConfiguration(default_config)
+ conso.load(site_config)
+ conso.save(consolidated_config)
+ except Exception as inst:
+ print("Could not consolidate, %s" % (str(inst)))
+ return
+ print(("Merged\n\t%s\nand\t%s\ninto\t%s" % (default_config, site_config,
+ consolidated_config)))
+
+
+def reload_service():
+ global service
+ os.system("set -x ; systemctl reload %s" % service)
+
+####################
+
+
+def restart_service():
+ global service
+ print(("==================== Stopping %s" % service))
+ os.system("systemctl stop %s" % service)
+ print(("==================== Starting %s" % service))
+ os.system("systemctl start %s" % service)
+
+####################
+
+
+def prompt_variable(cdef, cread, cwrite, category, variable,
+ show_comments, support_next=False):
+
+ assert 'id' in category
+ assert 'id' in variable
+
+ category_id = category['id']
+ variable_id = variable['id']
+
+ while True:
+ default_value = get_value(cdef, category_id, variable_id)
+ variable_type = get_type(cdef, category_id, variable_id)
+ current_value = get_current_value(
+ cread, cwrite, category_id, variable_id)
+ varname = get_varname(cread, category_id, variable_id)
+
+ if show_comments:
+ print_name_comments(cdef, category_id, variable_id)
+ prompt = "== %s : [%s] " % (varname, current_value)
+ try:
+ answer = input(prompt).strip()
+ except EOFError:
+ raise Exception('BailOut')
+ except KeyboardInterrupt:
+ print("\n")
+ raise Exception('BailOut')
+
+ # no change
+ if (answer == "") or (answer == current_value):
+ return None
+ elif (answer == "."):
+ raise Exception('BailOut')
+ elif (answer == "#"):
+ print_name_comments(cread, category_id, variable_id)
+ elif (answer == "?"):
+ print(variable_usage.strip())
+ elif (answer == "="):
+ print(("%s defaults to %s" % (varname, default_value)))
+ # revert to default : remove from cwrite (i.e. site-config)
+ elif (answer == "/"):
+ cwrite.delete(category_id, variable_id)
+ print(("%s reverted to %s" % (varname, default_value)))
+ return
+ elif (answer == ">"):
+ if support_next:
+ raise Exception('NextCategory')
+ else:
+ print("No support for next category")
+ else:
+ if cdef.validate_type(variable_type, answer):
+ variable['value'] = answer
+ cwrite.set(category, variable)
+ return
+ else:
+ print("Not a valid value")
+
+
+def prompt_variables_all(cdef, cread, cwrite, show_comments):
+ try:
+ for (category_id, (category, variables)) in cread.variables().items():
+ print(("========== Category = %s" % category_id.upper()))
+ for variable in list(variables.values()):
+ try:
+ newvar = prompt_variable(cdef, cread, cwrite, category, variable,
+ show_comments, True)
+ except Exception as inst:
+ if (str(inst) == 'NextCategory'):
+ break
+ else:
+ raise
+
+ except Exception as inst:
+ if (str(inst) == 'BailOut'):
+ return
+ else:
+ raise
+
+
+def prompt_variables_category(cdef, cread, cwrite, cid, show_comments):
+ cid = cid.lower()
+ CID = cid.upper()
+ try:
+ print(("========== Category = %s" % CID))
+ for vid in list_category(cdef, cid):
+ (category, variable) = cdef.locate_varname(vid.upper())
+ newvar = prompt_variable(cdef, cread, cwrite, category, variable,
+ show_comments, False)
+ except Exception as inst:
+ if (str(inst) == 'BailOut'):
+ return
+ else:
+ raise
+
+####################
+
+
+def show_variable(cdef, cread, cwrite,
+ category, variable, show_value, show_comments):
+ assert 'id' in category
+ assert 'id' in variable
+
+ category_id = category['id']
+ variable_id = variable['id']
+
+ default_value = get_value(cdef, category_id, variable_id)
+ current_value = get_current_value(cread, cwrite, category_id, variable_id)
+ varname = get_varname(cread, category_id, variable_id)
+ if show_comments:
+ print_name_comments(cdef, category_id, variable_id)
+ if show_value:
+ print("%s = %s" % (varname, current_value))
+ else:
+ print("%s" % (varname))
+
+
+def show_variables_all(cdef, cread, cwrite, show_value, show_comments):
+ for (category_id, (category, variables)) in cread.variables().items():
+ print(("========== Category = %s" % category_id.upper()))
+ for variable in list(variables.values()):
+ show_variable(cdef, cread, cwrite,
+ category, variable, show_value, show_comments)
+
+
+def show_variables_category(cdef, cread, cwrite, cid, show_value, show_comments):
+ cid = cid.lower()
+ CID = cid.upper()
+ print(("========== Category = %s" % CID))
+ for vid in list_category(cdef, cid):
+ (category, variable) = cdef.locate_varname(vid.upper())
+ show_variable(cdef, cread, cwrite, category, variable,
+ show_value, show_comments)
+
+
+####################
+re_mainloop_0arg = "^(?P<command>[uUwrRqlLsSeEcvVhH\?])[ \t]*$"
+re_mainloop_1arg = "^(?P<command>[sSeEvV])[ \t]+(?P<arg>\w+)$"
+matcher_mainloop_0arg = re.compile(re_mainloop_0arg)
+matcher_mainloop_1arg = re.compile(re_mainloop_1arg)
+
+
+def mainloop(cdef, cread, cwrite, default_config, site_config, consolidated_config):
+ global service
+ while True:
+ try:
+ answer = input(
+ "Enter command (u for usual changes, w to save, ? for help) ").strip()
+ except EOFError:
+ answer = ""
+ except KeyboardInterrupt:
+ print("\nBye")
+ sys.exit()
+
+ if (answer == "") or (answer in "?hH"):
+ print(mainloop_usage)
+ continue
+ groups_parse = matcher_mainloop_0arg.match(answer)
+ command = None
+ if (groups_parse):
+ command = groups_parse.group('command')
+ arg = None
+ else:
+ groups_parse = matcher_mainloop_1arg.match(answer)
+ if (groups_parse):
+ command = groups_parse.group('command')
+ arg = groups_parse.group('arg')
+ if not command:
+ print(("Unknown command >%s< -- use h for help" % answer))
+ continue
+
+ show_comments = command.isupper()
+
+ mode = 'ALL'
+ if arg:
+ mode = None
+ arg = arg.lower()
+ variables = list_category(cdef, arg)
+ if len(variables):
+ # category_id as the category name
+ # variables as the list of variable names
+ mode = 'CATEGORY'
+ category_id = arg
+ arg = arg.upper()
+ (category, variable) = cdef.locate_varname(arg)
+ if variable:
+ # category/variable as output by locate_varname
+ mode = 'VARIABLE'
+ if not mode:
+ print("%s: no such category or variable" % arg)
+ continue
+
+ if command in "qQ":
+ # todo check confirmation
+ return
+ elif command == "w":
+ try:
+ # Confirm that various constraints are met before saving file.
+ validate_variables = g_configuration.get(
+ 'validate_variables', {})
+ validated_variables = cwrite.verify(
+ cdef, cread, validate_variables)
+ validator = g_configuration.get('validator', noop_validator)
+ validator(validated_variables)
+ cwrite.save(site_config)
+ except ConfigurationException as e:
+ print("Save failed due to a configuration exception: %s" % e)
+ break
+ except:
+ print(traceback.print_exc())
+ print(("Could not save -- fix write access on %s" % site_config))
+ break
+ print(("Wrote %s" % site_config))
+ consolidate(default_config, site_config, consolidated_config)
+ print(("You might want to type 'r' (restart %s), 'R' (reload %s) or 'q' (quit)" %
+ (service, service)))
+ elif command in "uU":
+ global usual_variables
+ try:
+ for varname in usual_variables:
+ (category, variable) = cdef.locate_varname(varname)
+ if not (category is None and variable is None):
+ prompt_variable(cdef, cread, cwrite,
+ category, variable, False)
+ except Exception as inst:
+ if (str(inst) != 'BailOut'):
+ raise
+ elif command == "r":
+ restart_service()
+ elif command == "R":
+ reload_service()
+ elif command == "c":
+ print_categories(cread)
+ elif command in "eE":
+ if mode == 'ALL':
+ prompt_variables_all(cdef, cread, cwrite, show_comments)
+ elif mode == 'CATEGORY':
+ prompt_variables_category(
+ cdef, cread, cwrite, category_id, show_comments)
+ elif mode == 'VARIABLE':
+ try:
+ prompt_variable(cdef, cread, cwrite, category, variable,
+ show_comments, False)
+ except Exception as inst:
+ if str(inst) != 'BailOut':
+ raise
+ elif command in "vVsSlL":
+ show_value = (command in "sSlL")
+ (c1, c2, c3) = (cdef, cread, cwrite)
+ if command in "lL":
+ (c1, c2, c3) = (cwrite, cwrite, cwrite)
+ if mode == 'ALL':
+ show_variables_all(c1, c2, c3, show_value, show_comments)
+ elif mode == 'CATEGORY':
+ show_variables_category(
+ c1, c2, c3, category_id, show_value, show_comments)
+ elif mode == 'VARIABLE':
+ show_variable(c1, c2, c3, category, variable,
+ show_value, show_comments)
+ else:
+ print(("Unknown command >%s< -- use h for help" % answer))
+
+####################
+# creates directory for file if not yet existing
+
+
+def check_dir(config_file):
+ dirname = os.path.dirname(config_file)
+ if (not os.path.exists(dirname)):
+ try:
+ os.makedirs(dirname, 0o755)
+ except OSError as e:
+ print("Cannot create dir %s due to %s - exiting" % (dirname, e))
+ sys.exit(1)
+
+ if (not os.path.exists(dirname)):
+ print("Cannot create dir %s - exiting" % dirname)
+ sys.exit(1)
+ else:
+ print("Created directory %s" % dirname)
+
+####################
+
+
+def optParserSetup(configuration):
+ parser = OptionParser(usage=usage())
+ parser.set_defaults(config_dir=configuration['config_dir'],
+ service=configuration['service'],
+ usual_variables=configuration['usual_variables'])
+ parser.add_option("", "--configdir", dest="config_dir",
+ help="specify configuration directory")
+ parser.add_option("", "--service", dest="service",
+ help="specify /etc/init.d style service name")
+ parser.add_option("", "--usual_variable", dest="usual_variables",
+ action="append", help="add a usual variable")
+ return parser
+
+
+def main(command, argv, configuration):
+ global g_configuration
+ g_configuration = configuration
+
+ parser = optParserSetup(configuration)
+ (config, args) = parser.parse_args()
+ if len(args) > 3:
+ parser.error("too many arguments")
+
+ configuration['service'] = config.service
+ configuration['usual_variables'] = config.usual_variables
+ configuration['config_dir'] = config.config_dir
+ # add in new usual_variables defined on the command line
+ for usual_variable in config.usual_variables:
+ if usual_variable not in configuration['usual_variables']:
+ configuration['usual_variables'].append(usual_variable)
+
+ # intialize configuration
+ init_configuration()
+
+ (default_config, site_config, consolidated_config) = (
+ def_default_config, def_site_config, def_consolidated_config)
+ if len(args) >= 1:
+ default_config = args[0]
+ if len(args) >= 2:
+ site_config = args[1]
+ if len(args) == 3:
+ consolidated_config = args[2]
+
+ for c in (default_config, site_config, consolidated_config):
+ check_dir(c)
+
+ try:
+ # the default settings only - read only
+ cdef = PLCConfiguration(default_config)
+
+ # in effect : default settings + local settings - read only
+ cread = PLCConfiguration(default_config)
+
+ except ConfigurationException as e:
+ print(("Error %s in default config file %s" % (e, default_config)))
+ return 1
+ except:
+ print(traceback.print_exc())
+ print(("default config files %s not found, is myplc installed ?" %
+ default_config))
+ return 1
+
+ # local settings only, will be modified & saved
+ cwrite = PLCConfiguration()
+
+ try:
+ cread.load(site_config)
+ cwrite.load(site_config)
+ except:
+ cwrite = PLCConfiguration()
+
+ mainloop(cdef, cread, cwrite, default_config,
+ site_config, consolidated_config)
+ return 0
+
+
if __name__ == '__main__':
import sys
if len(sys.argv) > 1 and sys.argv[1] in ['build', 'install', 'uninstall']: