Repository: ansible/ansible-modules-core

Issue:
docker_container - Setting state to stopped does not work
<!--- Verify first that your issue/request is not already reported in GitHub -->
##### ISSUE TYPE

<!--- Pick one below and delete the rest: -->
- Bug Report
##### COMPONENT NAME

docker_container
##### ANSIBLE VERSION

```
ansible 2.1.0.0 (detached HEAD b599477242) last updated 2016/05/31 22:32:16 (GMT +000)
  lib/ansible/modules/core: (detached HEAD 04a871d007) last updated 2016/05/31 22:32:28 (GMT +000)
  lib/ansible/modules/extras: (detached HEAD b5fa2b3416) last updated 2016/05/31 22:32:34 (GMT +000)
  config file = /ansible/ansible.cfg
  configured module search path = ['/opt/ansible/ansible/library']
```
##### CONFIGURATION

None.
##### OS / ENVIRONMENT

N/A
##### SUMMARY

A value of `stopped` for docker_container's `state` does not work. In looking at the source (https://github.com/ansible/ansible-modules-core/blob/e8db7fd8cc96299b6758164c8e1f45ebef04e7fc/cloud/docker/docker_container.py) it's clear that the container is only stopped if the state is `present` (and something has changed) or `absent`.
##### STEPS TO REPRODUCE

<!---
For bugs, show exactly how to reproduce the problem.
For new features, show how the feature would be used.
-->

```
- hosts: THE_HOST
  tasks:
  - name: start container
    docker_container:
      name: "test"
      image: "alpine:3.3"
      state: started
      command: sleep 9999
  - name: stop container
    docker_container:
      name: "test"
      image: "alpine:3.3"
      state: stopped
      command: sleep 9999
```

<!--- You can also paste gist.github.com links for larger files -->
##### EXPECTED RESULTS

docker_container, when provided a value of `stopped` for `state`, should stop the container.
##### ACTUAL RESULTS

The container is not stopped and `ok: [THE_HOST] => {"changed": false}` is returned.

