diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py index ff6b747cb2a..cc8276ec0d3 100644 --- a/lib/ansible/executor/task_executor.py +++ b/lib/ansible/executor/task_executor.py @@ -570,10 +570,13 @@ class TaskExecutor: vars_copy.update(result['ansible_facts']) vars_copy.update({'ansible_facts': result['ansible_facts']}) - # set the failed property if the result has a non-zero rc. This will be - # overridden below if the failed_when property is set - if result.get('rc', 0) != 0: - result['failed'] = True + # set the failed property if it was missing. + if 'failed' not in result: + result['failed'] = False + + # set the changed property if it was missing. + if 'changed' not in result: + result['changed'] = False # if we didn't skip this task, use the helpers to evaluate the changed/ # failed_when properties diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index ac10d61294e..973b5c6edf6 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -2023,6 +2023,9 @@ class AnsibleModule(object): assert 'msg' in kwargs, "implementation error -- msg to explain the error is required" kwargs['failed'] = True + if not 'changed' in kwargs: + kwargs['changed'] = False + self.do_cleanup_files() self._return_formatted(kwargs) sys.exit(1) diff --git a/lib/ansible/module_utils/powershell.ps1 b/lib/ansible/module_utils/powershell.ps1 index bdcded39747..0823e98573e 100644 --- a/lib/ansible/module_utils/powershell.ps1 +++ b/lib/ansible/module_utils/powershell.ps1 @@ -62,6 +62,10 @@ Function Exit-Json($obj) $obj = @{ } } + if (-not $obj.ContainsKey('changed')) { + Set-Attr $obj "changed" $false + } + echo $obj | ConvertTo-Json -Compress -Depth 99 Exit } @@ -88,6 +92,10 @@ Function Fail-Json($obj, $message = $null) Set-Attr $obj "msg" $message Set-Attr $obj "failed" $true + if (-not $obj.ContainsKey('changed')) { + Set-Attr $obj "changed" $false + } + echo $obj | ConvertTo-Json -Compress -Depth 99 Exit 1 } diff --git a/lib/ansible/modules/commands/command.py b/lib/ansible/modules/commands/command.py index 0f5037e1d24..01a83982bf5 100644 --- a/lib/ansible/modules/commands/command.py +++ b/lib/ansible/modules/commands/command.py @@ -204,7 +204,7 @@ def main(): if err is None: err = b('') - module.exit_json( + result = dict( cmd = args, stdout = out.rstrip(b("\r\n")), stderr = err.rstrip(b("\r\n")), @@ -216,5 +216,11 @@ def main(): warnings = warnings ) + if rc != 0: + module.fail_json(msg='non-zero return code', **result) + + module.exit_json(**result) + + if __name__ == '__main__': main() diff --git a/lib/ansible/modules/commands/expect.py b/lib/ansible/modules/commands/expect.py index 2101e0562c8..b7d3e08b06d 100644 --- a/lib/ansible/modules/commands/expect.py +++ b/lib/ansible/modules/commands/expect.py @@ -221,7 +221,7 @@ def main(): if out is None: out = '' - ret = dict( + result = dict( cmd=args, stdout=out.rstrip('\r\n'), rc=rc, @@ -231,11 +231,12 @@ def main(): changed=True, ) - if rc is not None: - module.exit_json(**ret) - else: - ret['msg'] = 'command exceeded timeout' - module.fail_json(**ret) + if rc is None: + module.fail_json(msg='command exceeded timeout', **result) + elif rc != 0: + module.fail_json(msg='non-zero return code', **result) + + module.exit_json(**result) if __name__ == '__main__': diff --git a/lib/ansible/plugins/action/raw.py b/lib/ansible/plugins/action/raw.py index 34677457e70..cf43ee69947 100644 --- a/lib/ansible/plugins/action/raw.py +++ b/lib/ansible/plugins/action/raw.py @@ -42,4 +42,8 @@ class ActionModule(ActionBase): result['changed'] = True + if 'rc' in result and result['rc'] != 0: + result['failed'] = True + result['msg'] = 'non-zero return code' + return result diff --git a/lib/ansible/plugins/action/script.py b/lib/ansible/plugins/action/script.py index 2111246acaf..9327ab204af 100644 --- a/lib/ansible/plugins/action/script.py +++ b/lib/ansible/plugins/action/script.py @@ -93,4 +93,8 @@ class ActionModule(ActionBase): result['changed'] = True + if 'rc' in result and result['rc'] != 0: + result['failed'] = True + result['msg'] = 'non-zero return code' + return result diff --git a/test/integration/roles/test_ec2_eip/tasks/main.yml b/test/integration/roles/test_ec2_eip/tasks/main.yml index 9f17f557e37..cb92902133d 100644 --- a/test/integration/roles/test_ec2_eip/tasks/main.yml +++ b/test/integration/roles/test_ec2_eip/tasks/main.yml @@ -111,7 +111,6 @@ - name: assert new EIP was assigned assert: that: - - '"failed" not in result' - '"public_ip" in result' @@ -128,10 +127,6 @@ instance_id={{ instance_id }} register: result -- name: assert success disassociate EIP associated with instance - assert: - that: - - '"failed" not in result' # eip allocated:1 assigned:0 @@ -152,7 +147,6 @@ - name: assert new EIP was assigned assert: that: - - '"failed" not in result' - '"public_ip" in result' @@ -176,11 +170,6 @@ ec2_secret_key={{ ec2_secret_key }} register: result -- name: assert EIP deactivated - assert: - that: - - '"failed" not in result' - # eip allocated:0 assigned:0 @@ -205,7 +194,6 @@ - name: assert provision EIP with instance_id assert: that: - - '"failed" not in result' - '"public_ip" in result' @@ -245,7 +233,6 @@ - name: assert VPC EIP creation assert: that: - - '"failed" not in result' - '"public_ip" in result' @@ -268,7 +255,6 @@ #- name: assert new VPC EIP was assigned # assert: # that: -# - '"failed" not in result' # - '"public_ip" in result' # # @@ -307,7 +293,6 @@ - name: assert environment variable EC2_REGION assert: that: - - '"failed" not in result' - '"public_ip" in result' diff --git a/test/integration/targets/ec2_elb_lb/tasks/main.yml b/test/integration/targets/ec2_elb_lb/tasks/main.yml index 1e933c2fe56..728fef7b51f 100644 --- a/test/integration/targets/ec2_elb_lb/tasks/main.yml +++ b/test/integration/targets/ec2_elb_lb/tasks/main.yml @@ -60,7 +60,6 @@ - assert: that: - 'info.changed' - - '"failed" not in info' - 'info.elb.status == "created"' - '"us-east-1c" in info.elb.zones' - '"us-east-1d" in info.elb.zones' @@ -124,7 +123,6 @@ - assert: that: - - '"failed" not in info' - 'info.elb.status == "ok"' - 'info.changed' - 'info.elb.zones[0] == "us-east-1b"' @@ -154,7 +152,6 @@ - assert: that: - - '"failed" not in info' - 'info.changed' - 'info.elb.status == "ok"' - '"us-east-1b" in info.elb.zones' @@ -187,7 +184,6 @@ - assert: that: - - '"failed" not in info' - 'info.elb.status == "ok"' - 'info.changed' - '[80, 81, "HTTP", "HTTP"] in info.elb.listeners' @@ -220,7 +216,6 @@ - assert: that: - - '"failed" not in info' - 'info.elb.status == "ok"' - 'info.changed' - '[80, 81, "HTTP", "HTTP"] in info.elb.listeners' diff --git a/test/integration/targets/ec2_group/tasks/main.yml b/test/integration/targets/ec2_group/tasks/main.yml index 4e5ae3f9912..b4ff567d941 100644 --- a/test/integration/targets/ec2_group/tasks/main.yml +++ b/test/integration/targets/ec2_group/tasks/main.yml @@ -188,11 +188,6 @@ state=absent register: result - - name: assert state=absent - assert: - that: - - '"failed" not in result' - # ============================================================ - name: test state=present (expected changed=true) ec2_group: diff --git a/test/integration/targets/ec2_key/tasks/main.yml b/test/integration/targets/ec2_key/tasks/main.yml index 637d67ab0ef..1881b5cfa6e 100644 --- a/test/integration/targets/ec2_key/tasks/main.yml +++ b/test/integration/targets/ec2_key/tasks/main.yml @@ -157,11 +157,6 @@ state=absent register: result - - name: assert state=absent with key_material - assert: - that: - - '"failed" not in result' - # ============================================================ - name: test state=present without key_material ec2_key: @@ -177,7 +172,6 @@ assert: that: - 'result.changed' - - '"failed" not in result' - '"key" in result' - '"name" in result.key' - '"fingerprint" in result.key' @@ -200,7 +194,6 @@ assert: that: - 'result.changed' - - '"failed" not in result' - '"key" in result' - 'result.key == None' @@ -220,7 +213,6 @@ - name: assert state=present with key_material assert: that: - - '"failed" not in result' - 'result.changed == True' - '"key" in result' - '"name" in result.key' @@ -246,7 +238,6 @@ assert: that: - 'result.changed' - - '"failed" not in result' - '"key" in result' - 'result.key == None' @@ -324,7 +315,6 @@ assert: that: - 'result.changed' - - '"failed" not in result' - '"key" in result' - 'result.key == None' @@ -345,6 +335,5 @@ assert: that: - 'not result.changed' - - '"failed" not in result' - '"key" in result' - 'result.key == None' diff --git a/test/integration/targets/failed_when/tasks/main.yml b/test/integration/targets/failed_when/tasks/main.yml index a4a73665b4e..3f8ae545f2e 100644 --- a/test/integration/targets/failed_when/tasks/main.yml +++ b/test/integration/targets/failed_when/tasks/main.yml @@ -23,7 +23,7 @@ - assert: that: - - "'failed' not in result" + - "'failed' in result and not result.failed" - name: command rc 0 failed_when_result False shell: exit 0 diff --git a/test/integration/targets/fetch/tasks/main.yml b/test/integration/targets/fetch/tasks/main.yml index 7fb34b385e5..03339dee814 100644 --- a/test/integration/targets/fetch/tasks/main.yml +++ b/test/integration/targets/fetch/tasks/main.yml @@ -95,7 +95,7 @@ - name: check fetch directory result assert: that: - - "failed_fetch_dir['failed']" + - "failed_fetch_dir|failed" - "fetch_dir.msg" - name: create symlink to a file that we can fetch diff --git a/test/integration/targets/get_url/tasks/main.yml b/test/integration/targets/get_url/tasks/main.yml index 5b87dffa13b..f7a9fa505fa 100644 --- a/test/integration/targets/get_url/tasks/main.yml +++ b/test/integration/targets/get_url/tasks/main.yml @@ -111,7 +111,7 @@ - name: Assert that the file was not downloaded assert: that: - - "result.failed == true" + - "result|failed" - "'Failed to validate the SSL certificate' in result.msg" - "stat_result.stat.exists == false" @@ -150,14 +150,13 @@ assert: that: - 'data_result.rc == 0' - - '"failed" not in get_url_result' when: "{{ python_has_ssl_context }}" # If the client doesn't support SNI then get_url should have failed with a certificate mismatch - name: Assert that hostname verification failed because SNI is not supported on this version of python assert: that: - - 'get_url_result["failed"]' + - 'get_url_result|failed' when: "{{ not python_has_ssl_context }}" # These tests are just side effects of how the site is hosted. It's not @@ -178,14 +177,14 @@ assert: that: - 'data_result.rc == 0' - - '"failed" not in get_url_result' + - 'not get_url_result|failed' when: "{{ python_has_ssl_context }}" # If the client doesn't support SNI then get_url should have failed with a certificate mismatch - name: Assert that hostname verification failed because SNI is not supported on this version of python assert: that: - - 'get_url_result["failed"]' + - 'get_url_result|failed' when: "{{ not python_has_ssl_context }}" # End hacky SNI test section diff --git a/test/integration/targets/win_find/tasks/main.yml b/test/integration/targets/win_find/tasks/main.yml index 524cfd890a2..9d85310bc52 100644 --- a/test/integration/targets/win_find/tasks/main.yml +++ b/test/integration/targets/win_find/tasks/main.yml @@ -155,6 +155,7 @@ expected: changed: False examined: 5 + failed: False files: - { isarchive: True, attributes: Archive, @@ -237,6 +238,7 @@ expected: changed: False examined: 11 + failed: False files: - { isarchive: True, attributes: "Hidden, Archive", @@ -278,6 +280,7 @@ expected: changed: False examined: 5 + failed: False files: - { isarchive: True, attributes: Archive, @@ -315,6 +318,7 @@ expected: changed: False examined: 8 + failed: False files: - { isarchive: True, attributes: Archive, @@ -383,6 +387,7 @@ expected: changed: False examined: 10 + failed: False files: - { isarchive: True, attributes: Archive, @@ -465,6 +470,7 @@ expected: changed: False examined: 2 + failed: False files: - { isarchive: False, attributes: Directory, @@ -529,6 +535,7 @@ expected: changed: False examined: 5 + failed: False files: - { isarchive: True, attributes: Archive, @@ -571,6 +578,7 @@ expected: changed: False examined: 5 + failed: False files: - { isarchive: True, attributes: Archive, diff --git a/test/integration/targets/win_reg_stat/tasks/main.yml b/test/integration/targets/win_reg_stat/tasks/main.yml index eb4486011d9..6793e75976f 100644 --- a/test/integration/targets/win_reg_stat/tasks/main.yml +++ b/test/integration/targets/win_reg_stat/tasks/main.yml @@ -52,6 +52,7 @@ expected: changed: false exists: true + failed: false properties: binary: { raw_value: ["0x01", "0x16"], type: 'REG_BINARY', value: [1, 22] } dword: { raw_value: 1, type: 'REG_DWORD', value: 1 } @@ -79,6 +80,7 @@ expected: changed: false exists: true + failed: false properties: none: { raw_value: [], type: 'REG_NONE', value: [] } none1: { raw_value: ["0x00"], type: 'REG_NONE', value: [0] } @@ -101,6 +103,7 @@ expected: changed: false exists: true + failed: false properties: {} sub_keys: [] register: expected @@ -120,6 +123,7 @@ expected: changed: false exists: false + failed: false - name: validate test assert: @@ -137,6 +141,7 @@ expected: changed: false exists: true + failed: false raw_value: 'test' type: 'REG_SZ' value: 'test' @@ -157,6 +162,7 @@ expected: changed: false exists: true + failed: false raw_value: '%windir%\dir' type: 'REG_EXPAND_SZ' value: "{{win_dir_value.stdout_lines[0]}}\\dir" @@ -177,6 +183,7 @@ expected: changed: false exists: true + failed: false raw_value: ['a, b', 'c'] type: 'REG_MULTI_SZ' value: ['a, b', 'c'] @@ -197,6 +204,7 @@ expected: changed: false exists: true + failed: false raw_value: ["0x01", "0x16"] type: 'REG_BINARY' value: [1, 22] @@ -217,6 +225,7 @@ expected: changed: false exists: true + failed: false raw_value: 1 type: 'REG_DWORD' value: 1 @@ -237,6 +246,7 @@ expected: changed: false exists: true + failed: false raw_value: 1 type: 'REG_QWORD' value: 1 @@ -257,6 +267,7 @@ expected: changed: false exists: true + failed: false raw_value: [] type: 'REG_NONE' value: [] @@ -277,6 +288,7 @@ expected: changed: false exists: true + failed: false raw_value: ["0x00"] type: 'REG_NONE' value: [0] @@ -297,6 +309,7 @@ expected: changed: false exists: false + failed: false - name: validate test assert: diff --git a/test/units/module_utils/basic/test_exit_json.py b/test/units/module_utils/basic/test_exit_json.py index 6f52100a869..5e4d626c3ab 100644 --- a/test/units/module_utils/basic/test_exit_json.py +++ b/test/units/module_utils/basic/test_exit_json.py @@ -81,7 +81,7 @@ class TestAnsibleModuleExitJson(unittest.TestCase): else: self.assertEquals(ctx.exception.code, 1) return_val = json.loads(self.fake_stream.getvalue()) - self.assertEquals(return_val, dict(msg="message", failed=True, invocation=empty_invocation)) + self.assertEquals(return_val, dict(msg="message", changed=False, failed=True, invocation=empty_invocation)) def test_exit_json_proper_changed(self): with self.assertRaises(SystemExit) as ctx: @@ -143,7 +143,6 @@ class TestAnsibleModuleExitValuesRemoved(unittest.TestCase): self.maxDiff = None for args, return_val, expected in self.dataset: expected = copy.deepcopy(expected) - del expected['changed'] expected['failed'] = True params = dict(ANSIBLE_MODULE_ARGS=args) params = json.dumps(params)