From 5db7ab284f4b83c0b25e68edf258bae912a5b418 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Fri, 14 Jun 2013 10:58:13 -0400 Subject: Cfg: Fixed and documented .cat and .diff file behavior with host-specific base file --- doc/server/plugins/generators/cfg.txt | 47 +++++++++++++++------- src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py | 25 ++++++++---- .../TestServer/TestPlugins/TestCfg/Test_init.py | 16 ++++---- 3 files changed, 58 insertions(+), 30 deletions(-) diff --git a/doc/server/plugins/generators/cfg.txt b/doc/server/plugins/generators/cfg.txt index f31923866..e3768a3ba 100644 --- a/doc/server/plugins/generators/cfg.txt +++ b/doc/server/plugins/generators/cfg.txt @@ -596,6 +596,11 @@ Deltas cat file functionality. ``bcfg2-lint`` checks for deltas and warns about them. +.. warning:: + + In Bcfg2 1.3, deltas **do not** work with `SSH key or + authorized_keys generation `_. + Bcfg2 has finer grained control over how to deliver configuration files to a host. Let's say we have a Group named file-server. Members of this group need the exact same ``/etc/motd`` as all other hosts except @@ -632,23 +637,35 @@ server and we have the following configuration files:: motd.G01_web-server motd.G01_mail-server.cat motd.G02_file-server.cat + motd.H_bar.example.com motd.H_foo.example.com.cat -If our machine **isn't** *foo.example.com* then here's what would happen: - -Bcfg2 would choose ``motd.G01_web-server`` as the base file. It is -the most specific base file for this host. Bcfg2 would apply the -``motd.G01_mail-server.cat`` delta to the ``motd.G01_web-server`` -base file. It is the least specific delta. Bcfg2 would then apply the -``motd.G02_file-server.cat`` delta to the result of the delta before -it. If our machine **is** *foo.example.com* then here's what would happen: - -Bcfg2 would choose ``motd.G01_web-server`` as the base file. It -is the most specific base file for this host. Bcfg2 would apply the -``motd.H_foo.example.com.cat`` delta to the ``motd.G01_web-server`` base -file. The reason the other deltas aren't applied to *foo.example.com* -is because a **.H_** delta is more specific than a **.G##_** delta. Bcfg2 -applies all the deltas at the most specific level. +If our machine isn't *foo.example.com* or *bar.example.com*, but +is a web server, then Bcfg2 would choose ``motd.G01_web-server`` as +the base file. It is the most specific base file for this host. Bcfg2 +would apply the ``motd.G01_mail-server.cat`` delta to the +``motd.G01_web-server`` base file. It is the least specific +delta. Bcfg2 would then apply the ``motd.G02_file-server.cat`` delta +to the result of the delta before it. + +If our machine is *foo.example.com* and a web server, then Bcfg2 would +choose ``motd.G01_web-server`` as the base file. It is the most +specific base file for this host. Bcfg2 would apply the +``motd.H_foo.example.com.cat`` delta to the ``motd.G01_web-server`` +base file. The reason the other deltas aren't applied to +*foo.example.com* is because a **.H_** delta is more specific than a +**.G##_** delta. Bcfg2 applies all the deltas at the most specific +level. + +If our machine is *bar.example.com*, then Bcfg2 would chose +``motd.H_foo.example.com`` as the base file because it is the most +specific base file for this host. Regardless of the groups +*bar.example.com* is a member of, **no cat files** would be applied, +because only cat files as specific or more specific than the base file +are applied. (In other words, if a group-specific base file is +selected, only group- or host-specific cat files can be applied; if a +host-specific base file is selected, only host-specific cat files can +be applied.) .. _server-plugins-generators-cfg-validation: diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py index 842202a9c..154cd5e63 100644 --- a/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/__init__.py @@ -10,7 +10,6 @@ import lxml.etree import Bcfg2.Options import Bcfg2.Server.Plugin import Bcfg2.Server.Lint -from itertools import chain from Bcfg2.Server.Plugin import PluginExecutionError # pylint: disable=W0622 from Bcfg2.Compat import u_str, unicode, b64encode, walk_packages, \ @@ -582,10 +581,18 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet, def bind_entry(self, entry, metadata): self.bind_info_to_entry(entry, metadata) - data = self._generate_data(entry, metadata) - - for fltr in self.get_handlers(metadata, CfgFilter): - data = fltr.modify_data(entry, metadata, data) + data, generator = self._generate_data(entry, metadata) + + if generator is not None: + # apply no filters if the data was created by a CfgCreator + for fltr in self.get_handlers(metadata, CfgFilter): + if fltr.specific <= generator.specific: + # only apply filters that are as specific or more + # specific than the generator used for this entry. + # Note that specificity comparison is backwards in + # this sense, since it's designed to sort from + # most specific to least specific. + data = fltr.modify_data(entry, metadata, data) if SETUP['validate']: try: @@ -694,7 +701,9 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet, :type entry: lxml.etree._Element :param metadata: The client metadata to generate data for :type metadata: Bcfg2.Server.Plugins.Metadata.ClientMetadata - :returns: string - the data for the entry + :returns: tuple of (string, generator) - the data for the + entry and the generator used to generate it (or + None, if data was created) """ try: generator = self.best_matching(metadata, @@ -703,7 +712,7 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet, except PluginExecutionError: # if no creators or generators exist, _create_data() # raises an appropriate exception - return self._create_data(entry, metadata) + return (self._create_data(entry, metadata), None) if entry.get('mode').lower() == 'inherit': # use on-disk permissions @@ -713,7 +722,7 @@ class CfgEntrySet(Bcfg2.Server.Plugin.EntrySet, entry.set('mode', oct_mode(stat.S_IMODE(os.stat(fname).st_mode))) try: - return generator.get_data(entry, metadata) + return (generator.get_data(entry, metadata), generator) except: msg = "Cfg: Error rendering %s: %s" % (entry.get("name"), sys.exc_info()[1]) diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py index f838030e2..ea3549c1b 100644 --- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py +++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/Test_init.py @@ -6,7 +6,7 @@ import Bcfg2.Options from Bcfg2.Compat import walk_packages from mock import Mock, MagicMock, patch from Bcfg2.Server.Plugins.Cfg import * -from Bcfg2.Server.Plugin import PluginExecutionError +from Bcfg2.Server.Plugin import PluginExecutionError, Specificity # add all parent testsuite directories to sys.path to allow (most) # relative imports in python 2.4 @@ -461,7 +461,7 @@ class TestCfgEntrySet(TestEntrySet): metadata = Mock() # test basic entry, no validation, no filters, etc. - eset._generate_data.return_value = "data" + eset._generate_data.return_value = ("data", None) eset.get_handlers.return_value = [] bound = eset.bind_entry(entry, metadata) eset.bind_info_to_entry.assert_called_with(entry, metadata) @@ -474,7 +474,7 @@ class TestCfgEntrySet(TestEntrySet): # test empty entry entry = reset() - eset._generate_data.return_value = "" + eset._generate_data.return_value = ("", None) bound = eset.bind_entry(entry, metadata) eset.bind_info_to_entry.assert_called_with(entry, metadata) eset._generate_data.assert_called_with(entry, metadata) @@ -485,7 +485,9 @@ class TestCfgEntrySet(TestEntrySet): # test filters entry = reset() - eset._generate_data.return_value = "initial data" + generator = Mock() + generator.specific = Specificity(all=True) + eset._generate_data.return_value = ("initial data", generator) filters = [Mock(), Mock()] filters[0].modify_data.return_value = "modified data" filters[1].modify_data.return_value = "final data" @@ -507,7 +509,7 @@ class TestCfgEntrySet(TestEntrySet): entry.set("encoding", "base64") mock_b64encode.return_value = "base64 data" eset.get_handlers.return_value = [] - eset._generate_data.return_value = "data" + eset._generate_data.return_value = ("data", None) bound = eset.bind_entry(entry, metadata) eset.bind_info_to_entry.assert_called_with(entry, metadata) eset._generate_data.assert_called_with(entry, metadata) @@ -691,7 +693,7 @@ class TestCfgEntrySet(TestEntrySet): eset._create_data.reset_mock() # test success - self.assertEqual(eset._generate_data(entry, metadata), + self.assertEqual(eset._generate_data(entry, metadata)[0], "data") eset.get_handlers.assert_called_with(metadata, CfgGenerator) eset.best_matching.assert_called_with(metadata, @@ -708,7 +710,7 @@ class TestCfgEntrySet(TestEntrySet): reset() eset.best_matching.side_effect = PluginExecutionError self.assertEqual(eset._generate_data(entry, metadata), - eset._create_data.return_value) + (eset._create_data.return_value, None)) eset.get_handlers.assert_called_with(metadata, CfgGenerator) eset.best_matching.assert_called_with(metadata, eset.get_handlers.return_value) -- cgit v1.2.3-1-g7c22