From 9ca8c3262c20c12513e4a52694b37ebd7f16cb16 Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Tue, 8 Apr 2014 09:05:07 -0500 Subject: [PATCH 1/3] Switch module safe_eval to ast.literal_eval --- lib/ansible/module_utils/basic.py | 42 +++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index 94326711ba2..15c0f0eef32 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -101,6 +101,44 @@ except ImportError: import syslog has_journal = False +try: + from ast import literal_eval as _literal_eval +except ImportError: + # a replacement for literal_eval that works with python 2.4. from: + # https://mail.python.org/pipermail/python-list/2009-September/551880.html + # which is essentially a cut/past from an earlier (2.6) version of python's + # ast.py + from compiler import parse + from compiler.ast import * + def _literal_eval(node_or_string): + """ + Safely evaluate an expression node or a string containing a Python + expression. The string or node provided may only consist of the following + Python literal structures: strings, numbers, tuples, lists, dicts, booleans, + and None. + """ + _safe_names = {'None': None, 'True': True, 'False': False} + if isinstance(node_or_string, basestring): + node_or_string = parse(node_or_string, mode='eval') + if isinstance(node_or_string, Expression): + node_or_string = node_or_string.node + def _convert(node): + if isinstance(node, Const) and isinstance(node.value, (basestring, int, float, long, complex)): + return node.value + elif isinstance(node, Tuple): + return tuple(map(_convert, node.nodes)) + elif isinstance(node, List): + return list(map(_convert, node.nodes)) + elif isinstance(node, Dict): + return dict((_convert(k), _convert(v)) for k, v in node.items) + elif isinstance(node, Name): + if node.name in _safe_names: + return _safe_names[node.name] + elif isinstance(node, UnarySub): + return -_convert(node.expr) + raise ValueError('malformed string') + return _convert(node_or_string) + FILE_COMMON_ARGUMENTS=dict( src = dict(), mode = dict(), @@ -655,9 +693,9 @@ class AnsibleModule(object): try: result = None if not locals: - result = eval(str) + result = _literal_eval(str) else: - result = eval(str, None, locals) + result = _literal_eval(str, None, locals) if include_exceptions: return (result, None) else: From 567f3819b19848c664ca755d019a95913db92557 Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Tue, 8 Apr 2014 11:42:29 -0500 Subject: [PATCH 2/3] Fixing newline escapes in lineinfile Fixes #5679 --- library/files/lineinfile | 12 ++++-- .../roles/test_lineinfile/tasks/main.yml | 39 ++++++++++++++++++- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/library/files/lineinfile b/library/files/lineinfile index f781911ccd1..f5181cbe8ff 100644 --- a/library/files/lineinfile +++ b/library/files/lineinfile @@ -18,6 +18,7 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +import pipes import re import os import tempfile @@ -351,9 +352,14 @@ def main(): if ins_bef is None and ins_aft is None: ins_aft = 'EOF' - # Replace the newline character with an actual newline. Don't replace - # escaped \\n, hence sub and not str.replace. - line = re.sub(r'\n', os.linesep, params['line']) + # Replace escape sequences like '\n' while being sure + # not to replace octal escape sequences (\ooo) since they + # match the backref syntax + if backrefs: + line = re.sub(r'(\\[0-9]{1,3})', r'\\\1', params['line']) + else: + line = params['line'] + line = module.safe_eval(pipes.quote(line)) present(module, dest, params['regexp'], line, ins_aft, ins_bef, create, backup, backrefs) diff --git a/test/integration/roles/test_lineinfile/tasks/main.yml b/test/integration/roles/test_lineinfile/tasks/main.yml index f59979473b9..efc55c87f05 100644 --- a/test/integration/roles/test_lineinfile/tasks/main.yml +++ b/test/integration/roles/test_lineinfile/tasks/main.yml @@ -114,7 +114,7 @@ - "result.stat.md5 == 'd5955ee042139dfef16dbe3a7334475f'" - name: replace a line with backrefs - lineinfile: dest={{output_dir}}/test.txt state=present line="This is line 3" backrefs=yes regexp="^(REF) .* \1$" + lineinfile: dest={{output_dir}}/test.txt state=present line="This is line 3" backrefs=yes regexp="^(REF) .* \\1$" register: result - name: assert that the line with backrefs was changed @@ -209,3 +209,40 @@ that: - "result.stat.md5 == 'fef1d487711facfd7aa2c87d788c19d9'" +- name: insert a multiple lines at the end of the file + lineinfile: dest={{output_dir}}/test.txt state=present line="This is a line\nwith \\\n character" insertafter="EOF" + register: result + +- name: assert that the multiple lines was inserted + assert: + that: + - "result.changed == true" + - "result.msg == 'line added'" + +- stat: path={{output_dir}}/test.txt + register: result + +- name: assert test md5 matches after insert the multiple lines + assert: + that: + - "result.stat.md5 == 'c2510d5bc8fdef8e752b8f8e74c784c2'" + +- name: replace a line with backrefs included in the line + lineinfile: dest={{output_dir}}/test.txt state=present line="New \\1 created with the backref" backrefs=yes regexp="^This is (line 4)$" + register: result + +- name: assert that the line with backrefs was changed + assert: + that: + - "result.changed == true" + - "result.msg == 'line replaced'" + +- name: stat the test after the backref line was replaced + stat: path={{output_dir}}/test.txt + register: result + +- name: assert test md5 matches after backref line was replaced + assert: + that: + - "result.stat.md5 == '65f955c2a9722fd43d07103d7756ff9b'" + From 22ae21ab43c1f94807e282b7d50987af13a6a9d6 Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Fri, 20 Jun 2014 10:12:14 -0500 Subject: [PATCH 3/3] Exclude ps1 modules from the TestModules unittest Fixes #7864 --- test/units/TestModules.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/units/TestModules.py b/test/units/TestModules.py index 54e3ec3213f..83c2b7c3986 100644 --- a/test/units/TestModules.py +++ b/test/units/TestModules.py @@ -15,7 +15,9 @@ class TestModules(unittest.TestCase): for path in paths: for (dirpath, dirnames, filenames) in os.walk(path): for filename in filenames: - module_list.append(os.path.join(dirpath, filename)) + (path, ext) = os.path.splitext(filename) + if ext != ".ps1": + module_list.append(os.path.join(dirpath, filename)) return module_list def test_ast_parse(self):