From a8c02b7049b79032ab0c05ed68f1beafbc7da7ba Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Mon, 2 Mar 2015 12:25:41 -0800 Subject: [PATCH] Rework the shell quoting of remote checksumming Instead of getting rid of pipes.quote, use pipes.quote and get rid of the manually entered toplevel quotes. This should properly escape backslashes, quotes, and other characters. Also fix the new checksumming python "one-liner" for csh. ansible_shell_type needs to be set to csh. Fixes #10363 Fixes #10353 --- lib/ansible/runner/shell_plugins/csh.py | 3 ++ lib/ansible/runner/shell_plugins/sh.py | 16 ++++++-- test/integration/check_mode.yml | 1 + .../roles/prepare_tests/tasks/main.yml | 2 + .../roles/test_check_mode/files/foo.txt | 1 + .../roles/test_check_mode/meta/main.yml | 3 ++ .../roles/test_check_mode/tasks/main.yml | 39 +++++++++++++++++++ .../roles/test_check_mode/templates/foo.j2 | 1 + .../roles/test_check_mode/vars/main.yml | 1 + .../roles/test_template/tasks/main.yml | 36 +++++++++++++++++ 10 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 test/integration/roles/test_check_mode/files/foo.txt create mode 100644 test/integration/roles/test_check_mode/meta/main.yml create mode 100644 test/integration/roles/test_check_mode/tasks/main.yml create mode 100644 test/integration/roles/test_check_mode/templates/foo.j2 create mode 100644 test/integration/roles/test_check_mode/vars/main.yml diff --git a/lib/ansible/runner/shell_plugins/csh.py b/lib/ansible/runner/shell_plugins/csh.py index 137c013c12f..4e9f8c8af74 100644 --- a/lib/ansible/runner/shell_plugins/csh.py +++ b/lib/ansible/runner/shell_plugins/csh.py @@ -19,5 +19,8 @@ from ansible.runner.shell_plugins.sh import ShellModule as ShModule class ShellModule(ShModule): + # How to end lines in a python script one-liner + _SHELL_EMBEDDED_PY_EOL = '\\\n' + def env_prefix(self, **kwargs): return 'env %s' % super(ShellModule, self).env_prefix(**kwargs) diff --git a/lib/ansible/runner/shell_plugins/sh.py b/lib/ansible/runner/shell_plugins/sh.py index fc4063c2044..5fb0dc3add3 100644 --- a/lib/ansible/runner/shell_plugins/sh.py +++ b/lib/ansible/runner/shell_plugins/sh.py @@ -24,6 +24,9 @@ _USER_HOME_PATH_RE = re.compile(r'^~[_.A-Za-z0-9][-_.A-Za-z0-9]*$') class ShellModule(object): + # How to end lines in a python script one-liner + _SHELL_EMBEDDED_PY_EOL = '\n' + def env_prefix(self, **kwargs): '''Build command prefix with environment variables.''' env = dict( @@ -103,14 +106,19 @@ class ShellModule(object): # 2: No read permissions on the file # 3: File is a directory # 4: No python interpreter - test = "rc=flag; [ -r \'%(p)s\' ] || rc=2; [ -f \'%(p)s\' ] || rc=1; [ -d \'%(p)s\' ] && rc=3; %(i)s -V 2>/dev/null || rc=4; [ x\"$rc\" != \"xflag\" ] && echo \"${rc}\"\' %(p)s\' && exit 0" % dict(p=path, i=python_interp) + + # Quoting gets complex here. We're writing a python string that's + # used by a variety of shells on the remote host to invoke a python + # "one-liner". + shell_escaped_path = pipes.quote(path) + test = "rc=flag; [ -r %(p)s ] || rc=2; [ -f %(p)s ] || rc=1; [ -d %(p)s ] && rc=3; %(i)s -V 2>/dev/null || rc=4; [ x\"$rc\" != \"xflag\" ] && echo \"${rc} \"%(p)s && exit 0" % dict(p=shell_escaped_path, i=python_interp) csums = [ - "(%s -c 'import hashlib; BLOCKSIZE = 65536; hasher = hashlib.sha1();\nafile = open(\"%s\", \"rb\")\nbuf = afile.read(BLOCKSIZE)\nwhile len(buf) > 0:\n\thasher.update(buf)\n\tbuf = afile.read(BLOCKSIZE)\nafile.close()\nprint(hasher.hexdigest())' 2>/dev/null)" % (python_interp, path), # Python > 2.4 (including python3) - "(%s -c 'import sha; BLOCKSIZE = 65536; hasher = sha.sha();\nafile = open(\"%s\", \"rb\")\nbuf = afile.read(BLOCKSIZE)\nwhile len(buf) > 0:\n\thasher.update(buf)\n\tbuf = afile.read(BLOCKSIZE)\nafile.close()\nprint(hasher.hexdigest())' 2>/dev/null)" % (python_interp, path), # Python == 2.4 + "({0} -c 'import hashlib; BLOCKSIZE = 65536; hasher = hashlib.sha1();{2}afile = open(\"'{1}'\", \"rb\"){2}buf = afile.read(BLOCKSIZE){2}while len(buf) > 0:{2}\thasher.update(buf){2}\tbuf = afile.read(BLOCKSIZE){2}afile.close(){2}print(hasher.hexdigest())' 2>/dev/null)".format(python_interp, shell_escaped_path, self._SHELL_EMBEDDED_PY_EOL), # Python > 2.4 (including python3) + "({0} -c 'import sha; BLOCKSIZE = 65536; hasher = sha.sha();{2}afile = open(\"'{1}'\", \"rb\"){2}buf = afile.read(BLOCKSIZE){2}while len(buf) > 0:{2}\thasher.update(buf){2}\tbuf = afile.read(BLOCKSIZE){2}afile.close(){2}print(hasher.hexdigest())' 2>/dev/null)".format(python_interp, shell_escaped_path, self._SHELL_EMBEDDED_PY_EOL), # Python == 2.4 ] cmd = " || ".join(csums) - cmd = "%s; %s || (echo \'0 %s\')" % (test, cmd, path) + cmd = "%s; %s || (echo \'0 \'%s)" % (test, cmd, shell_escaped_path) return cmd def build_module_command(self, env_string, shebang, cmd, rm_tmp=None): diff --git a/test/integration/check_mode.yml b/test/integration/check_mode.yml index f2679699e0a..ff04a690ec0 100644 --- a/test/integration/check_mode.yml +++ b/test/integration/check_mode.yml @@ -1,3 +1,4 @@ - hosts: testhost roles: - { role: test_always_run, tags: test_always_run } + - { role: test_check_mode, tags: test_check_mode } diff --git a/test/integration/roles/prepare_tests/tasks/main.yml b/test/integration/roles/prepare_tests/tasks/main.yml index fbdb7f861ca..3641880baa1 100644 --- a/test/integration/roles/prepare_tests/tasks/main.yml +++ b/test/integration/roles/prepare_tests/tasks/main.yml @@ -19,11 +19,13 @@ - name: clean out the test directory file: name={{output_dir|mandatory}} state=absent + always_run: True tags: - prepare - name: create the test directory file: name={{output_dir}} state=directory + always_run: True tags: - prepare diff --git a/test/integration/roles/test_check_mode/files/foo.txt b/test/integration/roles/test_check_mode/files/foo.txt new file mode 100644 index 00000000000..3e96db9b3ec --- /dev/null +++ b/test/integration/roles/test_check_mode/files/foo.txt @@ -0,0 +1 @@ +templated_var_loaded diff --git a/test/integration/roles/test_check_mode/meta/main.yml b/test/integration/roles/test_check_mode/meta/main.yml new file mode 100644 index 00000000000..1050c23ce30 --- /dev/null +++ b/test/integration/roles/test_check_mode/meta/main.yml @@ -0,0 +1,3 @@ +dependencies: + - prepare_tests + diff --git a/test/integration/roles/test_check_mode/tasks/main.yml b/test/integration/roles/test_check_mode/tasks/main.yml new file mode 100644 index 00000000000..d8edf5b285d --- /dev/null +++ b/test/integration/roles/test_check_mode/tasks/main.yml @@ -0,0 +1,39 @@ +# test code for the template module +# (c) 2014, Michael DeHaan + +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +- name: fill in a basic template in check mode + template: src=foo.j2 dest={{output_dir}}/foo.templated mode=0644 + register: template_result + +- name: verify that the file was marked as changed in check mode + assert: + that: + - "template_result.changed == true" + +- name: Actually create the file + template: src=foo.j2 dest={{output_dir}}/foo.templated2 mode=0644 + always_run: True + +- name: fill in a basic template in check mode + template: src=foo.j2 dest={{output_dir}}/foo.templated2 mode=0644 + register: template_result2 + +- name: verify that the file was marked as not changed in check mode + assert: + that: + - "template_result2.changed == false" diff --git a/test/integration/roles/test_check_mode/templates/foo.j2 b/test/integration/roles/test_check_mode/templates/foo.j2 new file mode 100644 index 00000000000..55aab8f1ea1 --- /dev/null +++ b/test/integration/roles/test_check_mode/templates/foo.j2 @@ -0,0 +1 @@ +{{ templated_var }} diff --git a/test/integration/roles/test_check_mode/vars/main.yml b/test/integration/roles/test_check_mode/vars/main.yml new file mode 100644 index 00000000000..1e8f64ccf44 --- /dev/null +++ b/test/integration/roles/test_check_mode/vars/main.yml @@ -0,0 +1 @@ +templated_var: templated_var_loaded diff --git a/test/integration/roles/test_template/tasks/main.yml b/test/integration/roles/test_template/tasks/main.yml index 8ab6d4ac199..2568843cf7e 100644 --- a/test/integration/roles/test_template/tasks/main.yml +++ b/test/integration/roles/test_template/tasks/main.yml @@ -138,3 +138,39 @@ that: - "template_result.changed == False" +# Test strange filenames + +- name: Create a temp dir for filename tests + file: + state: directory + dest: '{{ output_dir }}/filename-tests' + +- name: create a file with an unusual filename + template: + src: foo.j2 + dest: "{{ output_dir }}/filename-tests/foo t'e~m\\plated" + register: template_result + +- assert: + that: + - "template_result.changed == True" + +- name: check that the unusual filename was created + command: "ls {{ output_dir }}/filename-tests/" + register: unusual_results + +- assert: + that: + - "\"foo t'e~m\\plated\" in unusual_results.stdout_lines" + - "{{unusual_results.stdout_lines| length}} == 1" + +- name: check that the unusual filename can be checked for changes + template: + src: foo.j2 + dest: "{{ output_dir }}/filename-tests/foo t'e~m\\plated" + register: template_result + +- assert: + that: + - "template_result.changed == False" +