summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSol Jerome <sol.jerome@gmail.com>2014-11-17 14:31:31 -0600
committerSol Jerome <sol.jerome@gmail.com>2014-11-17 14:31:31 -0600
commitc544b18a985edd7444593e75ad52483f4842c119 (patch)
tree6b4504ec02bcc339217dcf754ae6e51f9e697685
parentb8310f6f2b2c440704913af53b4af90b9ce13e8c (diff)
parent0512f18f11b2ba9432a8be8eb2c05b6e290b976b (diff)
downloadbcfg2-c544b18a985edd7444593e75ad52483f4842c119.tar.gz
bcfg2-c544b18a985edd7444593e75ad52483f4842c119.tar.bz2
bcfg2-c544b18a985edd7444593e75ad52483f4842c119.zip
Merge branch 'include2' of https://github.com/gordonmessmer/bcfg2
-rw-r--r--doc/server/plugins/generators/examples/jinja2/extends.txt61
-rw-r--r--doc/server/plugins/generators/examples/jinja2/include.txt54
-rw-r--r--src/lib/Bcfg2/Server/Plugins/Cfg/CfgJinja2Generator.py45
-rw-r--r--testsuite/Testsrc/Testlib/TestServer/TestPlugins/TestCfg/TestCfgJinja2Generator.py45
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)