From 515fd9e91589e21c7b9d73b797a825e1c5a56ffa Mon Sep 17 00:00:00 2001 From: Dale Sedivec Date: Sat, 6 Apr 2013 21:20:10 -0500 Subject: [PATCH] copy action plug-in check mode respects force=no The copy action accepts force=no, which tells it not to replace an existing file even if it differs from the source. The copy action plug-in wasn't respecting this option when operated in check mode, so it would report that changes are necessary in check mode even though copy would make no changes when run normally. Runner._remote_md5 was changed to make the logic for setting rc perhaps a little more clear, and to make sure that rc=0 when the file does not exist. --- lib/ansible/runner/__init__.py | 6 +++++- lib/ansible/runner/action_plugins/copy.py | 5 +++++ test/TestRunner.py | 9 ++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index 6c5737f5ecc..d13876a64ef 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -584,7 +584,11 @@ class Runner(object): ''' takes a remote md5sum without requiring python, and returns 0 if no file ''' path = pipes.quote(path) - test = "rc=0; [ -r \"%s\" ] || rc=2; [ -f \"%s\" ] || rc=1; [ -d \"%s\" ] && rc=3" % (path, path, path) + test = ('if [ ! -e \"%s\" ]; then rc=0;' + 'elif [ -d \"%s\" ]; then rc=3;' + 'elif [ ! -f \"%s\" ]; then rc=1;' + 'elif [ ! -r \"%s\" ]; then rc=2;' + 'else rc=0; fi') % ((path,) * 4) md5s = [ "(/usr/bin/md5sum %s 2>/dev/null)" % path, # Linux "(/sbin/md5sum -q %s 2>/dev/null)" % path, # ? diff --git a/lib/ansible/runner/action_plugins/copy.py b/lib/ansible/runner/action_plugins/copy.py index c633366e46f..bc03d15dedf 100644 --- a/lib/ansible/runner/action_plugins/copy.py +++ b/lib/ansible/runner/action_plugins/copy.py @@ -42,6 +42,7 @@ class ActionModule(object): source = options.get('src', None) content = options.get('content', None) dest = options.get('dest', None) + force = utils.boolean(options.get('force', 'yes')) if (source is None and content is None and not 'first_available_file' in inject) or dest is None: result=dict(failed=True, msg="src (or content) and dest are required") @@ -105,6 +106,10 @@ class ActionModule(object): dest = os.path.join(dest, os.path.basename(source)) remote_md5 = self.runner._remote_md5(conn, tmp, dest) + # remote_md5 == '0' would mean that the file does not exist. + if remote_md5 != '0' and not force: + return ReturnData(conn=conn, result=dict(changed=False)) + exec_rc = None if local_md5 != remote_md5: diff --git a/test/TestRunner.py b/test/TestRunner.py index 21afb4aae46..63f89dc3f3c 100644 --- a/test/TestRunner.py +++ b/test/TestRunner.py @@ -63,12 +63,13 @@ class TestRunner(unittest.TestCase): filename = os.path.join(self.stage_dir, filename) return filename - def _run(self, module_name, module_args, background=0): + def _run(self, module_name, module_args, background=0, check_mode=False): ''' run a module and get the localhost results ''' self.runner.module_name = module_name args = ' '.join(module_args) self.runner.module_args = args self.runner.background = background + self.runner.check = check_mode results = self.runner.run() # when using nosetests this will only show up on failure # which is pretty useful @@ -117,6 +118,12 @@ class TestRunner(unittest.TestCase): "dest=%s" % output, ]) assert result['changed'] is False + with open(output, "a") as output_stream: + output_stream.write("output file now differs from input") + result = self._run('copy', + ["src=%s" % input_, "dest=%s" % output, "force=no"], + check_mode=True) + assert result['changed'] is False def test_command(self): # test command module, change trigger, etc