summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/client/tools/augeas.txt33
-rw-r--r--schemas/augeas.xsd9
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/Augeas.py31
-rw-r--r--testsuite/Testsrc/Testlib/TestClient/TestTools/TestPOSIX/TestAugeas.py49
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>&lt;Test/&gt;</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")