diff options
-rw-r--r-- | doc/server/plugins/generators/decisions.txt | 25 | ||||
-rw-r--r-- | schemas/decisions.xsd | 77 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Decisions.py | 58 | ||||
-rwxr-xr-x | tools/upgrade/1.4/migrate_decisions.py | 82 |
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()) |