From 53f8eb67378f6a8054cb107e72b094f070d40c83 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 5 Dec 2013 09:58:18 -0500 Subject: Tools: new Augeas driver --- src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py | 211 +++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py (limited to 'src/lib/Bcfg2/Client/Tools/POSIX') diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py b/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py new file mode 100644 index 000000000..cda9a1e3b --- /dev/null +++ b/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py @@ -0,0 +1,211 @@ +""" Augeas driver """ + +import sys +import augeas +import Bcfg2.Client.XML +from Bcfg2.Client.Tools.POSIX.base import POSIXTool + + +class AugeasCommand(object): + def __init__(self, command, augeas, logger): + self.augeas = augeas + self.command = command + self.entry = self.command.getparent() + self.logger = logger + + def get_path(self, attr="path"): + return "/files/%s/%s" % (self.entry.get("name").strip("/"), + self.command.get(attr).lstrip("/")) + + def _exists(self, path): + return len(self.augeas.match(path)) > 1 + + def _verify_exists(self, path=None): + if path is None: + path = self.get_path() + self.logger.debug("Augeas: Verifying that '%s' exists" % path) + return self._exists(path) + + def _verify_not_exists(self, path=None): + if path is None: + path = self.get_path() + self.logger.debug("Augeas: Verifying that '%s' does not exist" % path) + return not self._exists(path) + + def _verify_set(self, expected, path=None): + if path is None: + path = self.get_path() + self.logger.debug("Augeas: Verifying '%s' == '%s'" % (path, expected)) + actual = self.augeas.get(path) + if actual == expected: + return True + else: + self.logger.debug("Augeas: '%s' failed verification: '%s' != '%s'" + % (path, actual, expected)) + return False + + def __str__(self): + return Bcfg2.Client.XML.tostring(self.command) + + +class Remove(AugeasCommand): + def verify(self): + return self._verify_not_exists() + + def install(self): + self.logger.debug("Augeas: Removing %s" % self.get_path()) + return self.augeas.remove(self.get_path()) + + +class Move(AugeasCommand): + def __init__(self, command, augeas, logger): + AugeasCommand.__init__(self, command, augeas, logger) + self.source = self.get_path("source") + self.dest = self.get_path("destination") + + def verify(self): + return (self._verify_not_exists(self.source), + self._verify_exists(self.dest)) + + def install(self): + self.logger.debug("Augeas: Moving %s to %s" % (self.source, self.dest)) + return self.augeas.move(self.source, self.dest) + + +class Set(AugeasCommand): + def __init__(self, command, augeas, logger): + AugeasCommand.__init__(self, command, augeas, logger) + self.value = self.command.get("value") + + def verify(self): + return self._verify_set(self.value) + + def install(self): + self.logger.debug("Augeas: Setting %s to %s" % (self.get_path(), + self.value)) + return self.augeas.set(self.get_path(), self.value) + + +class Clear(Set): + def __init__(self, command, augeas, logger): + Set.__init__(self, command, augeas, logger) + self.value = None + + +class SetMulti(AugeasCommand): + def __init__(self, command, augeas, logger): + AugeasCommand.__init__(self, command, augeas, logger) + self.sub = self.command.get("sub") + self.value = self.command.get("value") + self.base = self.get_path("base") + + def verify(self): + return all(self._verify_set(self.value, + path="%s/%s" % (path, self.sub)) + for path in self.augeas.match(self.base)) + + def install(self): + return self.augeas.setm(self.base, self.sub, self.value) + + +class Insert(AugeasCommand): + def __init__(self, command, augeas, logger): + AugeasCommand.__init__(self, command, augeas, logger) + self.label = self.command.get("label") + self.where = self.command.get("where", "before") + self.before = self.where == "before" + + def verify(self): + return self._verify_exists("%s/../%s" % (self.get_path(), self.label)) + + def install(self): + self.logger.debug("Augeas: Inserting new %s %s %s" % + (self.label, self.where, self.get_path())) + return self.augeas.insert(self.get_path(), self.label, self.before) + + +class POSIXAugeas(POSIXTool): + """ Handle entries. See + :ref:`client-tools-augeas`. """ + + __handles__ = [('Path', 'augeas')] + __req__ = {'Path': ['type', 'name', 'setting', 'value']} + + def __init__(self, logger, setup, config): + POSIXTool.__init__(self, logger, setup, config) + self._augeas = dict() + + def get_augeas(self, entry): + if entry.get("name") not in self._augeas: + aug = augeas.augeas() + if entry.get("lens"): + self.logger.debug("Augeas: Adding %s to include path for %s" % + (entry.get("name"), entry.get("lens"))) + incl = "/augeas/load/%s/incl" % entry.get("lens") + ilen = len(aug.match(incl)) + if ilen == 0: + self.logger.error("Augeas: Lens %s does not exist" % + entry.get("lens")) + else: + aug.set("%s[%s]" % (incl, ilen + 1), entry.get("name")) + aug.load() + self._augeas[entry.get("name")] = aug + return self._augeas[entry.get("name")] + + def fully_specified(self, entry): + return entry.text is not None + + def get_commands(self, entry, unverified=False): + rv = [] + for cmd in entry.iterchildren(): + if cmd.tag in globals(): + rv.append(globals()[cmd.tag](cmd, self.get_augeas(entry), + self.logger)) + else: + err = "Augeas: Unknown command %s in %s" % (cmd.tag, + entry.get("name")) + self.logger.error(err) + entry.set('qtext', "\n".join([entry.get('qtext', ''), err])) + return rv + + def verify(self, entry, modlist): + rv = True + for cmd in self.get_commands(entry): + try: + if not cmd.verify(): + err = "Augeas: Command has not been applied to %s: %s" % \ + (entry.get("name"), cmd) + self.logger.debug(err) + entry.set('qtext', "\n".join([entry.get('qtext', ''), + err])) + rv = False + cmd.command.set("verified", "false") + else: + cmd.command.set("verified", "true") + except: # pylint: disable=W0702 + err = "Augeas: Unexpected error verifying %s: %s: %s" % \ + (entry.get("name"), cmd, sys.exc_info()[1]) + self.logger.error(err) + entry.set('qtext', "\n".join([entry.get('qtext', ''), err])) + rv = False + cmd.command.set("verified", "false") + return POSIXTool.verify(self, entry, modlist) and rv + + def install(self, entry): + rv = True + for cmd in self.get_commands(entry, unverified=True): + try: + cmd.install() + except: # pylint: disable=W0702 + self.logger.error( + "Failure running Augeas command on %s: %s: %s" % + (entry.get("name"), cmd, sys.exc_info()[1])) + rv = False + try: + self.get_augeas(entry).save() + except: # pylint: disable=W0702 + self.logger.error( + "Failure saving Augeas changes to %s: %s" % + (entry.get("name"), sys.exc_info()[1])) + rv = False + return POSIXTool.install(self, entry) and rv -- cgit v1.2.3-1-g7c22 From c425991254dd11163455882fb8aaf918c9274c10 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 5 Dec 2013 14:47:52 -0500 Subject: POSIX: skip loading POSIX sub-tools that raise ImportError This mimics the behavior for "real" tools --- src/lib/Bcfg2/Client/Tools/POSIX/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/lib/Bcfg2/Client/Tools/POSIX') diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py b/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py index 7708c4f72..8d64cf84d 100644 --- a/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py +++ b/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py @@ -47,8 +47,11 @@ class POSIX(Bcfg2.Client.Tools.Tool): mname = submodule[1].rsplit('.', 1)[-1] if mname == 'base': continue - module = getattr(__import__(submodule[1]).Client.Tools.POSIX, - mname) + try: + module = getattr(__import__(submodule[1]).Client.Tools.POSIX, + mname) + except ImportError: + continue hdlr = getattr(module, "POSIX" + mname) if POSIXTool in hdlr.__mro__: # figure out what entry type this handler handles -- cgit v1.2.3-1-g7c22 From 5050cdeb3e7635b1d32d354c30c7acef5f1c9c43 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 5 Dec 2013 14:48:35 -0500 Subject: Augeas: Only install unverified commands --- src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/lib/Bcfg2/Client/Tools/POSIX') diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py b/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py index cda9a1e3b..55f3d5cf7 100644 --- a/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py +++ b/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py @@ -158,6 +158,8 @@ class POSIXAugeas(POSIXTool): def get_commands(self, entry, unverified=False): rv = [] for cmd in entry.iterchildren(): + if unverified and cmd.get("verified", "false") != "false": + continue if cmd.tag in globals(): rv.append(globals()[cmd.tag](cmd, self.get_augeas(entry), self.logger)) -- cgit v1.2.3-1-g7c22 From 81166aa3b38fe6f4554c72f3733f2d0153f18978 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 5 Dec 2013 14:48:42 -0500 Subject: Augeas: Added docstrings, fixed some minor pylint issues --- src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py | 111 +++++++++++++++++++++++------ 1 file changed, 91 insertions(+), 20 deletions(-) (limited to 'src/lib/Bcfg2/Client/Tools/POSIX') diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py b/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py index 55f3d5cf7..5e5412fed 100644 --- a/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py +++ b/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py @@ -7,36 +7,82 @@ from Bcfg2.Client.Tools.POSIX.base import POSIXTool class AugeasCommand(object): - def __init__(self, command, augeas, logger): - self.augeas = augeas + """ Base class for all Augeas command objects """ + + def __init__(self, command, augeas_obj, logger): + self._augeas = augeas_obj self.command = command self.entry = self.command.getparent() self.logger = logger def get_path(self, attr="path"): + """ Get a fully qualified path from the name of the parent entry and + the path given in this command tag. + + @param attr: The attribute to get the relative path from + @type attr: string + @returns: string - the fully qualified Augeas path + + """ return "/files/%s/%s" % (self.entry.get("name").strip("/"), self.command.get(attr).lstrip("/")) def _exists(self, path): - return len(self.augeas.match(path)) > 1 + """ Return True if a path exists in Augeas, False otherwise. + + Note that a False return can mean many things: A file that + doesn't exist, a node within the file that doesn't exist, no + lens to parse the file, etc. """ + return len(self._augeas.match(path)) > 1 def _verify_exists(self, path=None): + """ Verify that the given path exists, with friendly debug + logging. + + @param path: The path to verify existence of. Defaults to the + result of + :func:`Bcfg2.Client.Tools.POSIX.Augeas.AugeasCommand.getpath`. + @type path: string + @returns: bool - Whether or not the path exists + """ if path is None: path = self.get_path() self.logger.debug("Augeas: Verifying that '%s' exists" % path) return self._exists(path) def _verify_not_exists(self, path=None): + """ Verify that the given path does not exist, with friendly + debug logging. + + @param path: The path to verify existence of. Defaults to the + result of + :func:`Bcfg2.Client.Tools.POSIX.Augeas.AugeasCommand.getpath`. + @type path: string + @returns: bool - Whether or not the path does not exist. + (I.e., True if it does not exist, False if it does + exist.) + """ if path is None: path = self.get_path() self.logger.debug("Augeas: Verifying that '%s' does not exist" % path) return not self._exists(path) def _verify_set(self, expected, path=None): + """ Verify that the given path is set to the given value, with + friendly debug logging. + + @param expected: The expected value of the node. + @param path: The path to verify existence of. Defaults to the + result of + :func:`Bcfg2.Client.Tools.POSIX.Augeas.AugeasCommand.getpath`. + @type path: string + @returns: bool - Whether or not the path matches the expected value. + + """ if path is None: path = self.get_path() self.logger.debug("Augeas: Verifying '%s' == '%s'" % (path, expected)) - actual = self.augeas.get(path) + actual = self._augeas.get(path) if actual == expected: return True else: @@ -47,19 +93,29 @@ class AugeasCommand(object): def __str__(self): return Bcfg2.Client.XML.tostring(self.command) + def verify(self): + """ Verify that the command has been applied. """ + raise NotImplementedError + + def install(self): + """ Run the command. """ + raise NotImplementedError + class Remove(AugeasCommand): + """ Augeas ``rm`` command """ def verify(self): return self._verify_not_exists() def install(self): self.logger.debug("Augeas: Removing %s" % self.get_path()) - return self.augeas.remove(self.get_path()) + return self._augeas.remove(self.get_path()) class Move(AugeasCommand): - def __init__(self, command, augeas, logger): - AugeasCommand.__init__(self, command, augeas, logger) + """ Augeas ``move`` command """ + def __init__(self, command, augeas_obj, logger): + AugeasCommand.__init__(self, command, augeas_obj, logger) self.source = self.get_path("source") self.dest = self.get_path("destination") @@ -69,12 +125,13 @@ class Move(AugeasCommand): def install(self): self.logger.debug("Augeas: Moving %s to %s" % (self.source, self.dest)) - return self.augeas.move(self.source, self.dest) + return self._augeas.move(self.source, self.dest) class Set(AugeasCommand): - def __init__(self, command, augeas, logger): - AugeasCommand.__init__(self, command, augeas, logger) + """ Augeas ``set`` command """ + def __init__(self, command, augeas_obj, logger): + AugeasCommand.__init__(self, command, augeas_obj, logger) self.value = self.command.get("value") def verify(self): @@ -83,18 +140,20 @@ class Set(AugeasCommand): def install(self): self.logger.debug("Augeas: Setting %s to %s" % (self.get_path(), self.value)) - return self.augeas.set(self.get_path(), self.value) + return self._augeas.set(self.get_path(), self.value) class Clear(Set): - def __init__(self, command, augeas, logger): - Set.__init__(self, command, augeas, logger) + """ Augeas ``clear`` command """ + def __init__(self, command, augeas_obj, logger): + Set.__init__(self, command, augeas_obj, logger) self.value = None class SetMulti(AugeasCommand): - def __init__(self, command, augeas, logger): - AugeasCommand.__init__(self, command, augeas, logger) + """ Augeas ``setm`` command """ + def __init__(self, command, augeas_obj, logger): + AugeasCommand.__init__(self, command, augeas_obj, logger) self.sub = self.command.get("sub") self.value = self.command.get("value") self.base = self.get_path("base") @@ -102,15 +161,16 @@ class SetMulti(AugeasCommand): def verify(self): return all(self._verify_set(self.value, path="%s/%s" % (path, self.sub)) - for path in self.augeas.match(self.base)) + for path in self._augeas.match(self.base)) def install(self): - return self.augeas.setm(self.base, self.sub, self.value) + return self._augeas.setm(self.base, self.sub, self.value) class Insert(AugeasCommand): - def __init__(self, command, augeas, logger): - AugeasCommand.__init__(self, command, augeas, logger) + """ Augeas ``ins`` command """ + def __init__(self, command, augeas_obj, logger): + AugeasCommand.__init__(self, command, augeas_obj, logger) self.label = self.command.get("label") self.where = self.command.get("where", "before") self.before = self.where == "before" @@ -121,7 +181,7 @@ class Insert(AugeasCommand): def install(self): self.logger.debug("Augeas: Inserting new %s %s %s" % (self.label, self.where, self.get_path())) - return self.augeas.insert(self.get_path(), self.label, self.before) + return self._augeas.insert(self.get_path(), self.label, self.before) class POSIXAugeas(POSIXTool): @@ -136,6 +196,7 @@ class POSIXAugeas(POSIXTool): self._augeas = dict() def get_augeas(self, entry): + """ Get an augeas object for the given entry. """ if entry.get("name") not in self._augeas: aug = augeas.augeas() if entry.get("lens"): @@ -156,6 +217,16 @@ class POSIXAugeas(POSIXTool): return entry.text is not None def get_commands(self, entry, unverified=False): + """ Get a list of commands to verify or install. + + @param entry: The entry to get commands from. + @type entry: lxml.etree._Element + @param unverified: Only get commands that failed verification. + @type unverified: bool + @returns: list of + :class:`Bcfg2.Client.Tools.POSIX.Augeas.AugeasCommand` + objects representing the commands. + """ rv = [] for cmd in entry.iterchildren(): if unverified and cmd.get("verified", "false") != "false": -- cgit v1.2.3-1-g7c22 From 1c335d124031b28d240559770c2a45059bb4e273 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 5 Dec 2013 15:01:30 -0500 Subject: Augeas: avoid deprecation warning --- src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/lib/Bcfg2/Client/Tools/POSIX') diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py b/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py index 5e5412fed..81c948d0d 100644 --- a/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py +++ b/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py @@ -1,8 +1,8 @@ """ Augeas driver """ import sys -import augeas import Bcfg2.Client.XML +from augeas import Augeas from Bcfg2.Client.Tools.POSIX.base import POSIXTool @@ -198,7 +198,7 @@ class POSIXAugeas(POSIXTool): def get_augeas(self, entry): """ Get an augeas object for the given entry. """ if entry.get("name") not in self._augeas: - aug = augeas.augeas() + aug = Augeas() if entry.get("lens"): self.logger.debug("Augeas: Adding %s to include path for %s" % (entry.get("name"), entry.get("lens"))) -- cgit v1.2.3-1-g7c22