diff options
author | Sol Jerome <sol.jerome@gmail.com> | 2014-11-17 14:31:31 -0600 |
---|---|---|
committer | Sol Jerome <sol.jerome@gmail.com> | 2014-11-17 14:31:31 -0600 |
commit | c544b18a985edd7444593e75ad52483f4842c119 (patch) | |
tree | 6b4504ec02bcc339217dcf754ae6e51f9e697685 | |
parent | b8310f6f2b2c440704913af53b4af90b9ce13e8c (diff) | |
parent | 0512f18f11b2ba9432a8be8eb2c05b6e290b976b (diff) | |
download | bcfg2-c544b18a985edd7444593e75ad52483f4842c119.tar.gz bcfg2-c544b18a985edd7444593e75ad52483f4842c119.tar.bz2 bcfg2-c544b18a985edd7444593e75ad52483f4842c119.zip |
Merge branch 'include2' of https://github.com/gordonmessmer/bcfg2
4 files changed, 192 insertions, 13 deletions
diff --git a/doc/server/plugins/generators/examples/jinja2/extends.txt b/doc/server/plugins/generators/examples/jinja2/extends.txt new file mode 100644 index 000000000..a0eeb5c03 --- /dev/null +++ b/doc/server/plugins/generators/examples/jinja2/extends.txt @@ -0,0 +1,61 @@ +.. -*- mode: rst -*- + +========================= + Extending Jinja2 Templates +========================= + +Jinja2 templates can use the {% extends %} directive to inherit file +fragments which might be common to many configuration files. + +Use the "jinja2_include" suffix for file fragments you will extend. + +``/var/lib/bcfg2/Cfg/foo/common.jinja2_include`` + +.. code-block:: none + + [global] + setting1 = true + setting2 = false + {% block setting3 %}setting3 = "default value"{% endblock %} + + {% block section1 -%} + [section1] + setting4 = true + setting5 = false + {%- endblock %} + + {% block section2 -%} + [section2] + setting6 = true + setting7 = false + {%- endblock %} + +``/var/lib/bcfg2/Cfg/foo/foo.H_hostname.jinja2`` + +.. code-block:: none + + {% extends "common.jinja2_include" %} + {% block setting3 %}setting3 = "new value"{% endblock %} + {% block section1 -%} + [section1] + setting4 = false + setting5 = false + {%- endblock %} + +Output +====== + +.. code-block:: none + + [global] + setting1 = true + setting2 = false + setting3 = "new value" + + [section1] + setting4 = false + setting5 = false + + [section2] + setting6 = true + setting7 = false diff --git a/doc/server/plugins/generators/examples/jinja2/include.txt b/doc/server/plugins/generators/examples/jinja2/include.txt new file mode 100644 index 000000000..49be7c277 --- /dev/null +++ b/doc/server/plugins/generators/examples/jinja2/include.txt @@ -0,0 +1,54 @@ +.. -*- mode: rst -*- + +========================= + Including Jinja2 Templates +========================= + +Jinja2 templates can use the {% include %} directive to include file +fragments which might be common to many configuration files. + +Use the "jinja2_include" suffix for file fragments you will include. + +``/var/lib/bcfg2/Cfg/foo/foo.jinja2`` + +.. code-block:: none + + [global] + setting1 = true + setting2 = false + + {% for x in metadata.groups %}{% include x + '.jinja2_include' ignore missing %} + {% endfor %} + +``/var/lib/bcfg2/Cfg/foo/group1.jinja2_include`` + +.. code-block:: none + + [group1] + setting3 = true + setting4 = false + +``/var/lib/bcfg2/Cfg/foo/group3.jinja2_include`` + +.. code-block:: none + + [group3] + setting7 = true + setting8 = false + +Output +====== + +.. code-block:: none + + [global] + setting1 = true + setting2 = false + + [group1] + setting3 = true + setting4 = false + + [group3] + setting7 = true + setting8 = false diff --git a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgJinja2Generator.py b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgJinja2Generator.py index e36ee78aa..cff9ff61e 100644 --- a/src/lib/Bcfg2/Server/Plugins/Cfg/CfgJinja2Generator.py +++ b/src/lib/Bcfg2/Server/Plugins/Cfg/CfgJinja2Generator.py @@ -2,18 +2,26 @@ <http://jinja.pocoo.org/>`_ templating system to generate :ref:`server-plugins-generators-cfg` files. """ +import os +import sys import Bcfg2.Options from Bcfg2.Server.Plugin import PluginExecutionError, \ DefaultTemplateDataProvider, get_template_data from Bcfg2.Server.Plugins.Cfg import CfgGenerator try: - from jinja2 import Template + from jinja2 import Environment, FileSystemLoader HAS_JINJA2 = True except ImportError: HAS_JINJA2 = False +class RelEnvironment(Environment): + """Override join_path() to enable relative template paths.""" + def join_path(self, template, parent): + return os.path.join(os.path.dirname(parent), template) + + class DefaultJinja2DataProvider(DefaultTemplateDataProvider): """ Template data provider for Jinja2 templates. Jinja2 and Genshi currently differ over the value of the ``path`` variable, @@ -34,6 +42,20 @@ class CfgJinja2Generator(CfgGenerator): #: Handle .jinja2 files __extensions__ = ['jinja2'] + #: ``__loader_cls__`` is the class that will be instantiated to + #: load the template files. It must implement one public function, + #: ``load()``, as :class:`genshi.template.TemplateLoader`. + __loader_cls__ = FileSystemLoader + + #: ``__environment_cls__`` is the class that will be instantiated to + #: store the jinja2 environment. It must implement one public function, + #: ``get_template()``, as :class:`jinja2.Environment`. + __environment_cls__ = RelEnvironment + + #: Ignore ``.jinja2_include`` files so they can be used with the + #: Jinja2 ``{% include ... %}`` directive without raising warnings. + __ignore__ = ["jinja2_include"] + #: Low priority to avoid matching host- or group-specific #: .crypt.jinja2 files __priority__ = 50 @@ -42,11 +64,28 @@ class CfgJinja2Generator(CfgGenerator): CfgGenerator.__init__(self, fname, spec) if not HAS_JINJA2: raise PluginExecutionError("Jinja2 is not available") + self.template = None + encoding = Bcfg2.Options.setup.encoding + self.loader = self.__loader_cls__('/', + encoding=encoding) + self.environment = self.__environment_cls__(loader=self.loader) __init__.__doc__ = CfgGenerator.__init__.__doc__ def get_data(self, entry, metadata): - template = Template(self.data.decode(Bcfg2.Options.setup.encoding)) - return template.render( + if self.template is None: + raise PluginExecutionError("Failed to load template %s" % + self.name) + return self.template.render( get_template_data(entry, metadata, self.name, default=DefaultJinja2DataProvider())) get_data.__doc__ = CfgGenerator.get_data.__doc__ + + def handle_event(self, event): + CfgGenerator.handle_event(self, event) + try: + self.template = \ + self.environment.get_template(self.name) + except: + raise PluginExecutionError("Failed to load template: %s" % + sys.exc_info()[1]) + handle_event.__doc__ = CfgGenerator.handle_event.__doc__ diff --git a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgJinja2Generator.py b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgJinja2Generator.py index 036380d56..8cf02e328 100644 --- a/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgJinja2Generator.py +++ b/testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgJinja2Generator.py @@ -25,23 +25,48 @@ class TestCfgJinja2Generator(TestCfgGenerator): TestCfgGenerator.setUp(self) set_setup_default("repository", datastore) - @patch("Bcfg2.Server.Plugins.Cfg.CfgJinja2Generator.Template") + def test__init(self): + TestCfgGenerator.test__init(self) + cgg = self.get_obj() + self.assertIsInstance(cgg.loader, cgg.__loader_cls__) + self.assertIsInstance(cgg.environment, cgg.__environment_cls__) + + @patch("Bcfg2.Server.Plugins.Cfg.CfgJinja2Generator.Environment") @patch("Bcfg2.Server.Plugins.Cfg.CfgJinja2Generator.get_template_data") - def test_get_data(self, mock_get_template_data, mock_Template): - ccg = self.get_obj() - ccg.data = "data" + def test_get_data(self, mock_get_template_data, mock_Environment): + cgg = self.get_obj() entry = lxml.etree.Element("Path", name="/test.txt") metadata = Mock() + # self.template is currently None + self.assertRaises(PluginExecutionError, + cgg.get_data, entry, metadata) + + cgg.template = mock_Environment.return_value.get_template.return_value + template_vars = dict(name=entry.get("name"), metadata=metadata, - path=ccg.name, - source_path=ccg.name, + path=cgg.name, + source_path=cgg.name, repo=datastore) mock_get_template_data.return_value = template_vars - self.assertEqual(ccg.get_data(entry, metadata), - mock_Template.return_value.render.return_value) - mock_Template.assert_called_with("data".decode(Bcfg2.Options.setup.encoding)) - tmpl = mock_Template.return_value + tmpl = mock_Environment.return_value.get_template.return_value + self.assertEqual(cgg.get_data(entry, metadata), + tmpl.render.return_value) tmpl.render.assert_called_with(template_vars) + + def test_handle_event(self): + cgg = self.get_obj() + cgg.environment = Mock() + event = Mock() + cgg.handle_event(event) + cgg.environment.get_template.assert_called_with( + cgg.name) + + cgg.environment.reset_mock() + cgg.environment.get_template.side_effect = OSError + self.assertRaises(PluginExecutionError, + cgg.handle_event, event) + cgg.environment.get_template.assert_called_with( + cgg.name) |