summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2013-12-10 20:58:55 -0500
committerChris St. Pierre <chris.a.st.pierre@gmail.com>2013-12-11 10:51:54 -0500
commit94ba31279869d7052ba001e38927f9eecd0a636f (patch)
treed54894a50fbe4850996a3aa5fb254d6956cb92af
parent13fbef668878239dc5116f899a0c9791df81081e (diff)
downloadbcfg2-94ba31279869d7052ba001e38927f9eecd0a636f.tar.gz
bcfg2-94ba31279869d7052ba001e38927f9eecd0a636f.tar.bz2
bcfg2-94ba31279869d7052ba001e38927f9eecd0a636f.zip
Augeas improvements:
* Added ability to specify initial content for a file that doesn't exist, to avoid a messy situation where you'd have to probe for file existence and either use a Path type="file" or Path type="augeas" depending, and run Bcfg2 twice. * All commands in an Augeas path are run if *any* of them fail to verify. Previously, only commands that hadn't been run would be installed, but that had issues, particularly with the Clear command, which could pass verification but then be required during the installation phase anyway. * Miscellaneous bug fixes.
-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")