summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2013-02-05 08:58:41 -0500
committerChris St. Pierre <chris.a.st.pierre@gmail.com>2013-02-05 08:58:41 -0500
commitf05d66c4858f9757b1a372f0a5de2c956c058f00 (patch)
treeda2775f1c99ce2a930a720cb3957663c91442ffe
parentc3c449f509e5f849212e853b410b7b74341d3e1e (diff)
downloadbcfg2-f05d66c4858f9757b1a372f0a5de2c956c058f00.tar.gz
bcfg2-f05d66c4858f9757b1a372f0a5de2c956c058f00.tar.bz2
bcfg2-f05d66c4858f9757b1a372f0a5de2c956c058f00.zip
Decisions: use StructFile instead of host- or group-specific XML files
-rw-r--r--doc/server/plugins/generators/decisions.txt25
-rw-r--r--schemas/decisions.xsd77
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Decisions.py58
-rwxr-xr-xtools/upgrade/1.4/migrate_decisions.py82
4 files changed, 169 insertions, 73 deletions
diff --git a/doc/server/plugins/generators/decisions.txt b/doc/server/plugins/generators/decisions.txt
index 9a40ab8fd..f0afeba0a 100644
--- a/doc/server/plugins/generators/decisions.txt
+++ b/doc/server/plugins/generators/decisions.txt
@@ -29,18 +29,23 @@ client's whitelists or blacklists.
is not used. See `Decision Mode`_ below.
The Decisions plugin uses a directory in the Bcfg2 repository called
-Decisions. Files in the Decisions subdirectory are named similarly to
-files managed by Cfg and Probes, so you can use host- and
-group-specific files and the like after their basename. File basenames
-are either ``whitelist`` or ``blacklist``. These files have a simple
-format; the following is an example.
+Decisions, which may contain two files: ``whitelist.xml`` and
+``blacklist.xml``. These files have a simple format:
+
+.. xml:type:: DecisionsType
+ :linktotype:
+ :noautodep: py:genshiElements
+
+For example:
.. code-block:: xml
- $ cat Decisions/whitelist
+ $ cat Decisions/whitelist.xml
<Decisions>
<Decision type='Service' name='*'/>
- <Decision type='Path' name='/etc/apt/apt.conf'/>
+ <Group name="debian">
+ <Decision type='Path' name='/etc/apt/apt.conf'/>
+ </Group>
</Decisions>
This example, included as a whitelist due to its name, enables all services,
@@ -60,12 +65,6 @@ list. This list is sent to the client.
control these via their respective options (``-I`` or ``-n``, for
example).
-To add syntax highlighting to Decisions files in vim and emacs, you
-can add comments such as this::
-
- <Decisions><!--*- mode: xml; -*-->
- <!-- vim: set ft=xml : -->
-
Decision Mode
=============
diff --git a/schemas/decisions.xsd b/schemas/decisions.xsd
index 30115b367..c87d2a984 100644
--- a/schemas/decisions.xsd
+++ b/schemas/decisions.xsd
@@ -1,5 +1,6 @@
-<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xml:lang="en">
-
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:py="http://genshi.edgewall.org/" xml:lang="en">
+
<xsd:annotation>
<xsd:documentation>
decision list schema for bcfg2
@@ -7,16 +8,64 @@
</xsd:documentation>
</xsd:annotation>
- <xsd:element name='Decisions'>
- <xsd:complexType>
- <xsd:choice minOccurs='0' maxOccurs='unbounded'>
- <xsd:element name='Decision'>
- <xsd:complexType>
- <xsd:attribute name='type' type='xsd:string' use='required'/>
- <xsd:attribute name='name' type='xsd:string' use='required'/>
- </xsd:complexType>
- </xsd:element>
- </xsd:choice>
- </xsd:complexType>
- </xsd:element>
+ <xsd:import namespace="http://genshi.edgewall.org/"
+ schemaLocation="genshi.xsd"/>
+
+ <xsd:complexType name="DecisionsGroupType">
+ <xsd:annotation>
+ <xsd:documentation>
+ A **DecisionsGroupType** is a tag used to provide logic.
+ Child entries of a DecisionsGroupType tag only apply to
+ machines that match the condition specified -- either
+ membership in a group, or a matching client name.
+ :xml:attribute:`DecisionsGroupType:negate` can be set to
+ negate the sense of the match.
+ </xsd:documentation>
+ </xsd:annotation>
+ <xsd:choice minOccurs="1" maxOccurs="unbounded">
+ <xsd:element name="Decisions" type="DecisionsType"/>
+ <xsd:element name="Decision" type="DecisionType"/>
+ <xsd:element name="Group" type="DecisionsGroupType"/>
+ <xsd:element name="Client" type="DecisionsGroupType"/>
+ <xsd:group ref="py:genshiElements"/>
+ </xsd:choice>
+ <xsd:attribute name='name' type='xsd:string'>
+ <xsd:annotation>
+ <xsd:documentation>
+ The name of the client or group to match on. Child entries
+ will only apply to this client or group (unless
+ :xml:attribute:`DecisionsGroupType:negate` is set).
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attribute name='negate' type='xsd:boolean'>
+ <xsd:annotation>
+ <xsd:documentation>
+ Negate the sense of the match, so that child entries only
+ apply to a client if it is not a member of the given group
+ or does not have the given name.
+ </xsd:documentation>
+ </xsd:annotation>
+ </xsd:attribute>
+ <xsd:attributeGroup ref="py:genshiAttrs"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="DecisionType">
+ <xsd:attribute name='type' type='xsd:string' use='required'/>
+ <xsd:attribute name='name' type='xsd:string' use='required'/>
+ <xsd:attributeGroup ref="py:genshiAttrs"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="DecisionsType">
+ <xsd:choice minOccurs='0' maxOccurs='unbounded'>
+ <xsd:element name="Decisions" type="DecisionsType"/>
+ <xsd:element name="Decision" type="DecisionType"/>
+ <xsd:element name="Group" type="DecisionsGroupType"/>
+ <xsd:element name="Client" type="DecisionsGroupType"/>
+ <xsd:group ref="py:genshiElements"/>
+ </xsd:choice>
+ <xsd:attributeGroup ref="py:genshiAttrs"/>
+ </xsd:complexType>
+
+ <xsd:element name='Decisions' type="DecisionsType"/>
</xsd:schema>
diff --git a/src/lib/Bcfg2/Server/Plugins/Decisions.py b/src/lib/Bcfg2/Server/Plugins/Decisions.py
index a7ef25a84..a67a356d4 100644
--- a/src/lib/Bcfg2/Server/Plugins/Decisions.py
+++ b/src/lib/Bcfg2/Server/Plugins/Decisions.py
@@ -2,67 +2,33 @@
blacklist certain entries. """
import os
-import sys
-import lxml.etree
import Bcfg2.Server.Plugin
import Bcfg2.Server.FileMonitor
-class DecisionFile(Bcfg2.Server.Plugin.SpecificData):
+class DecisionFile(Bcfg2.Server.Plugin.StructFile):
""" Representation of a Decisions XML file """
- def __init__(self, name, specific, encoding):
- Bcfg2.Server.Plugin.SpecificData.__init__(self, name, specific,
- encoding)
- self.contents = None
-
- def handle_event(self, event):
- Bcfg2.Server.Plugin.SpecificData.handle_event(self, event)
- self.contents = lxml.etree.XML(self.data)
-
- def get_decisions(self):
+ def get_decisions(self, metadata):
""" Get a list of whitelist or blacklist tuples """
+ if self.xdata is None:
+ # no white/blacklist has been read yet, probably because
+ # it doesn't exist
+ return []
return [(x.get('type'), x.get('name'))
- for x in self.contents.xpath('.//Decision')]
+ for x in self.XMLMatch(metadata).xpath('.//Decision')]
-class Decisions(Bcfg2.Server.Plugin.EntrySet,
- Bcfg2.Server.Plugin.Plugin,
+class Decisions(Bcfg2.Server.Plugin.Plugin,
Bcfg2.Server.Plugin.Decision):
- """ Decisions plugin
-
- Arguments:
- - `core`: Bcfg2.Core instance
- - `datastore`: File repository location
- """
- basename_is_regex = True
+ """ Decisions plugin """
__author__ = 'bcfg-dev@mcs.anl.gov'
def __init__(self, core, datastore):
Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
Bcfg2.Server.Plugin.Decision.__init__(self)
-
- Bcfg2.Server.Plugin.EntrySet.__init__(self, '(white|black)list',
- self.data,
- DecisionFile,
- core.setup['encoding'])
- try:
- Bcfg2.Server.FileMonitor.get_fam().AddMonitor(self.data, self)
- except OSError:
- err = sys.exc_info()[1]
- msg = 'Adding filemonitor for %s failed: %s' % (self.data, err)
- self.logger.error(msg)
- raise Bcfg2.Server.Plugin.PluginInitError(msg)
-
- def HandleEvent(self, event):
- """ Handle events on Decision files by passing them off to
- EntrySet.handle_event """
- if event.filename != self.path:
- return self.handle_event(event)
+ self.whitelist = DecisionFile(os.path.join(self.data, "whitelist.xml"))
+ self.blacklist = DecisionFile(os.path.join(self.data, "blacklist.xml"))
def GetDecisions(self, metadata, mode):
- ret = []
- for cdt in self.get_matching(metadata):
- if os.path.basename(cdt).startswith(mode):
- ret.extend(cdt.get_decisions())
- return ret
+ return getattr(self, mode).get_decision(metadata)
diff --git a/tools/upgrade/1.4/migrate_decisions.py b/tools/upgrade/1.4/migrate_decisions.py
new file mode 100755
index 000000000..f7072783a
--- /dev/null
+++ b/tools/upgrade/1.4/migrate_decisions.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+
+import os
+import re
+import sys
+import glob
+import lxml.etree
+import Bcfg2.Options
+from Bcfg2.Server import XMLParser
+
+
+SPECIFIC = re.compile(r'.*\/(white|black)list'
+ r'(\.(H_(?P<host>.*)|G\d+_(?P<group>.*)))?$')
+
+
+def convert(files, xdata):
+ hosts = []
+ groups = []
+ for oldfile in files:
+ spec = SPECIFIC.match(oldfile)
+ if spec and spec.group('host'):
+ hosts.append(spec.group('host'))
+ elif spec and spec.group('group'):
+ groups.append(spec.group('group'))
+
+ for oldfile in files:
+ print("Converting %s" % oldfile)
+ spec = SPECIFIC.match(oldfile)
+ if not spec:
+ print("Skipping unknown file %s" % oldfile)
+ continue
+
+ parent = xdata
+ if spec.group('host'):
+ for host in hosts:
+ if host != spec.group('host'):
+ parent = lxml.etree.SubElement(parent, "Client",
+ name=host, negate="true")
+ parent = lxml.etree.SubElement(parent, "Client",
+ name=spec.group('host'))
+ elif spec.group('group'):
+ for host in hosts:
+ parent = lxml.etree.SubElement(parent, "Client",
+ name=host, negate="true")
+ for group in groups:
+ if group != spec.group('group'):
+ parent = lxml.etree.SubElement(parent, "Group",
+ name=group, negate="true")
+ parent = lxml.etree.SubElement(parent, "Group",
+ name=spec.group('group'))
+ parent.append(lxml.etree.Comment("Converted from %s" % oldfile))
+ olddata = lxml.etree.parse(oldfile, parser=Bcfg2.Server.XMLParser)
+ for decision in olddata.xpath('//Decision'):
+ parent.append(decision)
+ return xdata
+
+
+def main():
+ opts = dict(repo=Bcfg2.Options.SERVER_REPOSITORY,
+ configfile=Bcfg2.Options.CFILE)
+ setup = Bcfg2.Options.load_option_parser(opts)
+ setup.parse(sys.argv[1:])
+
+ datadir = os.path.join(setup['repo'], 'Decisions')
+ whitelist = lxml.etree.Element("Decisions")
+ blacklist = lxml.etree.Element("Decisions")
+ if os.path.exists(datadir):
+ convert(glob.glob(os.path.join(datadir, 'whitelist*')),
+ whitelist)
+ convert(glob.glob(os.path.join(datadir, 'blacklist*')),
+ blacklist)
+
+ print("Writing %s" % os.path.join(datadir, "whitelist.xml"))
+ open(os.path.join(datadir, "whitelist.xml"),
+ 'w').write(lxml.etree.tostring(whitelist, pretty_print=True))
+ print("Writing %s" % os.path.join(datadir, "blacklist.xml"))
+ open(os.path.join(datadir, "blacklist.xml"),
+ 'w').write(lxml.etree.tostring(blacklist, pretty_print=True))
+
+
+if __name__ == '__main__':
+ sys.exit(main())