summaryrefslogtreecommitdiff
path: root/reconfigure/items
diff options
context:
space:
mode:
authorAndrew Shadura <andrew@shadura.me>2015-08-20 15:58:26 +0200
committerAndrew Shadura <andrew@shadura.me>2015-08-20 15:58:26 +0200
commitff1408420159488a106492ccd11dd234967029b6 (patch)
tree473420cee1c5229a427ec4cafead1aa6c0a26800 /reconfigure/items
Imported Upstream version 0.1.29
Diffstat (limited to 'reconfigure/items')
-rw-r--r--reconfigure/items/__init__.py1
-rw-r--r--reconfigure/items/ajenti.py54
-rw-r--r--reconfigure/items/bind9.py25
-rw-r--r--reconfigure/items/bound.py302
-rw-r--r--reconfigure/items/crontab.py64
-rw-r--r--reconfigure/items/ctdb.py50
-rw-r--r--reconfigure/items/dhcpd.py35
-rw-r--r--reconfigure/items/exports.py30
-rw-r--r--reconfigure/items/fstab.py26
-rw-r--r--reconfigure/items/group.py15
-rw-r--r--reconfigure/items/hosts.py26
-rw-r--r--reconfigure/items/iptables.py120
-rw-r--r--reconfigure/items/netatalk.py38
-rw-r--r--reconfigure/items/nsd.py23
-rw-r--r--reconfigure/items/passwd.py15
-rw-r--r--reconfigure/items/resolv.py19
-rw-r--r--reconfigure/items/samba.py59
-rw-r--r--reconfigure/items/squid.py95
-rw-r--r--reconfigure/items/supervisor.py22
-rw-r--r--reconfigure/items/util.py3
20 files changed, 1022 insertions, 0 deletions
diff --git a/reconfigure/items/__init__.py b/reconfigure/items/__init__.py
new file mode 100644
index 0000000..b680692
--- /dev/null
+++ b/reconfigure/items/__init__.py
@@ -0,0 +1 @@
+__all__ = [] \ No newline at end of file
diff --git a/reconfigure/items/ajenti.py b/reconfigure/items/ajenti.py
new file mode 100644
index 0000000..3756c5c
--- /dev/null
+++ b/reconfigure/items/ajenti.py
@@ -0,0 +1,54 @@
+import json
+
+from reconfigure.nodes import Node, PropertyNode
+from reconfigure.items.bound import BoundData, BoundDictionary
+
+
+class AjentiData (BoundData):
+ pass
+
+
+class HttpData (BoundData):
+ pass
+
+
+class SSLData (BoundData):
+ pass
+
+
+class UserData (BoundData):
+ def template(self):
+ return Node('unnamed',
+ PropertyNode('configs', {}),
+ PropertyNode('password', ''),
+ PropertyNode('permissions', []),
+ )
+
+
+class ConfigData (BoundData):
+ def template(self):
+ return PropertyNode('', '{}')
+
+
+AjentiData.bind_property('authentication', 'authentication')
+AjentiData.bind_property('installation_id', 'installation_id')
+AjentiData.bind_property('enable_feedback', 'enable_feedback')
+AjentiData.bind_child('http_binding', lambda x: x.get('bind'), item_class=HttpData)
+AjentiData.bind_child('ssl', lambda x: x.get('ssl'), item_class=SSLData)
+AjentiData.bind_collection('users', path=lambda x: x.get('users'), item_class=UserData, collection_class=BoundDictionary, key=lambda x: x.name)
+
+
+HttpData.bind_property('host', 'host')
+HttpData.bind_property('port', 'port')
+
+SSLData.bind_property('certificate_path', 'certificate_path')
+SSLData.bind_property('enable', 'enable')
+
+ConfigData.bind_name('name')
+
+UserData.bind_name('name')
+UserData.bind_property('password', 'password')
+UserData.bind_property('permissions', 'permissions')
+UserData.bind_collection('configs', lambda x: x.get('configs'), item_class=ConfigData, collection_class=BoundDictionary, key=lambda x: x.name)
+
+ConfigData.bind_attribute('value', 'data', getter=json.loads, setter=json.dumps)
diff --git a/reconfigure/items/bind9.py b/reconfigure/items/bind9.py
new file mode 100644
index 0000000..a42bb90
--- /dev/null
+++ b/reconfigure/items/bind9.py
@@ -0,0 +1,25 @@
+from reconfigure.nodes import Node, PropertyNode
+from reconfigure.items.bound import BoundData
+
+
+class BIND9Data (BoundData):
+ pass
+
+
+class ZoneData (BoundData):
+ def template(self):
+ return Node(
+ 'zone',
+ PropertyNode('type', 'master'),
+ PropertyNode('file', 'db.example.com'),
+ parameter='"example.com"',
+ )
+
+
+quote = lambda x: '"%s"' % x
+unquote = lambda x: x.strip('"')
+
+BIND9Data.bind_collection('zones', selector=lambda x: x.name == 'zone', item_class=ZoneData)
+ZoneData.bind_attribute('parameter', 'name', getter=unquote, setter=quote)
+ZoneData.bind_property('type', 'type')
+ZoneData.bind_property('file', 'file', getter=unquote, setter=quote)
diff --git a/reconfigure/items/bound.py b/reconfigure/items/bound.py
new file mode 100644
index 0000000..f096965
--- /dev/null
+++ b/reconfigure/items/bound.py
@@ -0,0 +1,302 @@
+import json
+
+
+class BoundCollection (object):
+ """
+ Binds a list-like object to a set of nodes
+
+ :param node: target node (its children will be bound)
+ :param item_class: :class:`BoundData` class for items
+ :param selector: ``lambda x: bool``, used to filter out a subset of nodes
+ """
+
+ def __init__(self, node, item_class, selector=lambda x: True):
+ self.node = node
+ self.selector = selector
+ self.item_class = item_class
+ self.data = []
+ self.rebuild()
+
+ def rebuild(self):
+ """
+ Discards cached collection and rebuilds it from the nodes
+ """
+ del self.data[:]
+ for node in self.node.children:
+ if self.selector(node):
+ self.data.append(self.item_class(node))
+
+ def to_dict(self):
+ return [x.to_dict() if hasattr(x, 'to_dict') else x for x in self]
+
+ def to_json(self):
+ return json.dumps(self.to_dict(), indent=4)
+
+ def __str__(self):
+ return self.to_json()
+
+ def __iter__(self):
+ return self.data.__iter__()
+
+ def __getitem__(self, index):
+ return self.data.__getitem__(index)
+
+ def __len__(self):
+ return len(self.data)
+
+ def __contains__(self, item):
+ return item in self.data
+
+ def append(self, item):
+ self.node.append(item._node)
+ self.data.append(item)
+
+ def remove(self, item):
+ self.node.remove(item._node)
+ self.data.remove(item)
+
+ def insert(self, index, item):
+ self.node.children.insert(index, item._node)
+ self.data.insert(index, item)
+
+ def pop(self, index):
+ d = self[index]
+ self.remove(d)
+ return d
+
+
+class BoundDictionary (BoundCollection):
+ """
+ Binds a dict-like object to a set of nodes. Accepts same params as :class:`BoundCollection` plus ``key``
+
+ :param key: ``lambda value: object``, is used to get key for value in the collection
+ """
+
+ def __init__(self, key=None, **kwargs):
+ self.key = key
+ BoundCollection.__init__(self, **kwargs)
+
+ def rebuild(self):
+ BoundCollection.rebuild(self)
+ self.rebuild_dict()
+
+ def rebuild_dict(self):
+ self.datadict = dict((self.key(x), x) for x in self.data)
+
+ def to_dict(self):
+ return dict((k, x.to_dict() if hasattr(x, 'to_dict') else x) for k, x in self.iteritems())
+
+ def __getitem__(self, key):
+ self.rebuild_dict()
+ return self.datadict[key]
+
+ def __setitem__(self, key, value):
+ self.rebuild_dict()
+ if not key in self:
+ self.append(value)
+ self.datadict[key] = value
+
+ def __contains__(self, key):
+ self.rebuild_dict()
+ return key in self.datadict
+
+ def __iter__(self):
+ self.rebuild_dict()
+ return self.datadict.__iter__()
+
+ def iteritems(self):
+ return self.datadict.iteritems()
+
+ def setdefault(self, k, v):
+ if not k in self:
+ self[k] = v
+ self.append(v)
+ return self[k]
+
+ def values(self):
+ return self.data
+
+ def update(self, other):
+ for k, v in other.iteritems():
+ self[k] = v
+
+ def pop(self, key):
+ if key in self:
+ self.remove(self[key])
+
+
+class BoundData (object):
+ """
+ Binds itself to a node.
+
+ ``bind_*`` classmethods should be called on module-level, after subclass declaration.
+
+ :param node: all bindings will be relative to this node
+ :param kwargs: if ``node`` is ``None``, ``template(**kwargs)`` will be used to create node tree fragment
+ """
+
+ def __init__(self, node=None, **kwargs):
+ if not node:
+ node = self.template(**kwargs)
+ self._node = node
+
+ def template(self, **kwargs):
+ """
+ Override to create empty objects.
+
+ :returns: a :class:`reconfigure.nodes.Node` tree that will be used as a template for new BoundData instance
+ """
+ return None
+
+ def to_dict(self):
+ res_dict = {}
+ for attr_key in self.__class__.__dict__:
+ if attr_key in self.__class__._bound:
+ attr_value = getattr(self, attr_key)
+ if isinstance(attr_value, BoundData):
+ res_dict[attr_key] = attr_value.to_dict()
+ elif isinstance(attr_value, BoundCollection):
+ res_dict[attr_key] = attr_value.to_dict()
+ else:
+ res_dict[attr_key] = attr_value
+ return res_dict
+
+ def to_json(self):
+ return json.dumps(self.to_dict(), indent=4)
+
+ def __str__(self):
+ return self.to_json()
+
+ @classmethod
+ def bind(cls, data_property, getter, setter):
+ """
+ Creates an arbitrary named property in the class with given getter and setter. Not usually used directly.
+
+ :param data_property: property name
+ :param getter: ``lambda: object``, property getter
+ :param setter: ``lambda value: None``, property setter
+ """
+ if not hasattr(cls, '_bound'):
+ cls._bound = []
+ cls._bound.append(data_property)
+ setattr(cls, data_property, property(getter, setter))
+
+ @classmethod
+ def bind_property(cls, node_property, data_property, default=None, \
+ default_remove=[], \
+ path=lambda x: x, getter=lambda x: x, setter=lambda x: x):
+ """
+ Binds the value of a child :class:`reconfigure.node.PropertyNode` to a property
+
+ :param node_property: ``PropertyNode``'s ``name``
+ :param data_property: property name to be created
+ :param default: default value of the property (is ``PropertyNode`` doesn't exist)
+ :param default_remove: if setting a value contained in default_remove, the target property is removed
+ :param path: ``lambda self.node: PropertyNode``, can be used to point binding to another Node instead of ``self.node``.
+ :param getter: ``lambda object: object``, used to transform value when getting
+ :param setter: ``lambda object: object``, used to transform value when setting
+ """
+ def pget(self):
+ prop = path(self._node).get(node_property)
+ if prop:
+ return getter(prop.value)
+ else:
+ return default
+
+ def pset(self, value):
+ if setter(value) in default_remove:
+ node = path(self._node).get(node_property)
+ if node:
+ path(self._node).remove(node)
+ else:
+ path(self._node).set_property(node_property, setter(value))
+
+ cls.bind(data_property, pget, pset)
+
+ @classmethod
+ def bind_attribute(cls, node_attribute, data_property, default=None, \
+ path=lambda x: x, getter=lambda x: x, setter=lambda x: x):
+ """
+ Binds the value of node object's attribute to a property
+
+ :param node_attribute: ``Node``'s attribute name
+ :param data_property: property name to be created
+ :param default: default value of the property (is ``PropertyNode`` doesn't exist)
+ :param path: ``lambda self.node: PropertyNode``, can be used to point binding to another Node instead of ``self.node``.
+ :param getter: ``lambda object: object``, used to transform value when getting
+ :param setter: ``lambda object: object``, used to transform value when setting
+ """
+ def pget(self):
+ prop = getattr(path(self._node), node_attribute)
+ if prop:
+ return getter(prop)
+ else:
+ return getter(default)
+
+ def pset(self, value):
+ setattr(path(self._node), node_attribute, setter(value))
+
+ cls.bind(data_property, pget, pset)
+
+ @classmethod
+ def bind_collection(cls, data_property, path=lambda x: x, selector=lambda x: True, item_class=None, \
+ collection_class=BoundCollection, **kwargs):
+ """
+ Binds the subset of node's children to a collection property
+
+ :param data_property: property name to be created
+ :param path: ``lambda self.node: PropertyNode``, can be used to point binding to another Node instead of ``self.node``.
+ :param selector: ``lambda Node: bool``, can be used to filter out a subset of child nodes
+ :param item_class: a :class:`BoundData` subclass to be used for collection items
+ :param collection_class: a :class:`BoundCollection` subclass to be used for collection property itself
+ """
+ def pget(self):
+ if not hasattr(self, '__' + data_property):
+ setattr(self, '__' + data_property,
+ collection_class(
+ node=path(self._node),
+ item_class=item_class,
+ selector=selector,
+ **kwargs
+ )
+ )
+ return getattr(self, '__' + data_property)
+
+ cls.bind(data_property, pget, None)
+
+ @classmethod
+ def bind_name(cls, data_property, getter=lambda x: x, setter=lambda x: x):
+ """
+ Binds the value of node's ``name`` attribute to a property
+
+ :param data_property: property name to be created
+ :param getter: ``lambda object: object``, used to transform value when getting
+ :param setter: ``lambda object: object``, used to transform value when setting
+ """
+ def pget(self):
+ return getter(self._node.name)
+
+ def pset(self, value):
+ self._node.name = setter(value)
+
+ cls.bind(data_property, pget, pset)
+
+ @classmethod
+ def bind_child(cls, data_property, path=lambda x: x, item_class=None):
+ """
+ Directly binds a child Node to a BoundData property
+
+ :param data_property: property name to be created
+ :param path: ``lambda self.node: PropertyNode``, can be used to point binding to another Node instead of ``self.node``.
+ :param item_class: a :class:`BoundData` subclass to be used for the property value
+ """
+ def pget(self):
+ if not hasattr(self, '__' + data_property):
+ setattr(self, '__' + data_property,
+ item_class(
+ path(self._node),
+ )
+ )
+ return getattr(self, '__' + data_property)
+
+ cls.bind(data_property, pget, None)
diff --git a/reconfigure/items/crontab.py b/reconfigure/items/crontab.py
new file mode 100644
index 0000000..185ff94
--- /dev/null
+++ b/reconfigure/items/crontab.py
@@ -0,0 +1,64 @@
+from reconfigure.nodes import Node, PropertyNode
+from reconfigure.items.bound import BoundData
+
+
+class CrontabData(BoundData):
+ """Data class for crontab configs"""
+ pass
+
+
+class CrontabNormalTaskData(BoundData):
+ fields = ['minute', 'hour', 'day_of_month', 'month', 'day_of_week', 'command']
+
+ def describe(self):
+ return ' '.join(getattr(self, x) for x in self.fields)
+
+ def template(self, **kwargs):
+ return Node('normal_task', children=[
+ PropertyNode('minute', '0'),
+ PropertyNode('hour', '0'),
+ PropertyNode('day_of_month', '1'),
+ PropertyNode('month', '1'),
+ PropertyNode('day_of_week', '1'),
+ PropertyNode('command', 'false')
+ ])
+
+
+class CrontabSpecialTaskData(BoundData):
+ fields = ['special', 'command']
+
+ def template(self, **kwargs):
+ return Node('special_task', children=[
+ PropertyNode('special', '@reboot'),
+ PropertyNode('command', 'false')
+ ])
+
+
+class CrontabEnvSettingData(BoundData):
+ fields = ['name', 'value']
+
+ def template(self, **kwargs):
+ return Node('env_setting', children=[
+ PropertyNode('name', 'ENV_NAME'),
+ PropertyNode('value', 'ENV_VALUE')
+ ])
+
+
+def bind_for_fields(bound_data_class):
+ for field in bound_data_class.fields:
+ bound_data_class.bind_property(field, field)
+
+CrontabData.bind_collection('normal_tasks', selector=lambda x: x.name == 'normal_task', item_class=CrontabNormalTaskData)
+bind_for_fields(CrontabNormalTaskData)
+
+CrontabNormalTaskData.bind_attribute('comment', 'comment')
+
+CrontabData.bind_collection('env_settings', selector=lambda x: x.name == 'env_setting', item_class=CrontabEnvSettingData)
+bind_for_fields(CrontabEnvSettingData)
+
+CrontabEnvSettingData.bind_attribute('comment', 'comment')
+
+CrontabData.bind_collection('special_tasks', selector=lambda x: x.name == 'special_task', item_class=CrontabSpecialTaskData)
+bind_for_fields(CrontabSpecialTaskData)
+
+CrontabSpecialTaskData.bind_attribute('comment', 'comment')
diff --git a/reconfigure/items/ctdb.py b/reconfigure/items/ctdb.py
new file mode 100644
index 0000000..f700c03
--- /dev/null
+++ b/reconfigure/items/ctdb.py
@@ -0,0 +1,50 @@
+from reconfigure.nodes import Node, PropertyNode
+from reconfigure.items.bound import BoundData
+from util import yn_getter, yn_setter
+
+
+class CTDBData (BoundData):
+ pass
+
+CTDBData.bind_property('CTDB_RECOVERY_LOCK', 'recovery_lock_file', path=lambda x: x.get(None))
+CTDBData.bind_property('CTDB_PUBLIC_INTERFACE', 'public_interface', path=lambda x: x.get(None))
+CTDBData.bind_property('CTDB_PUBLIC_ADDRESSES', 'public_addresses_file', default='/etc/ctdb/public_addresses', path=lambda x: x.get(None))
+CTDBData.bind_property(
+ 'CTDB_MANAGES_SAMBA', 'manages_samba', path=lambda x: x.get(None),
+ getter=yn_getter, setter=yn_setter)
+CTDBData.bind_property('CTDB_NODES', 'nodes_file', default='/etc/ctdb/nodes', path=lambda x: x.get(None))
+CTDBData.bind_property('CTDB_LOGFILE', 'log_file', path=lambda x: x.get(None))
+CTDBData.bind_property('CTDB_DEBUGLEVEL', 'debug_level', default='2', path=lambda x: x.get(None))
+CTDBData.bind_property('CTDB_PUBLIC_NETWORK', 'public_network', default='', path=lambda x: x.get(None))
+CTDBData.bind_property('CTDB_PUBLIC_GATEWAY', 'public_gateway', default='', path=lambda x: x.get(None))
+
+
+class NodesData (BoundData):
+ pass
+
+
+class NodeData (BoundData):
+ def template(self):
+ return Node('line', children=[
+ Node('token', children=[PropertyNode('value', '127.0.0.1')]),
+ ])
+
+
+NodesData.bind_collection('nodes', item_class=NodeData)
+NodeData.bind_property('value', 'address', path=lambda x: x.children[0])
+
+
+class PublicAddressesData (BoundData):
+ pass
+
+
+class PublicAddressData (BoundData):
+ def template(self):
+ return Node('line', children=[
+ Node('token', children=[PropertyNode('value', '127.0.0.1')]),
+ Node('token', children=[PropertyNode('value', 'eth0')]),
+ ])
+
+PublicAddressesData.bind_collection('addresses', item_class=PublicAddressData)
+PublicAddressData.bind_property('value', 'address', path=lambda x: x.children[0])
+PublicAddressData.bind_property('value', 'interface', path=lambda x: x.children[1])
diff --git a/reconfigure/items/dhcpd.py b/reconfigure/items/dhcpd.py
new file mode 100644
index 0000000..aadda9a
--- /dev/null
+++ b/reconfigure/items/dhcpd.py
@@ -0,0 +1,35 @@
+from reconfigure.nodes import Node, PropertyNode
+from reconfigure.items.bound import BoundData
+
+
+class DHCPDData (BoundData):
+ pass
+
+
+class SubnetData (BoundData):
+ def template(self):
+ return Node(
+ 'subnet',
+ parameter='192.168.0.0 netmask 255.255.255.0',
+ )
+
+
+class RangeData (BoundData):
+ def template(self):
+ return PropertyNode('range', '192.168.0.1 192.168.0.100')
+
+
+class OptionData (BoundData):
+ def template(self):
+ return PropertyNode('option', '')
+
+
+DHCPDData.bind_collection('subnets', selector=lambda x: x.name == 'subnet', item_class=SubnetData)
+SubnetData.bind_attribute('parameter', 'name')
+SubnetData.bind_collection('subnets', selector=lambda x: x.name == 'subnet', item_class=SubnetData)
+SubnetData.bind_collection('ranges', selector=lambda x: x.name == 'range', item_class=RangeData)
+RangeData.bind_attribute('value', 'range')
+OptionData.bind_attribute('value', 'value')
+
+for x in [DHCPDData, SubnetData]:
+ x.bind_collection('options', selector=lambda x: x.name == 'option', item_class=OptionData)
diff --git a/reconfigure/items/exports.py b/reconfigure/items/exports.py
new file mode 100644
index 0000000..5fe3a40
--- /dev/null
+++ b/reconfigure/items/exports.py
@@ -0,0 +1,30 @@
+from reconfigure.nodes import Node, PropertyNode
+from reconfigure.items.bound import BoundData
+
+
+class ExportsData (BoundData):
+ pass
+
+
+class ExportData (BoundData):
+ def template(self):
+ return Node(
+ '/',
+ Node('clients')
+ )
+
+
+class ClientData (BoundData):
+ def template(self):
+ return Node(
+ 'localhost',
+ PropertyNode('options', '')
+ )
+
+
+ExportsData.bind_collection('exports', item_class=ExportData)
+ExportData.bind_name('name')
+ExportData.bind_attribute('comment', 'comment', default='')
+ExportData.bind_collection('clients', path=lambda x: x['clients'], item_class=ClientData)
+ClientData.bind_name('name')
+ClientData.bind_property('options', 'options')
diff --git a/reconfigure/items/fstab.py b/reconfigure/items/fstab.py
new file mode 100644
index 0000000..81b8060
--- /dev/null
+++ b/reconfigure/items/fstab.py
@@ -0,0 +1,26 @@
+from reconfigure.nodes import Node, PropertyNode
+from reconfigure.items.bound import BoundData
+
+
+class FSTabData (BoundData):
+ pass
+
+
+class FilesystemData (BoundData):
+ fields = ['device', 'mountpoint', 'type', 'options', 'freq', 'passno']
+
+ def template(self):
+ return Node('line', children=[
+ Node('token', children=[PropertyNode('value', 'none')]),
+ Node('token', children=[PropertyNode('value', 'none')]),
+ Node('token', children=[PropertyNode('value', 'auto')]),
+ Node('token', children=[PropertyNode('value', 'none')]),
+ Node('token', children=[PropertyNode('value', '0')]),
+ Node('token', children=[PropertyNode('value', '0')]),
+ ])
+
+
+FSTabData.bind_collection('filesystems', item_class=FilesystemData)
+for i in range(0, len(FilesystemData.fields)):
+ path = lambda i: lambda x: x.children[i]
+ FilesystemData.bind_property('value', FilesystemData.fields[i], path=path(i))
diff --git a/reconfigure/items/group.py b/reconfigure/items/group.py
new file mode 100644
index 0000000..f95b28a
--- /dev/null
+++ b/reconfigure/items/group.py
@@ -0,0 +1,15 @@
+from reconfigure.items.bound import BoundData
+
+
+class GroupsData (BoundData):
+ pass
+
+
+class GroupData (BoundData):
+ fields = ['name', 'password', 'gid', 'users']
+
+
+GroupsData.bind_collection('groups', item_class=GroupData)
+for i in range(0, len(GroupData.fields)):
+ path = lambda i: lambda x: x.children[i]
+ GroupData.bind_property('value', GroupData.fields[i], path=path(i))
diff --git a/reconfigure/items/hosts.py b/reconfigure/items/hosts.py
new file mode 100644
index 0000000..74904dc
--- /dev/null
+++ b/reconfigure/items/hosts.py
@@ -0,0 +1,26 @@
+from reconfigure.nodes import Node, PropertyNode
+from reconfigure.items.bound import BoundData
+
+
+class HostsData (BoundData):
+ pass
+
+
+class HostData (BoundData):
+ def template(self):
+ return Node('line', children=[
+ Node('token', children=[PropertyNode('value', '127.0.0.1')]),
+ Node('token', children=[PropertyNode('value', 'localhost')]),
+ ])
+
+
+class AliasData (BoundData):
+ def template(self):
+ return Node()
+
+
+HostsData.bind_collection('hosts', item_class=HostData)
+HostData.bind_property('value', 'address', path=lambda x: x.children[0])
+HostData.bind_property('value', 'name', path=lambda x: x.children[1])
+HostData.bind_collection('aliases', item_class=AliasData, selector=lambda x: x.parent.indexof(x) > 1)
+AliasData.bind_property('value', 'name')
diff --git a/reconfigure/items/iptables.py b/reconfigure/items/iptables.py
new file mode 100644
index 0000000..d4656ff
--- /dev/null
+++ b/reconfigure/items/iptables.py
@@ -0,0 +1,120 @@
+from reconfigure.nodes import Node, PropertyNode
+from reconfigure.items.bound import BoundData
+
+
+class IPTablesData (BoundData):
+ pass
+
+
+class TableData (BoundData):
+ def template(self):
+ return Node('custom')
+
+
+class ChainData (BoundData):
+ def template(self):
+ return Node(
+ 'CUSTOM',
+ PropertyNode('default', '-'),
+ )
+
+
+class RuleData (BoundData):
+ def template(self):
+ return Node(
+ 'append',
+ Node(
+ 'option',
+ Node('argument', PropertyNode('value', 'ACCEPT')),
+ PropertyNode('negative', False),
+ PropertyNode('name', 'j'),
+ )
+ )
+
+ @property
+ def summary(self):
+ return ' '.join((
+ ('! ' if x.negative else '') +
+ ('-' if len(x.name) == 1 else '--') + x.name + ' ' +
+ ' '.join(a.value for a in x.arguments))
+ for x in self.options
+ )
+
+ def verify(self):
+ protocol_option = None
+ for option in self.options:
+ if option.name in ['p', 'protocol']:
+ self.options.remove(option)
+ self.options.insert(0, option)
+ protocol_option = option
+ for option in self.options:
+ if 'port' in option.name:
+ if not protocol_option:
+ protocol_option = OptionData.create('protocol')
+ self.options.insert(0, protocol_option)
+
+ def get_option(self, *names):
+ for name in names:
+ for option in self.options:
+ if option.name == name:
+ return option
+
+
+class OptionData (BoundData):
+ templates = {
+ 'protocol': ['protocol', ['tcp']],
+ 'match': ['match', ['multiport']],
+ 'source': ['source', ['127.0.0.1']],
+ 'mac-source': ['mac-source', ['00:00:00:00:00:00']],
+ 'destination': ['destination', ['127.0.0.1']],
+ 'in-interface': ['in-interface', ['lo']],
+ 'out-interface': ['out-interface', ['lo']],
+ 'source-port': ['source-port', ['80']],
+ 'source-ports': ['source-ports', ['80,443']],
+ 'destination-port': ['destination-port', ['80']],
+ 'destination-ports': ['destination-ports', ['80,443']],
+ 'state': ['state', ['NEW']],
+ 'reject-with': ['reject-with', ['icmp-net-unreachable']],
+ 'custom': ['name', ['value']],
+ }
+
+ @staticmethod
+ def create(template_id):
+ print 'new'
+ t = OptionData.templates[template_id]
+ return OptionData(Node(
+ 'option',
+ *(
+ [Node('argument', PropertyNode('value', x)) for x in t[1]]
+ + [PropertyNode('negative', False)]
+ + [PropertyNode('name', t[0])]
+ )
+ ))
+
+ @staticmethod
+ def create_destination():
+ print 'new'
+ return OptionData(Node(
+ 'option',
+ Node('argument', PropertyNode('value', 'ACCEPT')),
+ PropertyNode('negative', False),
+ PropertyNode('name', 'j'),
+ ))
+
+
+class ArgumentData (BoundData):
+ pass
+
+
+IPTablesData.bind_collection('tables', item_class=TableData)
+TableData.bind_collection('chains', item_class=ChainData)
+TableData.bind_name('name')
+ChainData.bind_property('default', 'default')
+ChainData.bind_collection('rules', selector=lambda x: x.name == 'append', item_class=RuleData)
+ChainData.bind_name('name')
+RuleData.bind_collection('options', item_class=OptionData)
+RuleData.bind_attribute('comment', 'comment')
+OptionData.bind_property('name', 'name')
+OptionData.bind_property('negative', 'negative')
+OptionData.bind_collection('arguments', selector=lambda x: x.name == 'argument', item_class=ArgumentData)
+ArgumentData.bind_property('value', 'value')
diff --git a/reconfigure/items/netatalk.py b/reconfigure/items/netatalk.py
new file mode 100644
index 0000000..e4957cf
--- /dev/null
+++ b/reconfigure/items/netatalk.py
@@ -0,0 +1,38 @@
+from reconfigure.nodes import Node, PropertyNode
+from reconfigure.items.bound import BoundData
+from util import yn_getter, yn_setter
+
+
+class NetatalkData (BoundData):
+ pass
+
+
+class GlobalData (BoundData):
+ pass
+
+
+class ShareData (BoundData):
+ fields = ['path', 'appledouble', 'valid users', 'cnid scheme', 'ea', 'password']
+ defaults = ['', 'ea', '', 'dbd', 'none', '']
+
+ def template(self):
+ return Node(
+ 'share',
+ *[PropertyNode(x, y) for x, y in zip(ShareData.fields, ShareData.defaults)]
+ )
+
+
+NetatalkData.bind_child('global', lambda x: x.get('Global'), item_class=GlobalData)
+NetatalkData.bind_collection('shares', selector=lambda x: x.name != 'Global', item_class=ShareData)
+
+
+GlobalData.bind_property('afp port', 'afp_port', default='548')
+GlobalData.bind_property('cnid listen', 'cnid_listen', default='localhost:4700')
+GlobalData.bind_property(
+ 'zeroconf', 'zeroconf', default=True,
+ getter=yn_getter, setter=yn_setter)
+
+ShareData.bind_name('name')
+ShareData.bind_attribute('comment', 'comment', path=lambda x: x.get('path'), default='')
+for f, d in zip(ShareData.fields, ShareData.defaults):
+ ShareData.bind_property(f, f.replace(' ', '_'), default=d)
diff --git a/reconfigure/items/nsd.py b/reconfigure/items/nsd.py
new file mode 100644
index 0000000..df20d95
--- /dev/null
+++ b/reconfigure/items/nsd.py
@@ -0,0 +1,23 @@
+from reconfigure.nodes import Node, PropertyNode
+from reconfigure.items.bound import BoundData
+
+
+class NSDData (BoundData):
+ pass
+
+
+class ZoneData (BoundData):
+ def template(self):
+ return Node(
+ 'zone',
+ PropertyNode('name', '"example.com"'),
+ PropertyNode('file', '"example.com.zone"'),
+ )
+
+
+quote = lambda x: '"%s"' % x
+unquote = lambda x: x.strip('"')
+
+NSDData.bind_collection('zones', selector=lambda x: x.name == 'zone', item_class=ZoneData)
+ZoneData.bind_property('name', 'name', getter=unquote, setter=quote)
+ZoneData.bind_property('zonefile', 'file', getter=unquote, setter=quote)
diff --git a/reconfigure/items/passwd.py b/reconfigure/items/passwd.py
new file mode 100644
index 0000000..147bc8a
--- /dev/null
+++ b/reconfigure/items/passwd.py
@@ -0,0 +1,15 @@
+from reconfigure.items.bound import BoundData
+
+
+class PasswdData (BoundData):
+ pass
+
+
+class UserData (BoundData):
+ fields = ['name', 'password', 'uid', 'gid', 'comment', 'home', 'shell']
+
+
+PasswdData.bind_collection('users', item_class=UserData)
+for i in range(0, len(UserData.fields)):
+ path = lambda i: lambda x: x.children[i]
+ UserData.bind_property('value', UserData.fields[i], path=path(i))
diff --git a/reconfigure/items/resolv.py b/reconfigure/items/resolv.py
new file mode 100644
index 0000000..dd9a009
--- /dev/null
+++ b/reconfigure/items/resolv.py
@@ -0,0 +1,19 @@
+from reconfigure.nodes import Node, PropertyNode
+from reconfigure.items.bound import BoundData
+
+
+class ResolvData (BoundData):
+ pass
+
+
+class ItemData (BoundData):
+ def template(self):
+ return Node('line', children=[
+ Node('token', children=[PropertyNode('value', 'nameserver')]),
+ Node('token', children=[PropertyNode('value', '8.8.8.8')]),
+ ])
+
+
+ResolvData.bind_collection('items', item_class=ItemData)
+ItemData.bind_property('value', 'name', path=lambda x: x.children[0])
+ItemData.bind_property('value', 'value', path=lambda x: x.children[1])
diff --git a/reconfigure/items/samba.py b/reconfigure/items/samba.py
new file mode 100644
index 0000000..079da5e
--- /dev/null
+++ b/reconfigure/items/samba.py
@@ -0,0 +1,59 @@
+from reconfigure.nodes import Node, PropertyNode
+from reconfigure.items.bound import BoundData
+from util import yn_getter, yn_setter
+
+
+class SambaData (BoundData):
+ pass
+
+
+class GlobalData (BoundData):
+ pass
+
+
+class ShareData (BoundData):
+ fields = [
+ 'comment', 'path', 'guest ok', 'browseable', 'create mask', 'directory mask', 'read only',
+ 'follow symlinks', 'wide links',
+ ]
+ defaults = [
+ '', '', 'no', 'yes', '0744', '0755', 'yes',
+ 'yes', 'no',
+ ]
+
+ def template(self):
+ return Node(
+ 'share',
+ *[PropertyNode(x, y) for x, y in zip(ShareData.fields, ShareData.defaults)]
+ )
+
+
+SambaData.bind_child('global', lambda x: x.get('global'), item_class=GlobalData)
+SambaData.bind_collection('shares', selector=lambda x: x.name != 'global', item_class=ShareData)
+
+
+GlobalData.bind_property('workgroup', 'workgroup', default='')
+GlobalData.bind_property('server string', 'server_string', default='')
+GlobalData.bind_property('interfaces', 'interfaces', default='')
+GlobalData.bind_property(
+ 'bind interfaces only', 'bind_interfaces_only', default=True,
+ getter=yn_getter, setter=yn_setter)
+GlobalData.bind_property('log file', 'log_file', default='')
+GlobalData.bind_property('security', 'security', default='user')
+
+ShareData.bind_name('name')
+ShareData.bind_property('path', 'path', default='')
+ShareData.bind_property('comment', 'comment', default='')
+ShareData.bind_property('create mask', 'create_mask', default='0744')
+ShareData.bind_property('directory mask', 'directory_mask', default='0755')
+
+for x, y in [
+ ('guest ok', False),
+ ('browseable', True),
+ ('read only', True),
+ ('follow symlinks', True),
+ ('wide links', False),
+]:
+ ShareData.bind_property(
+ x, x.replace(' ', '_'), default=y,
+ getter=yn_getter, setter=yn_setter)
diff --git a/reconfigure/items/squid.py b/reconfigure/items/squid.py
new file mode 100644
index 0000000..2504b55
--- /dev/null
+++ b/reconfigure/items/squid.py
@@ -0,0 +1,95 @@
+from reconfigure.nodes import Node, PropertyNode
+from reconfigure.items.bound import BoundData
+
+
+class SquidData (BoundData):
+ pass
+
+
+class ACLData (BoundData):
+ def template(self, name, *args):
+ children = [PropertyNode('1', name)]
+ index = 2
+ for arg in args:
+ children += [PropertyNode(str(index), arg)]
+ index += 1
+ return Node(
+ 'line',
+ PropertyNode('name', 'acl'),
+ Node(
+ 'arguments',
+ *children
+ )
+ )
+
+ def describe(self):
+ return ' '.join(x.value for x in self.options)
+
+
+class HTTPAccessData (BoundData):
+ def template(self):
+ return Node(
+ 'line',
+ PropertyNode('name', 'http_access'),
+ Node('arguments', PropertyNode('1', ''))
+ )
+
+
+class HTTPPortData (BoundData):
+ def template(self):
+ return Node(
+ 'line',
+ PropertyNode('name', 'http_port'),
+ Node('arguments', PropertyNode('1', '3128'))
+ )
+
+
+class HTTPSPortData (BoundData):
+ def template(self):
+ return Node(
+ 'line',
+ PropertyNode('name', 'https_port'),
+ Node('arguments', PropertyNode('1', '3128'))
+ )
+
+
+class ArgumentData (BoundData):
+ def template(self, *args):
+ return PropertyNode(*args)
+
+
+def __bind_by_name(cls, prop, name, itemcls):
+ cls.bind_collection(
+ prop,
+ selector=lambda x: x.get('name').value == name,
+ item_class=itemcls
+ )
+
+__bind_by_name(SquidData, 'acl', 'acl', ACLData)
+__bind_by_name(SquidData, 'http_access', 'http_access', HTTPAccessData)
+__bind_by_name(SquidData, 'http_port', 'http_port', HTTPPortData)
+__bind_by_name(SquidData, 'https_port', 'https_port', HTTPSPortData)
+
+
+def __bind_first_arg(cls, prop):
+ cls.bind_attribute('value', prop, path=lambda x: x.get('arguments').children[0])
+
+
+def __bind_other_args(cls, prop, itemcls):
+ cls.bind_collection(
+ prop, path=lambda x: x.get('arguments'),
+ selector=lambda x: x.parent.children.index(x) > 0, item_class=itemcls
+ )
+
+__bind_first_arg(ACLData, 'name')
+__bind_other_args(ACLData, 'options', ArgumentData)
+
+__bind_first_arg(HTTPAccessData, 'mode')
+__bind_other_args(HTTPAccessData, 'options', ArgumentData)
+
+__bind_first_arg(HTTPPortData, 'port')
+__bind_other_args(HTTPPortData, 'options', ArgumentData)
+__bind_first_arg(HTTPSPortData, 'port')
+__bind_other_args(HTTPSPortData, 'options', ArgumentData)
+
+ArgumentData.bind_attribute('value', 'value')
diff --git a/reconfigure/items/supervisor.py b/reconfigure/items/supervisor.py
new file mode 100644
index 0000000..a8e462f
--- /dev/null
+++ b/reconfigure/items/supervisor.py
@@ -0,0 +1,22 @@
+from reconfigure.nodes import Node, PropertyNode
+from reconfigure.items.bound import BoundData
+
+
+class SupervisorData (BoundData):
+ pass
+
+
+class ProgramData (BoundData):
+ fields = ['command', 'autostart', 'autorestart', 'startsecs', 'startretries', \
+ 'user', 'directory', 'umask', 'environment']
+
+ def template(self):
+ return Node('program:new',
+ PropertyNode('command', '127.0.0.1'),
+ )
+
+
+SupervisorData.bind_collection('programs', item_class=ProgramData, selector=lambda x: x.name.startswith('program:'))
+ProgramData.bind_name('name', getter=lambda x: x[8:], setter=lambda x: 'program:%s' % x)
+for i in range(0, len(ProgramData.fields)):
+ ProgramData.bind_property(ProgramData.fields[i], ProgramData.fields[i], default_remove=[None, ''])
diff --git a/reconfigure/items/util.py b/reconfigure/items/util.py
new file mode 100644
index 0000000..0a615ed
--- /dev/null
+++ b/reconfigure/items/util.py
@@ -0,0 +1,3 @@
+yn_getter = lambda x: x == 'yes'
+
+yn_setter = lambda x: 'yes' if x else 'no'