Repository: ansible/ansible

Issue:
get_url: "failed" and "status_code" are missing on success
##### ISSUE TYPE

 - Feature Request

##### COMPONENT NAME

get_url (maybe others?)

##### ANSIBLE VERSION

```
ansible 2.2.0.0
  config file =
  configured module search path = Default w/o overrides
```

##### CONFIGURATION

N/A

##### OS / ENVIRONMENT

Ubuntu 16.04

##### SUMMARY

Result variables are not consistently present to make it easy to determine success or failure.

I would like to write retry loops like these:

```
(a)
    register: result
    until: not result.failed

(b)
    register: result
    until: result.status_code // 100 == 4 or result.status_code // 100 == 2
```

But whilst result.failed and result.status_code are present when there is an error, they are not present on success. So I have to write:

```
    register: result
    until: result.failed is not defined

    register: result
    until: result.status_code is not defined or result.status_code // 100 == 4
```

which are less clear.

##### STEPS TO REPRODUCE

```yaml
- hosts: localhost
  tasks:
  - file: path=/tmp/get_url_module.html state=absent
  - file: path=/tmp/zzz state=absent

  - name: success
    get_url:
      url: http://docs.ansible.com/ansible/get_url_module.html
      dest: /tmp/get_url_module.html
    register: result

  - name: fail 404
    get_url:
      url: http://docs.ansible.com/ansible/zzz
      dest: /tmp/zzz
    register: result
    ignore_errors: true

  - name: fail timeout
    get_url:
      url: http://1.2.3.4/zzz
      dest: /tmp/zzz
    register: result
    ignore_errors: true
```

Run with -vvvv to see the module results

##### EXPECTED RESULTS

"failed" is true on error => "failed" is false on success

"status_code" is 4xx on error => "status_code" is 2xx on success

##### ACTUAL RESULTS

On success:

```
    "changed": true,
    "checksum_dest": null,
    "checksum_src": "9faa262e331dc2df871213e8072c01e17b1f1226",
    "dest": "/tmp/get_url_module.html",
    "gid": 1000,
    "group": "nsrc",
    "invocation": { ... }
    "md5sum": "5b51f9b26e8faab5332f67c1ac7c7e8d",
    "mode": "0664",
    "msg": "OK (unknown bytes)",
    "owner": "nsrc",
    "size": 43293,
    "src": "/tmp/tmpOPeQLD",
    "state": "file",
    "uid": 1000,
    "url": "http://docs.ansible.com/ansible/get_url_module.html"
```

(note: no `"failed": false` or `"status_code": 200`)

On error 404:

```
    "changed": false,
    "dest": "/tmp/zzz",
    "failed": true,
    "invocation": { ... }
    "msg": "Request failed",
    "response": "HTTP Error 404: Not Found",
    "state": "absent",
    "status_code": 404,
    "url": "http://docs.ansible.com/ansible/zzz"
```

On timeout error:

```
    "changed": false,
    "dest": "/tmp/zzz",
    "failed": true,
    "invocation": { ... }
    "msg": "Request failed",
    "response": "Request failed: <urlopen error timed out>",
    "state": "absent",
    "status_code": -1,
    "url": "http://1.2.3.4/zzz"
```



Discussion:
I am aware this would be a behaviour change. For safety I am now writing:

~~~
    until: result.failed is not defined or not result.failed
~~~

but this could break people who are just checking for result.failed being defined meaning failed.
I found a filter which can simplify this:

```
    until: result|succeeded
```

I still think that get_url should include status_code even for successful downloads though.

Resolving patch:
diff --git a/lib/ansible/modules/network/basics/get_url.py b/lib/ansible/modules/network/basics/get_url.py
--- a/lib/ansible/modules/network/basics/get_url.py
+++ b/lib/ansible/modules/network/basics/get_url.py
@@ -457,7 +457,7 @@ def main():
 
     res_args = dict(
         url = url, dest = dest, src = tmpsrc, md5sum = md5sum, checksum_src = checksum_src,
-        checksum_dest = checksum_dest, changed = changed, msg = info.get('msg', '')
+        checksum_dest = checksum_dest, changed = changed, msg = info.get('msg', ''), status_code=info.get('status','')
     )
     if backup_file:
         res_args['backup_file'] = backup_file


Test patch: