summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2013-03-28 15:24:16 -0400
committerChris St. Pierre <chris.a.st.pierre@gmail.com>2013-03-28 15:24:16 -0400
commitaf98f300fdf7362eb792df1456bf9f4a2fbc90bb (patch)
treeb79b6f5ca9fcf6390255b1aa1cebf57345e236a1
parent0987e2a52629607a9868201085e9ba5b995f8fb8 (diff)
downloadbcfg2-af98f300fdf7362eb792df1456bf9f4a2fbc90bb.tar.gz
bcfg2-af98f300fdf7362eb792df1456bf9f4a2fbc90bb.tar.bz2
bcfg2-af98f300fdf7362eb792df1456bf9f4a2fbc90bb.zip
new GroupLogic plugin
-rw-r--r--doc/server/plugins/connectors/grouplogic.txt122
-rw-r--r--schemas/grouplogic.xsd110
-rw-r--r--src/lib/Bcfg2/Server/Lint/Validate.py3
-rw-r--r--src/lib/Bcfg2/Server/Plugins/GroupLogic.py39
4 files changed, 273 insertions, 1 deletions
diff --git a/doc/server/plugins/connectors/grouplogic.txt b/doc/server/plugins/connectors/grouplogic.txt
new file mode 100644
index 000000000..b9a5b00d6
--- /dev/null
+++ b/doc/server/plugins/connectors/grouplogic.txt
@@ -0,0 +1,122 @@
+.. -*- mode: rst -*-
+
+.. _server-plugins-connectors-grouplogic:
+
+==========
+GroupLogic
+==========
+
+.. versionadded:: 1.3.2
+
+GroupLogic is a connector plugin that lets you use an XML Genshi
+template to dynamically set additional groups for clients.
+
+Usage
+=====
+
+To use the GroupLogic plugin, first do ``mkdir
+/var/lib/bcfg2/GroupLogic``. Add ``GroupLogic`` to your ``plugins``
+line in ``/etc/bcfg2.conf``. Next, create
+``/var/lib/bcfg2/GroupLogic/groups.xml``:
+
+.. code-block:: xml
+
+ <GroupLogic xmlns:py="http://genshi.edgewall.org/">
+ </GroupLogic>
+
+``groups.xml`` is structured very similarly to the
+:ref:`server-plugins-grouping-metadata` ``groups.xml``. A Group tag
+that contains no children is a declaration of membership; a Group or
+Client tag that does contain children is a conditional.
+
+Unlike ``Metadata/groups.xml``, GroupLogic supports genshi templating,
+so you can dynamically create groups. ``GroupLogic/groups.xml`` is
+rendered for each client, and the groups set in it are added to the
+client metadata.
+
+.. note::
+
+ Also unlike ``Metadata/groups.xml``, GroupLogic can not be used to
+ associate bundles with clients directly, or to negate groups. But
+ you can use GroupLogic to assign a group that is associated with a
+ bundle in Metadata.
+
+Consider the case where you have four environments -- dev, test,
+staging, and production -- and four components to a web application --
+the frontend, the API, the database server, and the caching proxy. In
+order to make files specific to the component *and* to the
+environment, you need groups to describe each combination:
+webapp-frontend-dev, webapp-frontend-test, and so on. You *could* do
+this in ``Metadata/groups.xml``:
+
+.. code-block:: xml
+
+ <Groups>
+ <Group name="webapp-frontend">
+ <Group name="dev">
+ <Group name="webapp-frontend-dev"/>
+ </Group>
+ <Group name="test">
+ <Group name="webapp-frontend-test"/>
+ </Group>
+ ...
+ </Group>
+ <Group name="webapp-api">
+ ...
+ </Group>
+ ...
+ </Groups>
+
+Creating the sixteen groups this way is incredibly tedious, and this
+is a quite *small* site. GroupLogic can automate this process.
+
+Assume that we've declared the groups thusly in
+``Metadata/groups.xml``:
+
+.. code-block:: xml
+
+ <Groups>
+ <Group name="webapp-frontend" category="webapp-component"/>
+ <Group name="webapp-api" category="webapp-component"/>
+ <Group name="webapp-db" category="webapp-component"/>
+ <Group name="webapp-proxy" category="webapp-component"/>
+ <Group name="dev" category="environment"/>
+ <Group name="test" category="environment"/>
+ <Group name="staging" category="environment"/>
+ <Group name="prod" category="environment"/>
+ </Groups>
+
+One way to automate the creation of the groups would be to simply
+generate the tedious config:
+
+.. code-block:: xml
+
+ <GroupLogic xmlns:py="http://genshi.edgewall.org/">
+ <py:for each="component in metadata.query.all_groups_in_category("webapp-component")>
+ <Group name="${component}">
+ <py:for each="env in metadata.query.all_groups_in_category("environment")>
+ <Group name="${env}">
+ <Group name="${component}-${env}"/>
+ </Group>
+ </py:for>
+ </Group>
+ </py:for>
+ </GroupLogic>
+
+But, since ``GroupLogic/groups.xml`` is rendered for each client
+individually, there's a more elegant way to accomplish the same thing:
+
+.. code-block:: xml
+
+ <GroupLogic xmlns:py="http://genshi.edgewall.org/">
+ <?python
+component = metadata.group_in_category("webapp-component")
+env = metadata.group_in_category("environment")
+ ?>
+ <py:if test="component and env">
+ <Group name="${component}-${env}"/>
+ </py:if>
+ </GroupLogic>
+
+This gets only the component and environment for the current client,
+and, if both are set, sets the single appropriate group.
diff --git a/schemas/grouplogic.xsd b/schemas/grouplogic.xsd
new file mode 100644
index 000000000..bf43bceb3
--- /dev/null
+++ b/schemas/grouplogic.xsd
@@ -0,0 +1,110 @@
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:py="http://genshi.edgewall.org/" xml:lang="en">
+
+ <xsd:annotation>
+ <xsd:documentation>
+ GroupLogic schema for bcfg2
+ </xsd:documentation>
+ </xsd:annotation>
+
+ <xsd:import namespace="http://genshi.edgewall.org/"
+ schemaLocation="genshi.xsd"/>
+
+ <xsd:complexType name="GroupLogicDeclarationType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A **GroupLogicDeclarationType** declares a Group to be added
+ to a client.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:attribute type='xsd:string' name='name' use='required'>
+ <xsd:annotation>
+ <xsd:documentation>
+ The group name
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attributeGroup ref="py:genshiAttrs"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="GroupLogicType">
+ <xsd:annotation>
+ <xsd:documentation>
+ The top-level tag of a GroupLogic configuration file.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice minOccurs="1" maxOccurs="unbounded">
+ <xsd:group ref="py:genshiElements"/>
+ <xsd:element name='Group' type='GroupLogicDeclarationType'/>
+ <xsd:element name='Group' type='GroupLogicContainerType'>
+ <xsd:annotation>
+ <xsd:documentation>
+ Elements within Group tags only apply to clients that are
+ members of that group (or vice-versa; see #element_negate
+ below)
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name='Client' type='GroupLogicContainerType'>
+ <xsd:annotation>
+ <xsd:documentation>
+ Elements within Client tags only apply to the named client
+ (or vice-versa; see #element_negate below)
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ <xsd:element name='GroupLogic' type='GroupLogicType'>
+ <xsd:annotation>
+ <xsd:documentation>
+ Nesting GroupLogic tags is allowed in order to support
+ XInclude.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+ </xsd:choice>
+ <xsd:attributeGroup ref="py:genshiAttrs"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="GroupLogicContainerType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A **GroupLogicContainerType** is a tag used to provide logic.
+ Child entries of a GroupLogicContainerType tag only apply to
+ machines that match the condition specified -- either
+ membership in a group, or a matching client name.
+ :xml:attribute:`GroupLogicContainerType:negate` can be set to
+ negate the sense of the match.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:complexContent>
+ <xsd:extension base="GroupLogicType">
+ <xsd:attribute type='xsd:string' name='name' use='required'>
+ <xsd:annotation>
+ <xsd:documentation>
+ The group name
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute type='xsd:string' name='negate'>
+ <xsd:annotation>
+ <xsd:documentation>
+ Negate the sense of this group or client; i.e., entries
+ within this tag are only used on clients that are not
+ members of the group, or that have hostnames that do not
+ match.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ <xsd:element name='GroupLogic' type='GroupLogicType'>
+ <xsd:annotation>
+ <xsd:documentation>
+ A GroupLogic file is a genshi file that can be used to
+ dynamically add additional groups to a client.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:element>
+</xsd:schema>
diff --git a/src/lib/Bcfg2/Server/Lint/Validate.py b/src/lib/Bcfg2/Server/Lint/Validate.py
index a5f41c7af..14d17a1e6 100644
--- a/src/lib/Bcfg2/Server/Lint/Validate.py
+++ b/src/lib/Bcfg2/Server/Lint/Validate.py
@@ -40,7 +40,8 @@ class Validate(Bcfg2.Server.Lint.ServerlessPlugin):
"NagiosGen/config.xml": "nagiosgen.xsd",
"FileProbes/config.xml": "fileprobes.xsd",
"SSLCA/**/cert.xml": "sslca-cert.xsd",
- "SSLCA/**/key.xml": "sslca-key.xsd"
+ "SSLCA/**/key.xml": "sslca-key.xsd",
+ "GroupLogic/groups.xml": "grouplogic.xsd"
}
self.filelists = {}
diff --git a/src/lib/Bcfg2/Server/Plugins/GroupLogic.py b/src/lib/Bcfg2/Server/Plugins/GroupLogic.py
new file mode 100644
index 000000000..c572fee2b
--- /dev/null
+++ b/src/lib/Bcfg2/Server/Plugins/GroupLogic.py
@@ -0,0 +1,39 @@
+import os
+import lxml.etree
+import Bcfg2.Server.Plugin
+try:
+ from Bcfg2.Server.Plugins.Bundler import BundleTemplateFile
+except ImportError:
+ # BundleTemplateFile missing means that genshi is missing. we
+ # import genshi to get the _real_ error
+ import genshi # pylint: disable=W0611
+
+
+class GroupLogicConfig(BundleTemplateFile):
+ create = lxml.etree.Element("GroupLogic",
+ nsmap=dict(py="http://genshi.edgewall.org/"))
+
+ def __init__(self, name, fam):
+ BundleTemplateFile.__init__(self, name,
+ Bcfg2.Server.Plugin.Specificity(), None)
+ self.fam = fam
+ self.should_monitor = True
+ self.fam.AddMonitor(self.name, self)
+
+ def _match(self, item, metadata):
+ if item.tag == 'Group' and not len(item.getchildren()):
+ return [item]
+ return BundleTemplateFile._match(self, item, metadata)
+
+
+class GroupLogic(Bcfg2.Server.Plugin.Plugin,
+ Bcfg2.Server.Plugin.Connector):
+ def __init__(self, core, datastore):
+ Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
+ Bcfg2.Server.Plugin.Connector.__init__(self)
+ self.config = GroupLogicConfig(os.path.join(self.data, "groups.xml"),
+ core.fam)
+
+ def get_additional_groups(self, metadata):
+ return [el.get("name")
+ for el in self.config.get_xml_value(metadata).findall("Group")]