```
TASK [start container] *********************************************************
task path: /ansible/stopbug.yml:3
...
changed: [10.xx.xx.xx] => {"ansible_facts": {"ansible_docker_container": {"AppArmorProfile": "", "Args": ["9999"], "Config": {"AttachStderr": false, "AttachStdin": false, "AttachStdout": false, "Cmd": ["sleep", "9999"], "Domainname": "", "Entrypoint": null, "Env": null, "Hostname": "caacd35c7ade", "Image": "alpine:3.3", "Labels": {}, "OnBuild": null, "OpenStdin": false, "StdinOnce": false, "Tty": false, "User": "", "Volumes": null, "WorkingDir": ""}, "Created": "2016-06-01T18:26:09.55873547Z", "Driver": "aufs", "ExecIDs": null, "GraphDriver": {"Data": null, "Name": "aufs"}, "HostConfig": {"AutoRemove": false, "Binds": [], "BlkioBps": 0, "BlkioDeviceReadBps": null, "BlkioDeviceReadIOps": null, "BlkioDeviceWriteBps": null, "BlkioDeviceWriteIOps": null, "BlkioIOps": 0, "BlkioWeight": 0, "BlkioWeightDevice": null, "CapAdd": null, "CapDrop": null, "Cgroup": "", "CgroupParent": "", "ConsoleSize": [0, 0], "ContainerIDFile": "", "CpuCount": 0, "CpuPercent": 0, "CpuPeriod": 0, "CpuQuota": 0, "CpuShares": 0, "CpusetCpus": "", "CpusetMems": "", "Devices": null, "DiskQuota": 0, "Dns": null, "DnsOptions": null, "DnsSearch": null, "ExtraHosts": null, "GroupAdd": null, "IpcMode": "", "Isolation": "", "KernelMemory": 0, "Links": null, "LogConfig": {"Config": {}, "Type": "json-file"}, "Memory": 0, "MemoryReservation": 0, "MemorySwap": 0, "MemorySwappiness": -1, "NetworkMode": "default", "OomKillDisable": false, "OomScoreAdj": 0, "PidMode": "", "PidsLimit": 0, "PortBindings": null, "Privileged": false, "PublishAllPorts": false, "ReadonlyRootfs": false, "RestartPolicy": {"MaximumRetryCount": 0, "Name": ""}, "SandboxSize": 0, "SecurityOpt": null, "ShmSize": 67108864, "StorageOpt": null, "UTSMode": "", "Ulimits": null, "UsernsMode": "", "VolumeDriver": "", "VolumesFrom": null}, "HostnamePath": "/var/lib/docker/containers/caacd35c7ade5e1d19d6d12f77d95e7b2288db38128fb128247ca24ee67afe11/hostname", "HostsPath": "/var/lib/docker/containers/caacd35c7ade5e1d19d6d12f77d95e7b2288db38128fb128247ca24ee67afe11/hosts", "Id": "caacd35c7ade5e1d19d6d12f77d95e7b2288db38128fb128247ca24ee67afe11", "Image": "sha256:13e1761bf172304ecf9b3fe05a653ceb7540973525e8ef83fb16c650b5410a08", "LogPath": "/var/lib/docker/containers/caacd35c7ade5e1d19d6d12f77d95e7b2288db38128fb128247ca24ee67afe11/caacd35c7ade5e1d19d6d12f77d95e7b2288db38128fb128247ca24ee67afe11-json.log", "MountLabel": "", "Mounts": [], "Name": "/test", "NetworkSettings": {"Bridge": "", "EndpointID": "044bba79ce9f428e0e01d8002efaacf6f6f4b381cbc77b33eca00107766603df", "Gateway": "172.17.0.1", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "HairpinMode": false, "IPAddress": "172.17.0.2", "IPPrefixLen": 16, "IPv6Gateway": "", "LinkLocalIPv6Address": "", "LinkLocalIPv6PrefixLen": 0, "MacAddress": "02:42:ac:11:00:02", "Networks": {"bridge": {"Aliases": null, "EndpointID": "044bba79ce9f428e0e01d8002efaacf6f6f4b381cbc77b33eca00107766603df", "Gateway": "172.17.0.1", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "IPAMConfig": null, "IPAddress": "172.17.0.2", "IPPrefixLen": 16, "IPv6Gateway": "", "Links": null, "MacAddress": "02:42:ac:11:00:02", "NetworkID": "870e3c923e2336c7ebec7a208cb12efec89a20e5681642a4654ca3bcf0360ca1"}}, "Ports": {}, "SandboxID": "736c6eafdb3c4a580491292660ef69cbeda88b4d02a6782fb830e5b9fa3d81d1", "SandboxKey": "/var/run/docker/netns/736c6eafdb3c", "SecondaryIPAddresses": null, "SecondaryIPv6Addresses": null}, "Path": "sleep", "ProcessLabel": "", "ResolvConfPath": "/var/lib/docker/containers/caacd35c7ade5e1d19d6d12f77d95e7b2288db38128fb128247ca24ee67afe11/resolv.conf", "RestartCount": 0, "State": {"Dead": false, "Error": "", "ExitCode": 0, "FinishedAt": "0001-01-01T00:00:00Z", "OOMKilled": false, "Paused": false, "Pid": 1111, "Restarting": false, "Running": true, "StartedAt": "2016-06-01T18:26:09.859578894Z", "Status": "running"}}}, "changed": true, "invocation": {"module_args": {"api_version": null, "blkio_weight": null, "cacert_path": null, "capabilities": null, "cert_path": null, "command": "sleep 9999", "cpu_period": null, "cpu_quota": null, "cpu_shares": null, "cpuset_cpus": null, "cpuset_mems": null, "debug": false, "detach": true, "devices": null, "dns_opts": null, "dns_search_domains": null, "dns_servers": null, "docker_host": null, "entrypoint": null, "env": null, "env_file": null, "etc_hosts": null, "exposed_ports": null, "filter_logger": false, "force_kill": false, "groups": null, "hostname": null, "image": "alpine:3.3", "interactive": false, "ipc_mode": null, "keep_volumes": true, "kernel_memory": null, "key_path": null, "kill_signal": null, "labels": null, "links": null, "log_driver": "json-file", "log_options": null, "mac_address": null, "memory": "0", "memory_reservation": null, "memory_swap": null, "memory_swappiness": null, "name": "test", "network_mode": null, "networks": null, "oom_killer": null, "paused": false, "pid_mode": null, "privileged": false, "published_ports": null, "pull": false, "purge_networks": null, "read_only": false, "recreate": false, "restart": false, "restart_policy": null, "restart_retries": 0, "security_opts": null, "shm_size": null, "ssl_version": null, "state": "started", "stop_signal": null, "stop_timeout": null, "timeout": null, "tls": null, "tls_hostname": null, "tls_verify": null, "trust_image_content": false, "tty": false, "ulimits": null, "user": null, "uts": null, "volume_driver": null, "volumes": null, "volumes_from": null}, "module_name": "docker_container"}}

TASK [stop container] **********************************************************
task path: /ansible/stopbug.yml:9
...
ok: [10.xx.xx.xx] => {"changed": false, "invocation": {"module_args": {"api_version": null, "blkio_weight": null, "cacert_path": null, "capabilities": null, "cert_path": null, "command": "sleep 9999", "cpu_period": null, "cpu_quota": null, "cpu_shares": null, "cpuset_cpus": null, "cpuset_mems": null, "debug": false, "detach": true, "devices": null, "dns_opts": null, "dns_search_domains": null, "dns_servers": null, "docker_host": null, "entrypoint": null, "env": null, "env_file": null, "etc_hosts": null, "exposed_ports": null, "filter_logger": false, "force_kill": false, "groups": null, "hostname": null, "image": "alpine:3.3", "interactive": false, "ipc_mode": null, "keep_volumes": true, "kernel_memory": null, "key_path": null, "kill_signal": null, "labels": null, "links": null, "log_driver": "json-file", "log_options": null, "mac_address": null, "memory": "0", "memory_reservation": null, "memory_swap": null, "memory_swappiness": null, "name": "test", "network_mode": null, "networks": null, "oom_killer": null, "paused": false, "pid_mode": null, "privileged": false, "published_ports": null, "pull": false, "purge_networks": null, "read_only": false, "recreate": false, "restart": false, "restart_policy": null, "restart_retries": 0, "security_opts": null, "shm_size": null, "ssl_version": null, "state": "stopped", "stop_signal": null, "stop_timeout": null, "timeout": null, "tls": null, "tls_hostname": null, "tls_verify": null, "trust_image_content": false, "tty": false, "ulimits": null, "user": null, "uts": null, "volume_driver": null, "volumes": null, "volumes_from": null}, "module_name": "docker_container"}}

PLAY RECAP *********************************************************************
10.xx.xx.xx              : ok=3    changed=1    unreachable=0    failed=0  
```



Discussion:

Resolving patch:
diff --git a/cloud/docker/docker_container.py b/cloud/docker/docker_container.py
--- a/cloud/docker/docker_container.py
+++ b/cloud/docker/docker_container.py
@@ -369,8 +369,8 @@
         re-create a matching container, even if it is running. Use restart to force a matching container to be stopped and
         restarted. Use force_kill to kill a container rather than stopping it. Use keep_volumes to retain volumes associated
         with a removed container.'
-      - 'I(stopped) - a container matching the specified name will be stopped. Use force_kill to kill a container rather than
-        stopping it.'
+      - 'I(stopped) - Asserts that the container is first I(present), and then if the container is running moves it to a stopped 
+        state. Use force_kill to kill a container rather than stopping it.'
     required: false
     default: started
     choices:
@@ -492,10 +492,13 @@
   docker_container:
     name: mycontainer
     state: present
-    recreate: yes
-    force_kill: yes
-    image: someplace/image
-    command: echo "I'm here!"
+    image: ubuntu:14.04
+    command: sleep infinity 
+
+- name: Stop a contianer
+  docker_container:
+    name: mycontainer
+    state: stopped
 
 - name: Start 4 load-balanced containers
   docker_container:
@@ -1089,7 +1092,7 @@ def fail(self, msg):
         self.parameters.client.module.fail_json(msg=msg)
 
     @property
-    def found(self):
+    def exists(self):
         return True if self.container else False
 
     @property
@@ -1557,7 +1560,7 @@ def __init__(self, client):
         self.facts = {}
 
         state = self.parameters.state
-        if state in ('started', 'present'):
+        if state in ('stopped', 'started', 'present'):
             self.present(state)
         elif state == 'absent':
             self.absent()
@@ -1578,51 +1581,46 @@ def present(self, state):
         container = self._get_container(self.parameters.name)
         image = self._get_image()
 
-        if not container.found:
-            self.log('No container found')
+        if not container.exists:
             # New container
+            self.log('No container found')
             new_container = self.container_create(self.parameters.image, self.parameters.create_parameters)
             if new_container:
                 container = new_container
-                container = self.update_limits(container)
-                container = self.update_networks(container)
-            if state == 'started':
-                container = self.container_start(container.Id)
-            self.facts = container.raw
-            return
-
-        # Existing container
-        different, differences = container.has_different_configuration(image)
-        image_different = self._image_is_different(image, container)
-        if image_different or different or self.parameters.recreate:
-            self.diff['differences'] = differences
-            self.log("differences")
-            self.log(differences, pretty_print=True)
-            self.container_stop(container.Id)
-            self.container_remove(container.Id)
-            new_container = self.container_create(self.parameters.image, self.parameters.create_parameters)
-            if new_container:
-                container = new_container
-        if image_different:
-            self.diff['image_different'] = True
+        else:
+            # Existing container
+            different, differences = container.has_different_configuration(image)
+            image_different = self._image_is_different(image, container)
+            if image_different or different or self.parameters.recreate:
+                self.diff['differences'] = differences
+                if image_different:
+                    self.diff['image_different'] = True
+                self.log("differences")
+                self.log(differences, pretty_print=True)
+                self.container_stop(container.Id)
+                self.container_remove(container.Id)
+                new_container = self.container_create(self.parameters.image, self.parameters.create_parameters)
+                if new_container:
+                    container = new_container
 
-        container = self.update_limits(container)
-        container = self.update_networks(container)
+        if container and container.exists:
+            container = self.update_limits(container)
+            container = self.update_networks(container)
 
-        if state == 'started' and not container.running:
-            container = self.container_start(container.Id)
-        elif state == 'started' and self.parameters.restart:
-            self.container_stop(container.Id)
-            container = self.container_start(container.Id)
-        elif state == 'present' and container.running:
-            self.container_stop(container.Id)
-            container = self._get_container(container.Id)
+            if state == 'started' and not container.running:
+                container = self.container_start(container.Id)
+            elif state == 'started' and self.parameters.restart:
+                self.container_stop(container.Id)
+                container = self.container_start(container.Id)
+            elif state == 'stopped' and container.running:
+                self.container_stop(container.Id)
+                container = self._get_container(container.Id)
 
         self.facts = container.raw
 
     def absent(self):
-        container = Container(self.client.get_container(self.parameters.name), self.parameters)
-        if container.found:
+        container = self._get_container(self.parameters.name)
+        if container.exists:
             if container.running:
                 self.container_stop(container.Id)
             self.container_remove(container.Id)


Test patch: