diff options
-rw-r--r-- | doc/client/tools/augeas.txt | 33 | ||||
-rw-r--r-- | schemas/augeas.xsd | 9 | ||||
-rw-r--r-- | src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py | 31 | ||||
-rw-r--r-- | testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestAugeas.py | 49 |
4 files changed, 81 insertions, 41 deletions
diff --git a/doc/client/tools/augeas.txt b/doc/client/tools/augeas.txt index 94ed9066f..6fed5f5ce 100644 --- a/doc/client/tools/augeas.txt +++ b/doc/client/tools/augeas.txt @@ -26,11 +26,20 @@ give it a sequence of commands: The commands are run in document order. There's no need to do an explicit ``save`` at the end. -Each of these commands will only be run if the path does not already -have the given setting. That is, the ip address for the first host -record will only be set to ``192.168.0.1`` if it's not set to that -value already. Its canonical name will only be set to -``pigiron.example.com`` if it's not that already; and so on. +These commands will be run if any of the paths do not already +have the given setting. In other words, if any command has not +already been run, they will all be run. + +So, if the first host already has all of the specified settings, then +that Path will verify successfully and nothing will be changed. But +suppose the first host looks like this:: + + 192.168.0.1 pigiron.example.com pigiron + +All that is missing is the second alias, ``piggy``. The entire Augeas +script will be run in this case. It's important, then, to ensure that +all commands you use are idempotent. (For instance, the ``Move`` and +``Insert`` commands are unlikely to be useful.) The Augeas paths are all relative to ``/files/etc/hosts``. @@ -39,6 +48,20 @@ tags are: ``Remove``, ``Move``, ``Set``, ``Clear``, ``SetMulti``, and ``Insert``. Refer to the official Augeas docs or the `Schema`_ below for details on the commands. +The Augeas tool also supports one additional directive, ``Initial``, +for setting initial file content when a file does not exist. For +instance, the ``Xml`` lens fails to parse a file that does not exist, +and, as a result, you cannot add content to it. You can use +``Initial`` to circumvent this issue: + +.. code-block:: xml + + <Path type="augeas" name="/etc/test.xml" lens="Xml" + owner="root" group="root" mode="0640"> + <Initial><Test/></Initial> + <Set path="Test/#text" value="text content"/> + </Path> + Editing files outside the default load path =========================================== diff --git a/schemas/augeas.xsd b/schemas/augeas.xsd index 0ede106f3..df27f91cc 100644 --- a/schemas/augeas.xsd +++ b/schemas/augeas.xsd @@ -173,6 +173,15 @@ </xsd:documentation> </xsd:annotation> <xsd:choice> + <xsd:element name="Initial" type="xsd:string"> + <xsd:annotation> + <xsd:documentation> + Specify initial content for a file, which will be created + before Augeas commands are applied if a file doesn't + exist. + </xsd:documentation> + </xsd:annotation> + </xsd:element> <xsd:element name="Remove" type="AugeasRemoveCommand"> <xsd:annotation> <xsd:documentation> diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py b/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py index 81c948d0d..187b4d77c 100644 --- a/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py +++ b/src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py @@ -4,6 +4,7 @@ import sys import Bcfg2.Client.XML from augeas import Augeas from Bcfg2.Client.Tools.POSIX.base import POSIXTool +from Bcfg2.Client.Tools.POSIX.File import POSIXFile class AugeasCommand(object): @@ -187,13 +188,14 @@ class Insert(AugeasCommand): class POSIXAugeas(POSIXTool): """ Handle <Path type='augeas'...> entries. See :ref:`client-tools-augeas`. """ - - __handles__ = [('Path', 'augeas')] - __req__ = {'Path': ['type', 'name', 'setting', 'value']} + __req__ = ['name', 'mode', 'owner', 'group'] def __init__(self, logger, setup, config): POSIXTool.__init__(self, logger, setup, config) self._augeas = dict() + # file tool for setting initial values of files that don't + # exist + self.filetool = POSIXFile(logger, setup, config) def get_augeas(self, entry): """ Get an augeas object for the given entry. """ @@ -214,9 +216,9 @@ class POSIXAugeas(POSIXTool): return self._augeas[entry.get("name")] def fully_specified(self, entry): - return entry.text is not None + return len(entry.getchildren()) != 0 - def get_commands(self, entry, unverified=False): + def get_commands(self, entry): """ Get a list of commands to verify or install. @param entry: The entry to get commands from. @@ -229,7 +231,7 @@ class POSIXAugeas(POSIXTool): """ rv = [] for cmd in entry.iterchildren(): - if unverified and cmd.get("verified", "false") != "false": + if cmd.tag == "Initial": continue if cmd.tag in globals(): rv.append(globals()[cmd.tag](cmd, self.get_augeas(entry), @@ -266,7 +268,17 @@ class POSIXAugeas(POSIXTool): def install(self, entry): rv = True - for cmd in self.get_commands(entry, unverified=True): + if entry.get("current_exists", "true") == "false": + initial = entry.find("Initial") + if initial is not None: + self.logger.debug("Augeas: Setting initial data for %s" % + entry.get("name")) + file_entry = Bcfg2.Client.XML.Element("Path", **entry.attrib) + file_entry.text = initial.text + self.filetool.install(file_entry) + # re-parse the file + self.get_augeas(entry).load() + for cmd in self.get_commands(entry): try: cmd.install() except: # pylint: disable=W0702 @@ -277,8 +289,7 @@ class POSIXAugeas(POSIXTool): 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])) + 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 diff --git a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestAugeas.py b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestAugeas.py index 9b25499fe..b8534f5a8 100644 --- a/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestAugeas.py +++ b/testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestAugeas.py @@ -74,7 +74,7 @@ if can_skip or HAS_AUGEAS: def tearDown(self): tmpfile = getattr(self, "tmpfile", None) - if tmpfile: + if tmpfile and os.path.exists(tmpfile): os.unlink(tmpfile) def test_fully_specified(self): @@ -83,7 +83,7 @@ if can_skip or HAS_AUGEAS: entry = lxml.etree.Element("Path", name="/test", type="augeas") self.assertFalse(ptool.fully_specified(entry)) - entry.text = "text" + lxml.etree.SubElement(entry, "Set", path="/test", value="test") self.assertTrue(ptool.fully_specified(entry)) def test_install(self): @@ -138,12 +138,14 @@ if can_skip or HAS_AUGEAS: self._verify(self.applied_commands.values()) @patch("Bcfg2.Client.Tools.POSIX.Augeas.POSIXTool.install") - def _install(self, commands, expected, mock_install): + def _install(self, commands, expected, mock_install, **attrs): ptool = self.get_obj() mock_install.return_value = True entry = lxml.etree.Element("Path", name=self.tmpfile, type="augeas", lens="Xml") + for key, val in attrs.items(): + entry.set(key, val) entry.extend(commands) self.assertTrue(ptool.install(entry)) @@ -156,8 +158,7 @@ if can_skip or HAS_AUGEAS: expected = copy.deepcopy(test_xdata) expected.find("Text").text = "Changed content" self._install([lxml.etree.Element("Set", path="Test/Text/#text", - value="Changed content", - verified="false")], + value="Changed content")], expected) def test_install_set_new(self): @@ -166,30 +167,16 @@ if can_skip or HAS_AUGEAS: newtext = lxml.etree.SubElement(expected, "NewText") newtext.text = "new content" self._install([lxml.etree.Element("Set", path="Test/NewText/#text", - value="new content", - verified="false")], + value="new content")], expected) - def test_install_only_verified(self): - """ Test that only unverified commands are installed """ - expected = copy.deepcopy(test_xdata) - newtext = lxml.etree.SubElement(expected, "NewText") - newtext.text = "new content" - self._install( - [lxml.etree.Element("Set", path="Test/NewText/#text", - value="new content", verified="false"), - lxml.etree.Element("Set", path="Test/Bogus/#text", - value="bogus", verified="true")], - expected) - def test_install_remove(self): """ Test removing a node """ expected = copy.deepcopy(test_xdata) expected.remove(expected.find("Attrs")) self._install( [lxml.etree.Element("Remove", - path='Test/*[#attribute/foo = "foo"]', - verified="false")], + path='Test/*[#attribute/foo = "foo"]')], expected) def test_install_move(self): @@ -199,8 +186,7 @@ if can_skip or HAS_AUGEAS: expected.append(foo) self._install( [lxml.etree.Element("Move", source='Test/Children/Foo', - destination='Test/Foo', - verified="false")], + destination='Test/Foo')], expected) def test_install_clear(self): @@ -228,7 +214,7 @@ if can_skip or HAS_AUGEAS: [lxml.etree.Element( "SetMulti", value="same", base='Test/Children[#attribute/identical = "true"]', - sub="Thing/#text", verified="false")], + sub="Thing/#text")], expected) def test_install_insert(self): @@ -242,9 +228,20 @@ if can_skip or HAS_AUGEAS: [lxml.etree.Element( "Insert", path='Test/Children[#attribute/identical = "true"]/Thing[2]', - label="Thing", where="after", verified="false"), + label="Thing", where="after"), lxml.etree.Element( "Set", path='Test/Children[#attribute/identical = "true"]/Thing[3]/#text', - value="three", verified="false")], + value="three")], expected) + + def test_install_initial(self): + """ Test creating initial content and then modifying it """ + os.unlink(self.tmpfile) + expected = copy.deepcopy(test_xdata) + expected.find("Text").text = "Changed content" + initial = lxml.etree.Element("Initial") + initial.text = test_data + modify = lxml.etree.Element("Set", path="Test/Text/#text", + value="Changed content") + self._install([initial, modify], expected, current_exists="false") |