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" +