diff options
author | Sol Jerome <sol.jerome@gmail.com> | 2015-09-01 10:23:10 -0500 |
---|---|---|
committer | Sol Jerome <sol.jerome@gmail.com> | 2015-09-01 10:23:10 -0500 |
commit | 75466a831a92ab136a9cc2b6b875d20f0b00a889 (patch) | |
tree | e83728ee5a1f67e072e4d6fba0b7c71ecb352253 | |
parent | 824879eb85a1065e842930485778a594b914c77f (diff) | |
parent | 3259a1611edf97241b3a2d1bd585185907fe4e9c (diff) | |
download | bcfg2-75466a831a92ab136a9cc2b6b875d20f0b00a889.tar.gz bcfg2-75466a831a92ab136a9cc2b6b875d20f0b00a889.tar.bz2 bcfg2-75466a831a92ab136a9cc2b6b875d20f0b00a889.zip |
Merge branch 'rules-replace_name' of https://github.com/AlexanderS/bcfg2
-rw-r--r-- | doc/releases/1.4.0pre2.txt | 1 | ||||
-rw-r--r-- | doc/server/plugins/generators/rules.txt | 20 | ||||
-rw-r--r-- | doc/server/plugins/structures/defaults.txt | 7 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugin/helpers.py | 14 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Defaults.py | 12 | ||||
-rw-r--r-- | src/lib/Bcfg2/Server/Plugins/Rules.py | 27 | ||||
-rw-r--r-- | testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDefaults.py | 9 | ||||
-rw-r--r-- | testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py | 18 | ||||
-rw-r--r-- | testsuite/common.py | 19 |
9 files changed, 116 insertions, 11 deletions
diff --git a/doc/releases/1.4.0pre2.txt b/doc/releases/1.4.0pre2.txt index 1dcdf237b..1ed2ae70f 100644 --- a/doc/releases/1.4.0pre2.txt +++ b/doc/releases/1.4.0pre2.txt @@ -19,6 +19,7 @@ environments. * NagiosGen: Add bundles to configuration * HomeBrew: Initial add of plugin +* Rules/Defaults: Add possibility to use name of entry in attributes backwards-incompatible user-facing changes ------------------------------------------ diff --git a/doc/server/plugins/generators/rules.txt b/doc/server/plugins/generators/rules.txt index e4f47c2bf..7aeec6990 100644 --- a/doc/server/plugins/generators/rules.txt +++ b/doc/server/plugins/generators/rules.txt @@ -512,3 +512,23 @@ you'd have to explicitly specify ``<Service name="bcfg2.*".../>``. Note that only one Rule can apply to any abstract entry, so you cannot specify multiple regexes to match the same rule. + +Replacing the name of the Entry in Attributes +============================================= + +If you are using regular expressions to match the abstract configuration +entries, you may need the concrete name of the entry in some attributes. +To use this feature, you have to enable it. It is only useful, if used +together with regex matching. :: + + [rules] + regex = yes + replace_name = yes + +You now can write something like that in your xml file: + +.. code-block:: xml + + <POSIXUser name='.*' home='/somewhere/%{name}'/> + +``%{name}`` will be correctly replaced with the username for each POSIXUser. diff --git a/doc/server/plugins/structures/defaults.txt b/doc/server/plugins/structures/defaults.txt index 58b9feddb..9d37b8e64 100644 --- a/doc/server/plugins/structures/defaults.txt +++ b/doc/server/plugins/structures/defaults.txt @@ -29,3 +29,10 @@ on Fedora 15 and the ``chkconfig`` tool on Fedora 14, you could do:: If you were to specify a ``type`` attribute for a Service entry in Rules (or a ``type`` attribute for a BoundService entry in Bundler), that would take precendence over the default. + +Like :ref:`server-plugins-generators-rules`, Defaults can also replace +``%{name}`` in attributes with the real name of the entry. To enable this, +add the following setting to ``bcfg2.conf``:: + + [defaults] + replace_name = yes diff --git a/src/lib/Bcfg2/Server/Plugin/helpers.py b/src/lib/Bcfg2/Server/Plugin/helpers.py index 3b62a3217..6b521dfd6 100644 --- a/src/lib/Bcfg2/Server/Plugin/helpers.py +++ b/src/lib/Bcfg2/Server/Plugin/helpers.py @@ -1064,6 +1064,20 @@ class PrioDir(Plugin, Generator, XMLDirectoryBacked): data = candidate break + self._apply(entry, data) + + def _apply(self, entry, data): + """ Apply all available values from data onto entry. This + sets the available attributes (for all attribues unset in + the entry), adds all children and copies the text from data + to entry. + + :param entry: The entry to apply the changes + :type entry: lxml.etree._Element + :param data: The entry to get the data from + :type data: lxml.etree._Element + """ + if data.text is not None and data.text.strip() != '': entry.text = data.text for item in data.getchildren(): diff --git a/src/lib/Bcfg2/Server/Plugins/Defaults.py b/src/lib/Bcfg2/Server/Plugins/Defaults.py index 79e2ca0e2..2242e3825 100644 --- a/src/lib/Bcfg2/Server/Plugins/Defaults.py +++ b/src/lib/Bcfg2/Server/Plugins/Defaults.py @@ -1,5 +1,6 @@ """This generator provides rule-based entry mappings.""" +import Bcfg2.Options import Bcfg2.Server.Plugin import Bcfg2.Server.Plugins.Rules @@ -9,7 +10,10 @@ class Defaults(Bcfg2.Server.Plugins.Rules.Rules, """Set default attributes on bound entries""" __author__ = 'bcfg-dev@mcs.anl.gov' - options = Bcfg2.Server.Plugin.PrioDir.options + options = Bcfg2.Server.Plugin.PrioDir.options + [ + Bcfg2.Options.BooleanOption( + cf=("defaults", "replace_name"), dest="defaults_replace_name", + help="Replace %{name} in attributes with name of target entry")] # Rules is a Generator that happens to implement all of the # functionality we want, so we overload it, but Defaults should @@ -41,3 +45,9 @@ class Defaults(Bcfg2.Server.Plugins.Rules.Rules, def _regex_enabled(self): """ Defaults depends on regex matching, so force it enabled """ return True + + @property + def _replace_name_enabled(self): + """ Return True if the replace_name feature is enabled, + False otherwise """ + return Bcfg2.Options.setup.defaults_replace_name diff --git a/src/lib/Bcfg2/Server/Plugins/Rules.py b/src/lib/Bcfg2/Server/Plugins/Rules.py index a3f682ed6..cf659251c 100644 --- a/src/lib/Bcfg2/Server/Plugins/Rules.py +++ b/src/lib/Bcfg2/Server/Plugins/Rules.py @@ -1,10 +1,17 @@ """This generator provides rule-based entry mappings.""" +import copy import re +import string import Bcfg2.Options import Bcfg2.Server.Plugin +class NameTemplate(string.Template): + """Simple subclass of string.Template with a custom delimiter.""" + delimiter = '%' + + class Rules(Bcfg2.Server.Plugin.PrioDir): """This is a generator that handles service assignments.""" __author__ = 'bcfg-dev@mcs.anl.gov' @@ -12,7 +19,10 @@ class Rules(Bcfg2.Server.Plugin.PrioDir): options = Bcfg2.Server.Plugin.PrioDir.options + [ Bcfg2.Options.BooleanOption( cf=("rules", "regex"), dest="rules_regex", - help="Allow regular expressions in Rules")] + help="Allow regular expressions in Rules"), + Bcfg2.Options.BooleanOption( + cf=("rules", "replace_name"), dest="rules_replace_name", + help="Replace %{name} in attributes with name of target entry")] def __init__(self, core): Bcfg2.Server.Plugin.PrioDir.__init__(self, core) @@ -46,7 +56,22 @@ class Rules(Bcfg2.Server.Plugin.PrioDir): return True return False + def _apply(self, entry, data): + if self._replace_name_enabled: + data = copy.deepcopy(data) + for key, val in list(data.attrib.items()): + data.attrib[key] = NameTemplate(val).safe_substitute( + name=entry.get('name')) + + Bcfg2.Server.Plugin.PrioDir._apply(self, entry, data) + @property def _regex_enabled(self): """ Return True if rules regexes are enabled, False otherwise """ return Bcfg2.Options.setup.rules_regex + + @property + def _replace_name_enabled(self): + """ Return True if the replace_name feature is enabled, + False otherwise """ + return Bcfg2.Options.setup.rules_replace_name diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDefaults.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDefaults.py index 9b4a6af88..3c660099e 100644 --- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDefaults.py +++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestDefaults.py @@ -22,6 +22,10 @@ from Testinterfaces import TestGoalValidator class TestDefaults(TestRules, TestGoalValidator): test_obj = Defaults + def setUp(self): + TestRules.setUp(self) + set_setup_default("defaults_replace_name", True) + def get_obj(self, *args, **kwargs): return TestRules.get_obj(self, *args, **kwargs) @@ -91,3 +95,8 @@ class TestDefaults(TestRules, TestGoalValidator): def test_regex(self): self._do_test('regex') + + def test_replace_name(self): + Bcfg2.Options.setup.defaults_replace_name = True + self._do_test('replace_name') + Bcfg2.Options.setup.defaults_replace_name = False diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py index 45f3671e8..88b334edf 100644 --- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py +++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestRules.py @@ -32,6 +32,7 @@ class TestRules(TestPrioDir): group=lxml.etree.Element("SEPort", name="6789/tcp"), children=lxml.etree.Element("Path", name="/etc/child-entries"), regex=lxml.etree.Element("Package", name="regex"), + replace_name=lxml.etree.Element("POSIXUser", name="regex"), slash=lxml.etree.Element("Path", name="/etc/trailing/slash"), no_slash=lxml.etree.Element("Path", name="/etc/no/trailing/slash/")) @@ -53,6 +54,8 @@ class TestRules(TestPrioDir): group="root", mode="0775"), regex=lxml.etree.Element("Package", name="regex", type="yum", version="any"), + replace_name=lxml.etree.Element("POSIXUser", name="regex", + home="/foobar%{bar}/regex"), slash=lxml.etree.Element("Path", name="/etc/trailing/slash", type="directory", owner="root", group="root", mode="0600"), @@ -70,6 +73,8 @@ class TestRules(TestPrioDir): in_file = copy.deepcopy(concrete) in_file['regex'].set("name", ".*") + in_file['replace_name'].set("home", "/foobar%{bar}/%{name}") + in_file['replace_name'].set("name", ".*") in_file['slash'].set("name", "/etc/trailing/slash/") in_file['no_slash'].set("name", "/etc/no/trailing/slash") @@ -91,6 +96,7 @@ class TestRules(TestPrioDir): rules3 = lxml.etree.Element("Rules", priority="10") rules3.append(in_file['duplicate']) rules3.append(in_file['regex']) + rules3.append(in_file['replace_name']) rules3.append(in_file['slash']) rules = {"rules1.xml": rules1, "rules2.xml": rules2, "rules3.xml": rules3} @@ -99,6 +105,7 @@ class TestRules(TestPrioDir): TestPrioDir.setUp(self) set_setup_default("lax_decryption", True) set_setup_default("rules_regex", False) + set_setup_default("rules_replace_name", False) def get_child(self, name): """ Turn one of the XML documents in `rules` into a child @@ -169,6 +176,17 @@ class TestRules(TestPrioDir): self._do_test('regex') Bcfg2.Options.setup.rules_regex = False + def test_replace_name(self): + """ Test that Rules handles replaces name in attribues with regular expressions """ + Bcfg2.Options.setup.rules_regex = False + Bcfg2.Options.setup.rules_replace_name = False + self._do_test_failure('replace_name', handles=False) + Bcfg2.Options.setup.rules_regex = True + Bcfg2.Options.setup.rules_replace_name = True + self._do_test('replace_name') + Bcfg2.Options.setup.rules_regex = False + Bcfg2.Options.setup.rules_replace_name = False + def test_slash(self): """ Test that Rules handles trailing slashes on Path entries """ self._do_test('slash') diff --git a/testsuite/common.py b/testsuite/common.py index e53cfdddf..45cfcbf0b 100644 --- a/testsuite/common.py +++ b/testsuite/common.py @@ -172,21 +172,22 @@ class Bcfg2TestCase(TestCase): msg = "XML trees are not equal: %s" else: msg += ": %s" - fullmsg = msg + "\nFirst: %s" % lxml.etree.tostring(el1) + \ + msg += "\n%s" + fullmsg = "First: %s" % lxml.etree.tostring(el1) + \ "\nSecond: %s" % lxml.etree.tostring(el2) - self.assertEqual(el1.tag, el2.tag, msg=fullmsg % "Tags differ") + self.assertEqual(el1.tag, el2.tag, msg=msg % ("Tags differ", fullmsg)) if el1.text is not None and el2.text is not None: self.assertEqual(el1.text.strip(), el2.text.strip(), - msg=fullmsg % "Text content differs") + msg=msg % ("Text content differs", fullmsg)) else: self.assertEqual(el1.text, el2.text, - msg=fullmsg % "Text content differs") + msg=msg % ("Text content differs", fullmsg)) self.assertItemsEqual(el1.attrib.items(), el2.attrib.items(), - msg=fullmsg % "Attributes differ") + msg=msg % ("Attributes differ", fullmsg)) self.assertEqual(len(el1.getchildren()), len(el2.getchildren()), - msg=fullmsg % "Different numbers of children") + msg=msg % ("Different numbers of children", fullmsg)) matched = [] for child1 in el1.getchildren(): for child2 in el2.xpath(child1.tag): @@ -200,10 +201,10 @@ class Bcfg2TestCase(TestCase): continue else: assert False, \ - fullmsg % ("Element %s is missing from second" % - lxml.etree.tostring(child1)) + msg % ("Element %s is missing from second" % + lxml.etree.tostring(child1), fullmsg) self.assertItemsEqual(el2.getchildren(), matched, - msg=fullmsg % "Second has extra element(s)") + msg=msg % ("Second has extra element(s)", fullmsg)) class DBModelTestCase(Bcfg2TestCase): |