Repository: ansible/ansible

Issue:
docker_image is unable to pull latest image for tag, if previous exist
##### ISSUE TYPE
- Bug Report
##### COMPONENT NAME
+ docker_image

##### ANSIBLE VERSION
```
ansible 2.3.0
  config file =
  configured module search path = Default w/o overrides
```
##### CONFIGURATION

None

##### OS / ENVIRONMENT

N/A

##### SUMMARY

Pulling a docker image without force=yes always returns `unchanged` even if the image have newest version on DockerHub.
But if I use `force=yes` workaround - it always returns `changed`, even if I already have latest image version.
So, I think it must be another flag, something like `pull=always` or same, to always pull latest image, but stay unchanged, if nothing to do.

##### STEPS TO REPRODUCE

The following task always return changed even when the image is in its latest version.

```yaml
  tasks:
    - name: Download busybox image
      docker_image: name=busybox state=present force=yes
```
But running docker pull from shell you could tell if the image was updated or not

```bash
# docker pull busybox
Using default tag: latest
latest: Pulling from library/busybox

56bec22e3559: Pull complete
Digest: sha256:29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912
Status: Downloaded newer image for busybox:latest

# docker pull busybox
Using default tag: latest
latest: Pulling from library/busybox

Digest: sha256:29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912
Status: Image is up to date for busybox:latest
```
##### EXPECTED RESULTS

It should return unchanged if the image was not updated.

##### ACTUAL RESULTS

```log
TASK [Download Download busybox image] *********************************
Thursday 03 November 2016  11:03:34 -0500 (0:00:00.567)       0:00:00.580 *****
changed: [example.com] => {"actions": ["Pulled image busybox:latest"], "changed":  true, "image": {"Architecture": "amd64", "Author": "", "Comment": "", "Config": {"AttachStderr": false, "AttachStdin": false, "AttachStdout": false, "Cmd": ["sh"], "Domainname": "", "Entrypoint": null, "Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"], "Hostname": "4a74292706a0", "Image": "sha256:1679bae2167496818312013654f5c66a16e185d0a0f6b762b53c8558014457c6", "Labels": {}, "OnBuild": null, "OpenStdin": false, "StdinOnce": false, "Tty": false, "User": "", "Volumes": null, "WorkingDir": ""}, "Container": "8bb318a3b4672c53a1747991c95fff3306eea13ec308740ebe0c81b56ece530f", "ContainerConfig": {"AttachStderr": false, "AttachStdin": false, "AttachStdout": false, "Cmd": ["/bin/sh", "-c", "#(nop) ", "CMD [\"sh\"]"], "Domainname": "", "Entrypoint": null, "Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"], "Hostname": "4a74292706a0", "Image": "sha256:1679bae2167496818312013654f5c66a16e185d0a0f6b762b53c8558014457c6", "Labels": {}, "OnBuild": null, "OpenStdin": false, "StdinOnce": false, "Tty": false, "User": "", "Volumes": null, "WorkingDir": ""}, "Created": "2016-10-07T21:03:58.469866982Z", "DockerVersion": "1.12.1", "GraphDriver": {"Data": null, "Name": "aufs"}, "Id": "sha256:e02e811dd08fd49e7f6032625495118e63f597eb150403d02e3238af1df240ba", "Os": "linux", "Parent": "", "RepoDigests": ["busybox@sha256:29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912"], "RepoTags": ["busybox:latest"], "RootFS": {"Layers": ["sha256:e88b3f82283bc59d5e0df427c824e9f95557e661fcb0ea15fb0fb6f97760f9d9"], "Type": "layers"}, "Size": 1093484, "VirtualSize": 1093484}}
```

### Related issues

It is maybe duplicate of ansible/ansible-modules-core#5489 , but is seems, that repo is locked, so I want to remind about it.


Discussion:
This may not be a bug which is more like by design, because force=yes will always pull the image and you will never know if the local image is the same as the remote one.
+1

This should check for new image using `pull`, e.g. `docker image pull My_organization/image_name:my_tag`
+1
I agree with the statement: 

`must be another flag, something like **pull=always** or same, to always pull latest image, but stay unchanged, if nothing to do.`
This would be especially helpful, as we use Ansible to roll out our Docker deployment runtime configs and would like to be able to automate and test remote image updates.  However, there is currently no obvious way to tell if a docker pull updated the local image with a newer remote one natively via Ansible.  It is certainly possible to do this otherwise by scraping the output from the Docker API call (at least from the old 1.10.x docker-py, for example).
It looks like the information can still be scraped with the new docker-py using the low-level API: http://docker-py.readthedocs.io/en/2.3.0/api.html#docker.api.image.ImageApiMixin.pull
I think this is fixed in 2.4.0.0 by this commit
https://github.com/ansible/ansible/commit/f8e79eb4811fc2f5519fc705524efc156684c553

Except it doesn't. I think this is a bug in ~~`module_utils/docker_common.py`~~
The current image is fetched with
```
image = self.client.find_image(name=self.name, tag=self.tag)
```

Which makes use of inspect_image() from docker-py which returns `image, True`
https://github.com/docker/docker-py/blob/master/docker/api/image.py#L262

After the pull, the contents of the pulled image gets saved with
```
self.results['image'] = self.client.pull_image(self.name, tag=self.tag)
```

Which make use of pull() from docker-py which returns the image
https://github.com/docker/docker-py/blob/master/docker/api/image.py#L398

so although the images are equal, `[image, True]` is not equal to `image`
Nevermind, The actual `True` is coming from
https://github.com/ansible/ansible/blob/devel/lib/ansible/module_utils/docker_common.py#L453

I'll prepare a pull request for this issue

Resolving patch:
diff --git a/lib/ansible/modules/cloud/docker/docker_image.py b/lib/ansible/modules/cloud/docker/docker_image.py
--- a/lib/ansible/modules/cloud/docker/docker_image.py
+++ b/lib/ansible/modules/cloud/docker/docker_image.py
@@ -338,8 +338,8 @@ def present(self):
                 self.results['actions'].append('Pulled image %s:%s' % (self.name, self.tag))
                 self.results['changed'] = True
                 if not self.check_mode:
-                    self.results['image'] = self.client.pull_image(self.name, tag=self.tag)
-                    if image and image == self.results['image']:
+                    self.results['image'], already_latest = self.client.pull_image(self.name, tag=self.tag)
+                    if already_latest:
                         self.results['changed'] = False
 
         if self.archive_path:


Test patch: