diff options
author | Andrew Shadura <andrew@shadura.me> | 2015-08-20 15:58:26 +0200 |
---|---|---|
committer | Andrew Shadura <andrew@shadura.me> | 2015-08-20 15:58:26 +0200 |
commit | ff1408420159488a106492ccd11dd234967029b6 (patch) | |
tree | 473420cee1c5229a427ec4cafead1aa6c0a26800 /reconfigure/items |
Imported Upstream version 0.1.29
Diffstat (limited to 'reconfigure/items')
-rw-r--r-- | reconfigure/items/__init__.py | 1 | ||||
-rw-r--r-- | reconfigure/items/ajenti.py | 54 | ||||
-rw-r--r-- | reconfigure/items/bind9.py | 25 | ||||
-rw-r--r-- | reconfigure/items/bound.py | 302 | ||||
-rw-r--r-- | reconfigure/items/crontab.py | 64 | ||||
-rw-r--r-- | reconfigure/items/ctdb.py | 50 | ||||
-rw-r--r-- | reconfigure/items/dhcpd.py | 35 | ||||
-rw-r--r-- | reconfigure/items/exports.py | 30 | ||||
-rw-r--r-- | reconfigure/items/fstab.py | 26 | ||||
-rw-r--r-- | reconfigure/items/group.py | 15 | ||||
-rw-r--r-- | reconfigure/items/hosts.py | 26 | ||||
-rw-r--r-- | reconfigure/items/iptables.py | 120 | ||||
-rw-r--r-- | reconfigure/items/netatalk.py | 38 | ||||
-rw-r--r-- | reconfigure/items/nsd.py | 23 | ||||
-rw-r--r-- | reconfigure/items/passwd.py | 15 | ||||
-rw-r--r-- | reconfigure/items/resolv.py | 19 | ||||
-rw-r--r-- | reconfigure/items/samba.py | 59 | ||||
-rw-r--r-- | reconfigure/items/squid.py | 95 | ||||
-rw-r--r-- | reconfigure/items/supervisor.py | 22 | ||||
-rw-r--r-- | reconfigure/items/util.py | 3 |
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' |