From a6183d93d3d495d25c20a379de8bab74b3ce6d65 Mon Sep 17 00:00:00 2001 From: Erik Johnson Date: Thu, 30 Mar 2017 15:00:18 -0500 Subject: [PATCH] Preserve windows newlines in salt.template.compile_template() Test included. --- salt/template.py | 7 +++++ tests/unit/template_test.py | 54 ++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/salt/template.py b/salt/template.py index 7e4c14040e..1060bc9999 100644 --- a/salt/template.py +++ b/salt/template.py @@ -80,6 +80,8 @@ def compile_template(template, # Get the list of render funcs in the render pipe line. render_pipe = template_shebang(template, renderers, default, blacklist, whitelist, input_data) + windows_newline = '\r\n' in input_data + input_data = string_io(input_data) for render, argline in render_pipe: # For GPG renderer, input_data can be an OrderedDict (from YAML) or dict (from py renderer). @@ -120,6 +122,11 @@ def compile_template(template, # structure. We don't want to log this, so ignore this # exception. pass + + # Preserve newlines from original template + if windows_newline and '\r\n' not in ret: + return ret.replace('\n', '\r\n') + return ret diff --git a/tests/unit/template_test.py b/tests/unit/template_test.py index 48ff4643dc..d0bbe9c30f 100644 --- a/tests/unit/template_test.py +++ b/tests/unit/template_test.py @@ -7,8 +7,9 @@ from __future__ import absolute_import # Import Salt Testing libs -from salttesting import TestCase +from salttesting import skipIf, TestCase from salttesting.helpers import ensure_in_syspath +from salttesting.mock import NO_MOCK, NO_MOCK_REASON, MagicMock ensure_in_syspath('../') @@ -29,6 +30,57 @@ class TemplateTestCase(TestCase): ret = template.compile_template(['1', '2', '3'], None, None, None, None) self.assertDictEqual(ret, {}) + @skipIf(NO_MOCK, NO_MOCK_REASON) + def test_compile_template_preserves_windows_newlines(self): + ''' + Test to ensure that a file with Windows newlines, when rendered by a + template renderer, does not eat the CR character. + + NOTE: template renderers actually return StringIO instances, but since + we're mocking the return from the renderer there's no sense in mocking + a StringIO only to have to join its contents to get it back to a string + for the purposes of comparing the results. + ''' + input_data_windows = 'foo\r\nbar\r\nbaz\r\n' + input_data_non_windows = input_data_windows.replace('\r\n', '\n') + renderer = 'test' + rend = {renderer: MagicMock(return_value=input_data_non_windows)} + blacklist = whitelist = [] + + ret = template.compile_template( + ':string:', + rend, + renderer, + blacklist, + whitelist, + input_data=input_data_windows) + # Even though the mocked renderer returned a string without the windows + # newlines, the compiled template should still have them. + self.assertEqual(ret, input_data_windows) + + # Now test that we aren't adding them in unnecessarily. + ret = template.compile_template( + ':string:', + rend, + renderer, + blacklist, + whitelist, + input_data=input_data_non_windows) + self.assertEqual(ret, input_data_non_windows) + + # Finally, ensure that we're not unnecessarily replacing the \n with + # \r\n in the event that the renderer returned a string with the + # windows newlines intact. + rend[renderer] = MagicMock(return_value=input_data_windows) + ret = template.compile_template( + ':string:', + rend, + renderer, + blacklist, + whitelist, + input_data=input_data_windows) + self.assertEqual(ret, input_data_windows) + def test_check_render_pipe_str(self): ''' Check that all renderers specified in the pipe string are available.