diff --git a/lib/ansible/plugins/action/template.py b/lib/ansible/plugins/action/template.py index a057e9262c9..6f24fb9c1cc 100644 --- a/lib/ansible/plugins/action/template.py +++ b/lib/ansible/plugins/action/template.py @@ -122,7 +122,7 @@ class ActionModule(ActionBase): old_vars = self._templar._available_variables self._templar.set_available_variables(temp_vars) - resultant = self._templar.template(template_data, preserve_trailing_newlines=True, convert_data=False) + resultant = self._templar.template(template_data, preserve_trailing_newlines=True, escape_backslashes=False, convert_data=False) self._templar.set_available_variables(old_vars) except Exception as e: return dict(failed=True, msg=type(e).__name__ + ": " + str(e)) diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py index b4259bba9fd..3191a5300e8 100644 --- a/lib/ansible/template/__init__.py +++ b/lib/ansible/template/__init__.py @@ -48,7 +48,7 @@ NON_TEMPLATED_TYPES = ( bool, Number ) JINJA2_OVERRIDE = '#jinja2:' -def _preserve_backslashes(data, jinja_env): +def _escape_backslashes(data, jinja_env): """Double backslashes within jinja2 expressions A user may enter something like this in a playbook:: @@ -206,7 +206,7 @@ class Templar: assert isinstance(variables, dict) self._available_variables = variables.copy() - def template(self, variable, convert_bare=False, preserve_trailing_newlines=True, fail_on_undefined=None, overrides=None, convert_data=True): + def template(self, variable, convert_bare=False, preserve_trailing_newlines=True, escape_backslashes=True, fail_on_undefined=None, overrides=None, convert_data=True): ''' Templates (possibly recursively) any given data as input. If convert_bare is set to True, the given data will be wrapped as a jinja2 variable ('{{foo}}') @@ -234,7 +234,7 @@ class Templar: elif resolved_val is None: return C.DEFAULT_NULL_REPRESENTATION - result = self._do_template(variable, preserve_trailing_newlines=preserve_trailing_newlines, fail_on_undefined=fail_on_undefined, overrides=overrides) + result = self._do_template(variable, preserve_trailing_newlines=preserve_trailing_newlines, escape_backslashes=escape_backslashes, fail_on_undefined=fail_on_undefined, overrides=overrides) if convert_data: # if this looks like a dictionary or list, convert it to such using the safe_eval method @@ -316,7 +316,7 @@ class Templar: else: raise AnsibleError("lookup plugin (%s) not found" % name) - def _do_template(self, data, preserve_trailing_newlines=True, fail_on_undefined=None, overrides=None): + def _do_template(self, data, preserve_trailing_newlines=True, escape_backslashes=True, fail_on_undefined=None, overrides=None): # For preserving the number of input newlines in the output (used # later in this method) @@ -346,7 +346,10 @@ class Templar: myenv.filters.update(self._get_filters()) myenv.tests.update(self._get_tests()) - data = _preserve_backslashes(data, myenv) + if escape_backslashes: + # Allow users to specify backslashes in playbooks as "\\" + # instead of as "\\\\". + data = _escape_backslashes(data, myenv) try: t = myenv.from_string(data) diff --git a/test/units/template/test_templar.py b/test/units/template/test_templar.py index a5da0b7686a..f7f22c75320 100644 --- a/test/units/template/test_templar.py +++ b/test/units/template/test_templar.py @@ -33,12 +33,6 @@ from units.mock.loader import DictDataLoader class TestTemplar(unittest.TestCase): def setUp(self): - pass - - def tearDown(self): - pass - - def test_templar_simple(self): fake_loader = DictDataLoader({ "/path/to/my_file.txt": "foo\n", }) @@ -54,8 +48,14 @@ class TestTemplar(unittest.TestCase): var_list=[1], recursive="{{recursive}}", ) - templar = Templar(loader=fake_loader, variables=variables) + self.templar = Templar(loader=fake_loader, variables=variables) + def tearDown(self): + pass + + def test_templar_simple(self): + + templar = self.templar # test some basic templating self.assertEqual(templar.template("{{foo}}"), "bar") self.assertEqual(templar.template("{{foo}}\n"), "bar\n") @@ -89,6 +89,20 @@ class TestTemplar(unittest.TestCase): # variables must be a dict() for set_available_variables() self.assertRaises(AssertionError, templar.set_available_variables, "foo=bam") + def test_templar_escape_backslashes(self): + # Rule of thumb: If escape backslashes is True you should end up with + # the same number of backslashes as when you started. + self.assertEqual(self.templar.template("\t{{foo}}", escape_backslashes=True), "\tbar") + self.assertEqual(self.templar.template("\t{{foo}}", escape_backslashes=False), "\tbar") + self.assertEqual(self.templar.template("\\{{foo}}", escape_backslashes=True), "\\bar") + self.assertEqual(self.templar.template("\\{{foo}}", escape_backslashes=False), "\\bar") + self.assertEqual(self.templar.template("\\{{foo + '\t' }}", escape_backslashes=True), "\\bar\t") + self.assertEqual(self.templar.template("\\{{foo + '\t' }}", escape_backslashes=False), "\\bar\t") + self.assertEqual(self.templar.template("\\{{foo + '\\t' }}", escape_backslashes=True), "\\bar\\t") + self.assertEqual(self.templar.template("\\{{foo + '\\t' }}", escape_backslashes=False), "\\bar\t") + self.assertEqual(self.templar.template("\\{{foo + '\\\\t' }}", escape_backslashes=True), "\\bar\\\\t") + self.assertEqual(self.templar.template("\\{{foo + '\\\\t' }}", escape_backslashes=False), "\\bar\\t") + def test_template_jinja2_extensions(self): fake_loader = DictDataLoader({}) templar = Templar(loader=fake_loader) diff --git a/test/units/template/test_template_utilities.py b/test/units/template/test_template_utilities.py index 55cd8eb02fc..da0ed0db5e2 100644 --- a/test/units/template/test_template_utilities.py +++ b/test/units/template/test_template_utilities.py @@ -22,7 +22,7 @@ __metaclass__ = type import jinja2 from ansible.compat.tests import unittest -from ansible.template import _preserve_backslashes, _count_newlines_from_end +from ansible.template import _escape_backslashes, _count_newlines_from_end # These are internal utility functions only needed for templating. They're # algorithmic so good candidates for unittesting by themselves @@ -78,7 +78,7 @@ class TestBackslashEscape(unittest.TestCase): def test_backslash_escaping(self): for test in self.test_data: - intermediate = _preserve_backslashes(test['template'], self.env) + intermediate = _escape_backslashes(test['template'], self.env) self.assertEquals(intermediate, test['intermediate']) template = jinja2.Template(intermediate) args = test['args']