Repository: ansible/ansible

Issue:
docker_network Failed but Overlay network created successfully on Docker swarm mode
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
docker_network

pip install git+git://github.com/docker/docker-py.git@2.1.0-release

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible --version
ansible 2.3.0
  config file = /playbook-create-networks/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.12 (default, Nov 19 2016, 06:48:10) [GCC 5.4.0 20160609]
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
N/A

##### SUMMARY
<!--- Explain the problem briefly -->
docker_network, delegated to locahost, connected to remote docker using tcp:// to create an overlay network. The target docker host is a manager in a docker swarm mode cluster.

The network was created successfully, but the command failed.
##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

Run the command below.

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: Create networks
  docker_network:
    name: "mynetwork"
    driver: "overlay"
    docker_host: "tcp://192.168.1.10:7379"
  delegate_to: 127.0.0.1
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
A successful command result so the rest of the tasks can proceed

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_9Ntaqw/ansible_module_docker_network.py", line 392, in <module>
    main()
  File "/tmp/ansible_9Ntaqw/ansible_module_docker_network.py", line 385, in main
    cm = DockerNetworkManager(client)
  File "/tmp/ansible_9Ntaqw/ansible_module_docker_network.py", line 218, in __init__
    self.present()
  File "/tmp/ansible_9Ntaqw/ansible_module_docker_network.py", line 352, in present
    self.disconnect_missing()
  File "/tmp/ansible_9Ntaqw/ansible_module_docker_network.py", line 323, in disconnect_missing
    for c in self.existing_network['Containers'].values():
AttributeError: 'NoneType' object has no attribute 'values'

failed: [192.168.1.120 -> 127.0.0.1] (item={u'driver': u'overlay', u'name': u'networks_default_1'}) => {
    "failed": true,
    "item": {
        "driver": "overlay",
        "name": "networks_default_1"
    },
    "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_9Ntaqw/ansible_module_docker_network.py\", line 392, in <module>\n    main()\n  File \"/tmp/ansible_9Ntaqw/ansible_module_docker_network.py\", line 385, in main\n    cm = DockerNetworkManager(client)\n  File \"/tmp/ansible_9Ntaqw/ansible_module_docker_network.py\", line 218, in __init__\n    self.present()\n  File \"/tmp/ansible_9Ntaqw/ansible_module_docker_network.py\", line 352, in present\n    self.disconnect_missing()\n  File \"/tmp/ansible_9Ntaqw/ansible_module_docker_network.py\", line 323, in disconnect_missing\n    for c in self.existing_network['Containers'].values():\nAttributeError: 'NoneType' object has no attribute 'values'\n",
    "module_stdout": "",
    "msg": "MODULE FAILURE",
    "rc": 0
```



Discussion:
Got problems too:

```
> ansible --version
ansible 2.2.1.0 (detached HEAD 5362910000) last updated 2017/03/15 14:39:38 (GMT +000)
  lib/ansible/modules/core: (detached HEAD 7e8bb413b7) last updated 2017/03/15 14:39:42 (GMT +000)
  lib/ansible/modules/extras: (detached HEAD c501e006ea) last updated 2017/03/15 14:39:45 (GMT +000)
  config file =
  configured module search path = Default w/o overrides
```

Error:
```
fatal: [master_1]: FAILED! => {
    "changed": false,
    "failed": true,
    "invocation": {
        "module_name": "docker_network"
    },
    "module_stderr": "Shared connection to 192.168.0.201 closed.\r\n",
    "module_stdout": "Traceback (most recent call last):\r\n  File \"/tmp/ansible_vEwmhs/ansible_module_docker_network.py\", line 378, in <module>\r\n    main()\r\n  File \"/tmp/ansible_vEwmhs/ansible_module_docker_network.py\", line 371, in main\r\n    cm = DockerNetworkManager(client)\r\n  File \"/tmp/ansible_vEwmhs/ansible_module_docker_network.py\", line 207, in __init__\r\n    self.parameters.connected = container_names_in_network(self.existing_network)\r\n  File \"/tmp/ansible_vEwmhs/ansible_module_docker_network.py\", line 189, in container_names_in_network\r\n    return [c['Name'] for c in network['Containers'].values()]\r\nAttributeError: 'NoneType' object has no attribute 'values'\r\n",
    "msg": "MODULE FAILURE"
}
```
cc @olsaki
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/modules/cloud/docker/docker_network.py b/lib/ansible/modules/cloud/docker/docker_network.py
--- a/lib/ansible/modules/cloud/docker/docker_network.py
+++ b/lib/ansible/modules/cloud/docker/docker_network.py
@@ -194,8 +194,7 @@ def __init__(self, client):
 
 
 def container_names_in_network(network):
-    return [c['Name'] for c in network['Containers'].values()]
-
+    return [c['Name'] for c in network['Containers'].values()] if network['Containers'] else []
 
 class DockerNetworkManager(object):
 
@@ -247,7 +246,7 @@ def has_different_config(self, net):
                 differences.append('driver_options')
             else:
                 for key, value in self.parameters.driver_options.items():
-                    if not net['Options'].get(key) or value != net['Options'][key]:
+                    if not (key in net['Options']) or value != net['Options'][key]:
                         different = True
                         differences.append('driver_options.%s' % key)
         if self.parameters.ipam_driver:
@@ -286,10 +285,10 @@ def create_network(self):
 
             if HAS_DOCKER_PY_2:
                 ipam_config = IPAMConfig(driver=self.parameters.ipam_driver,
-                                                   pool_configs=ipam_pools)
+                                         pool_configs=ipam_pools)
             else:
                 ipam_config = utils.create_ipam_config(driver=self.parameters.ipam_driver,
-                                                   pool_configs=ipam_pools)
+                                                       pool_configs=ipam_pools)
 
             if not self.check_mode:
                 resp = self.client.create_network(self.parameters.network_name,
@@ -321,13 +320,18 @@ def connect_containers(self):
                 self.results['changed'] = True
 
     def disconnect_missing(self):
-        for c in self.existing_network['Containers'].values():
+        containers = self.existing_network['Containers']
+        if not containers:
+            return
+        for c in containers.values():
             name = c['Name']
             if name not in self.parameters.connected:
                 self.disconnect_container(name)
 
     def disconnect_all_containers(self):
         containers = self.client.inspect_network(self.parameters.network_name)['Containers']
+        if not containers:
+            return
         for cont in containers.values():
             self.disconnect_container(cont['Name'])
 


Test patch: