From 255046b3f3484219bbfa6646c88bbc4bbc1b5256 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Fri, 16 Nov 2012 14:40:42 -0500 Subject: Templating updates: * Added "repo" variable to all template formats * Made variables available in Genshi and Cheetah template more similar * Improved docs --- doc/server/plugins/generators/cfg.txt | 85 +++++++++++++--------- doc/server/plugins/structures/bundler/index.txt | 55 +++++--------- src/lib/Bcfg2/Server/Plugins/Bundler.py | 15 +++- .../Server/Plugins/Cfg/CfgCheetahGenerator.py | 4 +- .../Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py | 6 +- .../TestPlugins/TestCfg/TestCfgCheetahGenerator.py | 11 ++- .../TestPlugins/TestCfg/TestCfgGenshiGenerator.py | 30 +++++--- 7 files changed, 116 insertions(+), 90 deletions(-) diff --git a/doc/server/plugins/generators/cfg.txt b/doc/server/plugins/generators/cfg.txt index 25986a413..7d0e0acff 100644 --- a/doc/server/plugins/generators/cfg.txt +++ b/doc/server/plugins/generators/cfg.txt @@ -106,26 +106,13 @@ Genshi templates allow you to use the `Genshi the deprecated :ref:`server-plugins-generators-tgenshi-index` plugin. Genshi templates should be named with a ``.genshi`` extension, e.g.:: - % ls Cfg/etc/motd + % ls Cfg/etc/motd info.xml motd.genshi See the genshi `documentation `_ for examples of Genshi syntax. -Inside Genshi Templates -~~~~~~~~~~~~~~~~~~~~~~~ - -Several variables are pre-defined inside templates: - -* **metadata** is the client's :ref:`metadata - ` -* **name** is the path name specified in Bcfg2 -* **path** is the path to the TGenshi template. It starts with a - leading slash, and is relative to the Bcfg2 specification root. - E.g., ``/Cfg/etc/foo.conf/foo.conf.genshi`` or - ``/TGenshi/etc/foo.conf/template.newtxt.H_foo.example.com`` - Troubleshooting ~~~~~~~~~~~~~~~ @@ -138,7 +125,8 @@ E.g.:: bcfg2-info buildfile /etc/foo.conf foo.example.com -To generate a file with an altsrc attribute, you can run:: +To generate a file with an :ref:`altsrc +` attribute, you can run:: bcfg2-info buildfile /etc/foo/foo.conf --altsrc=/etc/foo.conf \ foo.example.com @@ -149,13 +137,13 @@ debug``, and, once in the Python interpreter, run:: metadata = self.build_metadata("") path = "" - + ``path`` should be set to the path to the template file with a leading slash, relative to the Bcfg2 specification root. See `Inside Genshi Templates`_ for examples. Then, run:: - + import os, Bcfg2.Options from genshi.template import TemplateLoader, NewTextTemplate name = os.path.dirname(path[path.find('/', 1):]) @@ -230,19 +218,9 @@ Cheetah templates allow you to use the `cheetah templating system the deprecated :ref:`server-plugins-generators-tcheetah` plugin. Cheetah templates should be named with a ``.cheetah`` extension, e.g.:: - % ls Cfg/etc/motd + % ls Cfg/etc/motd info.xml motd.cheetah -Inside Cheetah Templates -~~~~~~~~~~~~~~~~~~~~~~~~ - -Several variables are pre-defined inside templates: - -* **self.metadata** is the client's :ref:`metadata - ` -* **self.path** is the path name specified in Bcfg2 -* **self.source_path** is the path to the Genshi template on the filesystem. - Examples ~~~~~~~~ @@ -265,12 +243,48 @@ comment to appear in the final config file.:: # This is a comment in my template which will be stripped when it's processed through Cheetah \# This comment will appear in the generated config file. +Inside Templates +---------------- + +Several variables are pre-defined inside templates: + ++-------------+--------------------------------------------------------+ +| Name | Description | ++=============+========================================================+ +| metadata | :ref:`Client metadata | +| | ` | ++-------------+--------------------------------------------------------+ +| name | The value of the ``name`` attribute as specified in | +| | the Path entry in Bcfg2. If an :ref:`altsrc | +| | ` attribute is used, | +| | then ``name`` will be the value of that attribute. | ++-------------+--------------------------------------------------------+ +| source_path | The path to the template file on the filesystem | ++-------------+--------------------------------------------------------+ +| repo | The path to the Bcfg2 repository on the filesystem | ++-------------+--------------------------------------------------------+ +| path | In Genshi templates, ``path`` is a synonym for | +| | ``source_path``. In Cheetah templates, it's a synonym | +| | for ``name``. For this reason, use of ``path`` is | +| | discouraged, and it may be deprecated in a future | +| | release. | ++-------------+--------------------------------------------------------+ + +To access these variables in a Genshi template, you can simply use the +name, e.g.:: + + Path to this file: ${name} + +In a Cheetah template, the variables are properties of ``self``, +e.g.:: + + Path to this file: $self.name Notes on Using Templates ------------------------ Templates can be host and group specific as well. Deltas will not be -processed for any genshi or cheetah base file. +processed for any Genshi or Cheetah base file. .. note:: @@ -278,19 +292,22 @@ processed for any genshi or cheetah base file. or group-specific files, you will need to ensure that the ``.genshi`` or ``.cheetah`` extension is at the **end** of the filename. Using the examples from above for *host.example.com* and group *server* you would - have the following (using genshi only):: + have the following:: Cfg/etc/fstab/fstab.H_host.example.com.genshi - Cfg/etc/fstab/fstab.G50_server.genshi + Cfg/etc/fstab/fstab.G50_server.cheetah Genshi templates take precence over cheetah templates. For example, if -two files exist named +two files exist named:: Cfg/etc/fstab/fstab.genshi Cfg/etc/fstab/fstab.cheetah -the cheetah template is ignored. But you can mix genshi and cheetah when -using different host-specific or group-specific files. For example: +The Cheetah template is ignored. Exploiting this fact is probably a +pretty bad idea in practice. + +You can mix Genshi and Cheetah when using different host-specific or +group-specific files. For example: Cfg/etc/fstab/fstab.H_host.example.com.genshi Cfg/etc/fstab/fstab.G50_server.cheetah diff --git a/doc/server/plugins/structures/bundler/index.txt b/doc/server/plugins/structures/bundler/index.txt index 528df79db..1cc287ebd 100644 --- a/doc/server/plugins/structures/bundler/index.txt +++ b/doc/server/plugins/structures/bundler/index.txt @@ -113,40 +113,31 @@ demonstrates how group entries can be used in bundles) Genshi templates ================ -Genshi xml templates can be specified one of two ways: +Genshi XML templates allow you to use the `Genshi +`_ templating system to dynamically +generate a bundle. Genshi templates can be specified one of two ways: -1. Add an xml-style genshi template to the Bundler directory with a +1. Add an XML-style genshi template to the Bundler directory with a ``.genshi`` and the associated namespace attribute. -2. Simply add the appropriate namespace attribute to your existing xml +2. Simply add the appropriate namespace attribute to your existing XML bundle. -The namespace attribute in this case should look like the following:: +The top-level Bundle tag should look like the following:: - xmlns:py="http://genshi.edgewall.org/" + -Motivation ----------- +Several variables are pre-defined inside templates: -Static Bundles have served us relatively well, but have a relatively -weak set of interal per-client differentiation mechanisms. In static -Bundles, the group hierarchy (from the perspective of the current -client) is available for use via boolean constraints with negation. This -notion does not mesh well with the use of Probes, which can result in -freeform data. In the presence of probe results, clients can have a wide -array of data, and rendering down to a boolean logic may not always -be desirable. Moreover, while static Bundles allow entry inclusion or -exclusion based on group memberships, they do not allow programatic entry -rendering. Hence, Genshi templates not only provide more control options, -but it also provide the ability to perform new entry rendering operations. ++-------------+--------------------------------------------------------+ +| Name | Description | ++=============+========================================================+ +| metadata | :ref:`Client metadata | +| | ` | ++-------------+--------------------------------------------------------+ +| repo | The path to the Bcfg2 repository on the filesystem | ++-------------+--------------------------------------------------------+ -The `Genshi templating system`_ is used internally. - -.. _Genshi templating system: http://genshi.edgewall.org/ - -Use ---- - -.. warning:: +.. note:: ```` and ```` tags are allowed inside of Genshi templates as of Bcfg2 1.2. However, they do not behave the same @@ -154,7 +145,7 @@ Use - + The conditional is evaluated when the template is rendered, so code inside the conditional is not executed if the conditional fails. A ```` tag is evaluated *after* the template is @@ -163,16 +154,6 @@ Use groups, you *must* use a Genshi conditional, not a ```` tag. The same caveats apply to ```` tags. -Bcfg2 uses the Genshi API for templates, and performs a XML format -stream rendering of the template into an lxml entry, which is included -in the client configuration. :ref:`Client metadata ` -is available inside of the template using the 'metadata' name. Note that -only the markup Genshi template format can be used, as the target output -format is XML. - -A Genshi template looks much like a Bundler file, except the Bundle tag -has an additional `xmlns:py` attribute. See the examples. - Troubleshooting --------------- diff --git a/src/lib/Bcfg2/Server/Plugins/Bundler.py b/src/lib/Bcfg2/Server/Plugins/Bundler.py index 7933fe9be..b200346bc 100644 --- a/src/lib/Bcfg2/Server/Plugins/Bundler.py +++ b/src/lib/Bcfg2/Server/Plugins/Bundler.py @@ -19,6 +19,9 @@ except ImportError: HAS_GENSHI = False +SETUP = None + + class BundleFile(Bcfg2.Server.Plugin.StructFile): """ Representation of a bundle XML file """ def get_xml_value(self, metadata): @@ -49,7 +52,8 @@ if HAS_GENSHI: msg = "No parsed template information for %s" % self.name self.logger.error(msg) raise Bcfg2.Server.Plugin.PluginExecutionError(msg) - stream = self.template.generate(metadata=metadata).filter( + stream = self.template.generate(metadata=metadata, + repo=SETUP['repo']).filter( Bcfg2.Server.Plugins.TGenshi.removecomment) data = lxml.etree.XML(stream.render('xml', strip_whitespace=False), @@ -93,8 +97,13 @@ class Bundler(Bcfg2.Server.Plugin.Plugin, self.data, self.core.fam) except OSError: - self.logger.error("Failed to load Bundle repository") - raise Bcfg2.Server.Plugin.PluginInitError + err = sys.exc_info()[1] + msg = "Failed to load Bundle repository %s: %s" % (self.data, err) + self.logger.error(msg) + raise Bcfg2.Server.Plugin.PluginInitError(msg) + + global SETUP + SETUP = core.setup def template_dispatch(self, name, _): """ Add the correct child entry type to Bundler depending on diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgCheetahGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgCheetahGenerator.py index 8ebd8d921..724164cf5 100644 --- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgCheetahGenerator.py +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgCheetahGenerator.py @@ -3,7 +3,7 @@ :ref:`server-plugins-generators-cfg` files. """ from Bcfg2.Server.Plugin import PluginExecutionError -from Bcfg2.Server.Plugins.Cfg import CfgGenerator +from Bcfg2.Server.Plugins.Cfg import CfgGenerator, SETUP try: from Cheetah.Template import Template @@ -37,7 +37,9 @@ class CfgCheetahGenerator(CfgGenerator): template = Template(self.data.decode(self.encoding), compilerSettings=self.settings) template.metadata = metadata + template.name = entry.get('realname', entry.get('name')) template.path = entry.get('realname', entry.get('name')) template.source_path = self.name + template.repo = SETUP['repo'] return template.respond() get_data.__doc__ = CfgGenerator.get_data.__doc__ diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py index df0c30c09..48f64ac7f 100644 --- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgGenshiGenerator.py @@ -6,7 +6,7 @@ import re import sys import traceback from Bcfg2.Server.Plugin import PluginExecutionError -from Bcfg2.Server.Plugins.Cfg import CfgGenerator +from Bcfg2.Server.Plugins.Cfg import CfgGenerator, SETUP try: import genshi.core @@ -74,7 +74,9 @@ class CfgGenshiGenerator(CfgGenerator): stream = \ self.template.generate(name=fname, metadata=metadata, - path=self.name).filter(removecomment) + path=self.name, + source_path=self.name, + repo=SETUP['repo']).filter(removecomment) try: try: return stream.render('text', encoding=self.encoding, diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgCheetahGenerator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgCheetahGenerator.py index 1832e5e03..fc5d5e53d 100644 --- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgCheetahGenerator.py +++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgCheetahGenerator.py @@ -31,9 +31,18 @@ if HAS_CHEETAH or can_skip: ccg.data = "data" entry = lxml.etree.Element("Path", name="/test.txt") metadata = Mock() + Bcfg2.Server.Plugins.Cfg.CfgCheetahGenerator.SETUP = MagicMock() self.assertEqual(ccg.get_data(entry, metadata), mock_Template.return_value.respond.return_value) + Bcfg2.Server.Plugins.Cfg.CfgCheetahGenerator.SETUP.__getitem__.assert_called_with("repo") mock_Template.assert_called_with("data".decode(ccg.encoding), compilerSettings=ccg.settings) - mock_Template.return_value.respond.assert_called_with() + tmpl = mock_Template.return_value + tmpl.respond.assert_called_with() + self.assertEqual(tmpl.metadata, metadata) + self.assertEqual(tmpl.name, entry.get("name")) + self.assertEqual(tmpl.path, entry.get("name")) + self.assertEqual(tmpl.source_path, ccg.name) + self.assertEqual(tmpl.repo, + Bcfg2.Server.Plugins.Cfg.CfgCheetahGenerator.SETUP.__getitem__.return_value) diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py index 4a849c11a..385f8df77 100644 --- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py +++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgGenshiGenerator.py @@ -51,15 +51,24 @@ if can_skip or HAS_GENSHI: entry = lxml.etree.Element("Path", name="/test.txt") metadata = Mock() + Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator.SETUP = MagicMock() + def reset(): cgg.template.reset_mock() cgg._handle_genshi_exception.reset_mock() + Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator.SETUP.reset_mock() + + template_vars = dict( + name=entry.get("name"), + metadata=metadata, + path=cgg.name, + source_path=cgg.name, + repo=Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator.SETUP.__getitem__.return_value) self.assertEqual(cgg.get_data(entry, metadata), stream.render.return_value) - cgg.template.generate.assert_called_with(name=entry.get("name"), - metadata=metadata, - path=cgg.name) + cgg.template.generate.assert_called_with(**template_vars) + Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator.SETUP.__getitem__.assert_called_with("repo") fltr.filter.assert_called_with(removecomment) stream.render.assert_called_with("text", encoding=cgg.encoding, strip_whitespace=False) @@ -71,9 +80,8 @@ if can_skip or HAS_GENSHI: stream.render.side_effect = render self.assertEqual(cgg.get_data(entry, metadata), stream.render.return_value) - cgg.template.generate.assert_called_with(name=entry.get("name"), - metadata=metadata, - path=cgg.name) + cgg.template.generate.assert_called_with(**template_vars) + Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator.SETUP.__getitem__.assert_called_with("repo") fltr.filter.assert_called_with(removecomment) self.assertEqual(stream.render.call_args_list, [call("text", encoding=cgg.encoding, @@ -84,9 +92,8 @@ if can_skip or HAS_GENSHI: stream.render.side_effect = UndefinedError("test") self.assertRaises(UndefinedError, cgg.get_data, entry, metadata) - cgg.template.generate.assert_called_with(name=entry.get("name"), - metadata=metadata, - path=cgg.name) + cgg.template.generate.assert_called_with(**template_vars) + Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator.SETUP.__getitem__.assert_called_with("repo") fltr.filter.assert_called_with(removecomment) stream.render.assert_called_with("text", encoding=cgg.encoding, strip_whitespace=False) @@ -96,9 +103,8 @@ if can_skip or HAS_GENSHI: cgg._handle_genshi_exception.side_effect = ValueError self.assertRaises(ValueError, cgg.get_data, entry, metadata) - cgg.template.generate.assert_called_with(name=entry.get("name"), - metadata=metadata, - path=cgg.name) + cgg.template.generate.assert_called_with(**template_vars) + Bcfg2.Server.Plugins.Cfg.CfgGenshiGenerator.SETUP.__getitem__.assert_called_with("repo") fltr.filter.assert_called_with(removecomment) stream.render.assert_called_with("text", encoding=cgg.encoding, strip_whitespace=False) -- cgit v1.2.3-1-g7c22