Repository: ansible/ansible

Issue:
docker dynamic inventory script ignores DOCKER_CONFIG_FILE environment variable
<!---
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 dynamic inventory
##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.4.0 (devel 097173c6f5) last updated 2017/03/29 12:35:28 (GMT -400)
  config file = /Users/milo/code/ansible-data/ansible.cfg
  configured module search path = [u'/Users/milo/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  python version = 2.7.13 (default, Dec 17 2016, 23:03:43) [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)]
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->
`ANSIBLE_CONFIG` points to mostly default config file, which only changes the inventory path (`inventory`) and roles path (`roles_path`) options.

Related to the `contrib/inventory/docker.py` dynamic inventory script in question for this issue, I have set the `DOCKER_CONFIG_FILE` environment variable to the absolute path of a custom docker config file which sets multiple docker hosts for use in building the dynamic inventory.
##### 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 -->
The documentation for the `docker.py` dynamic inventory script in `contrib/inventory` indicates that a docker configuration file can be specified by providing the `--config-file` cli argument or by setting the `DOCKER_CONFIG_FILE` environment variable. However, the environment variable is never checked, regardless of whether the `--config-file` option is given or not, or if the default/fallback `docker.yml` file exists or not.
##### 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.
-->
* Copy the `docker.yml` example config file from `contrib/inventory/` to any directory (e.g. `/some/path/docker.yml`), and uncomment lines 64-74, e.g: 
    ```yaml
    defaults:
      host: unix:///var/run/docker.sock
      private_ssh_port: 22
      default_ip: 127.0.0.1

  hosts:
    - host: tcp://10.45.5.16:4243
      private_ssh_port: 2022
      default_ip: 172.16.3.45
    - host: tcp://localhost:4243
      private_ssh_port: 2029
    ```
* set the `DOCKER_CONFIG_FILE` environment variable to the path set above (`/some/path/docker.yml`)
* call the docker.py inventory script from the command line with `<ansible_root_dir>/contrib/inventory/docker.py --debug` and note that the debug output under `defaults:`, `hosts:`, and `auth params:` do not reflect the values set in the config file.

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
```yaml
defaults:

{
  "default_ip": "127.0.0.1", 
  "host": "unix:///var/run/docker.sock", 
  "private_ssh_port": 22
}
hosts: 

[
  {
    "api_version": "1.24", 
    "cacert_path": null, 
    "cert_path": null, 
    "default_ip": "172.16.3.45", 
    "default_ssh_port": 2022, 
    "docker_host": "tcp://10.45.5.16:4243", 
    "key_path": null, 
    "ssl_version": null, 
    "timeout": 60, 
    "tls": false, 
    "tls_hostname": "localhost", 
    "tls_verify": false
  }, 
  {
    "api_version": "1.24", 
    "cacert_path": null, 
    "cert_path": null, 
    "default_ip": "127.0.0.1", 
    "default_ssh_port": 2029, 
    "docker_host": "tcp://localhost:4243", 
    "key_path": null, 
    "ssl_version": null, 
    "timeout": 60, 
    "tls": false, 
    "tls_hostname": "localhost", 
    "tls_verify": false
  }
]
auth params:

  tls: False

  cacert_path: None

  timeout: 60

  default_ip: 172.16.3.45

  ssl_version: None

  tls_hostname: localhost

  docker_host: tcp://10.45.5.16:4243

  tls_verify: False

  key_path: None

  default_ssh_port: 2022

  cert_path: None

  api_version: 1.24
```
##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
```yaml
defaults:

{}
hosts: 

[
  {
    "api_version": "1.24", 
    "cacert_path": null, 
    "cert_path": null, 
    "default_ip": "127.0.0.1", 
    "default_ssh_port": "22", 
    "docker_host": "unix://var/run/docker.sock", 
    "key_path": null, 
    "ssl_version": null, 
    "timeout": 60, 
    "tls": false, 
    "tls_hostname": "localhost", 
    "tls_verify": false
  }
]
auth params:

  tls: False

  cacert_path: None

  timeout: 60

  default_ip: 127.0.0.1

  ssl_version: None

  tls_hostname: localhost

  docker_host: unix://var/run/docker.sock

  tls_verify: False

  key_path: None

  default_ssh_port: 22

  cert_path: None

  api_version: 1.24
```


docker dynamic inventory script ignores DOCKER_CONFIG_FILE environment variable
<!---
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 dynamic inventory
##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.4.0 (devel 097173c6f5) last updated 2017/03/29 12:35:28 (GMT -400)
  config file = /Users/milo/code/ansible-data/ansible.cfg
  configured module search path = [u'/Users/milo/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  python version = 2.7.13 (default, Dec 17 2016, 23:03:43) [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)]
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->
`ANSIBLE_CONFIG` points to mostly default config file, which only changes the inventory path (`inventory`) and roles path (`roles_path`) options.

Related to the `contrib/inventory/docker.py` dynamic inventory script in question for this issue, I have set the `DOCKER_CONFIG_FILE` environment variable to the absolute path of a custom docker config file which sets multiple docker hosts for use in building the dynamic inventory.
##### 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 -->
The documentation for the `docker.py` dynamic inventory script in `contrib/inventory` indicates that a docker configuration file can be specified by providing the `--config-file` cli argument or by setting the `DOCKER_CONFIG_FILE` environment variable. However, the environment variable is never checked, regardless of whether the `--config-file` option is given or not, or if the default/fallback `docker.yml` file exists or not.
##### 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.
-->
* Copy the `docker.yml` example config file from `contrib/inventory/` to any directory (e.g. `/some/path/docker.yml`), and uncomment lines 64-74, e.g: 
    ```yaml
    defaults:
      host: unix:///var/run/docker.sock
      private_ssh_port: 22
      default_ip: 127.0.0.1

  hosts:
    - host: tcp://10.45.5.16:4243
      private_ssh_port: 2022
      default_ip: 172.16.3.45
    - host: tcp://localhost:4243
      private_ssh_port: 2029
    ```
* set the `DOCKER_CONFIG_FILE` environment variable to the path set above (`/some/path/docker.yml`)
* call the docker.py inventory script from the command line with `<ansible_root_dir>/contrib/inventory/docker.py --debug` and note that the debug output under `defaults:`, `hosts:`, and `auth params:` do not reflect the values set in the config file.

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
```yaml
defaults:

{
  "default_ip": "127.0.0.1", 
  "host": "unix:///var/run/docker.sock", 
  "private_ssh_port": 22
}
hosts: 

[
  {
    "api_version": "1.24", 
    "cacert_path": null, 
    "cert_path": null, 
    "default_ip": "172.16.3.45", 
    "default_ssh_port": 2022, 
    "docker_host": "tcp://10.45.5.16:4243", 
    "key_path": null, 
    "ssl_version": null, 
    "timeout": 60, 
    "tls": false, 
    "tls_hostname": "localhost", 
    "tls_verify": false
  }, 
  {
    "api_version": "1.24", 
    "cacert_path": null, 
    "cert_path": null, 
    "default_ip": "127.0.0.1", 
    "default_ssh_port": 2029, 
    "docker_host": "tcp://localhost:4243", 
    "key_path": null, 
    "ssl_version": null, 
    "timeout": 60, 
    "tls": false, 
    "tls_hostname": "localhost", 
    "tls_verify": false
  }
]
auth params:

  tls: False

  cacert_path: None

  timeout: 60

  default_ip: 172.16.3.45

  ssl_version: None

  tls_hostname: localhost

  docker_host: tcp://10.45.5.16:4243

  tls_verify: False

  key_path: None

  default_ssh_port: 2022

  cert_path: None

  api_version: 1.24
```
##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
```yaml
defaults:

{}
hosts: 

[
  {
    "api_version": "1.24", 
    "cacert_path": null, 
    "cert_path": null, 
    "default_ip": "127.0.0.1", 
    "default_ssh_port": "22", 
    "docker_host": "unix://var/run/docker.sock", 
    "key_path": null, 
    "ssl_version": null, 
    "timeout": 60, 
    "tls": false, 
    "tls_hostname": "localhost", 
    "tls_verify": false
  }
]
auth params:

  tls: False

  cacert_path: None

  timeout: 60

  default_ip: 127.0.0.1

  ssl_version: None

  tls_hostname: localhost

  docker_host: unix://var/run/docker.sock

  tls_verify: False

  key_path: None

  default_ssh_port: 22

  cert_path: None

  api_version: 1.24
```




Discussion:

Resolving patch:
diff --git a/contrib/inventory/docker.py b/contrib/inventory/docker.py
--- a/contrib/inventory/docker.py
+++ b/contrib/inventory/docker.py
@@ -391,6 +391,7 @@
         class Client:
             pass
 
+DEFAULT_DOCKER_CONFIG_FILE = os.path.splitext(os.path.basename(__file__))[0] + '.yml'
 DEFAULT_DOCKER_HOST = 'unix://var/run/docker.sock'
 DEFAULT_TLS = False
 DEFAULT_TLS_VERIFY = False
@@ -781,29 +782,29 @@ def get_hosts(self, config):
 
     def _parse_config_file(self):
         config = dict()
-        config_path = None
+        config_file = DEFAULT_DOCKER_CONFIG_FILE
 
         if self._args.config_file:
-            config_path = self._args.config_file
+            config_file = self._args.config_file
         elif self._env_args.config_file:
-            config_path = self._env_args.config_file
+            config_file = self._env_args.config_file
 
-        if config_path:
-            try:
-                config_file = os.path.abspath(config_path)
-                # default config path is docker.yml in same directory as this script
-                # old behaviour is docker.yml in current directory. Handle both.
-                if not os.path.exists(config_file):
-                    config_file = os.path.abspath(os.path.basename(config_path))
-            except:
-                config_file = None
-
-            if config_file and os.path.exists(config_file):
-                with open(config_file) as f:
-                    try:
-                        config = yaml.safe_load(f.read())
-                    except Exception as exc:
-                        self.fail("Error: parsing %s - %s" % (config_path, str(exc)))
+        config_file = os.path.abspath(config_file)
+
+        if os.path.isfile(config_file):
+            with open(config_file) as f:
+                try:
+                    config = yaml.safe_load(f.read())
+                except Exception as exc:
+                    self.fail("Error: parsing %s - %s" % (config_file, str(exc)))
+        else:
+            msg = "Error: config file given by {} does not exist - " + config_file
+            if self._args.config_file:
+                self.fail(msg.format('command line argument'))
+            elif self._env_args.config_file:
+                self.fail(msg.format(DOCKER_ENV_ARGS.get('config_file')))
+            else:
+                self.log(msg.format('DEFAULT_DOCKER_CONFIG_FILE'))
         return config
 
     def log(self, msg, pretty_print=False):
@@ -828,9 +829,6 @@ def _parse_env_args(self):
     def _parse_cli_args(self):
         # Parse command line arguments
 
-        basename = os.path.splitext(os.path.basename(__file__))[0]
-        default_config = os.path.join(os.path.dirname(__file__), basename + '.yml')
-
         parser = argparse.ArgumentParser(
             description='Return Ansible inventory for one or more Docker hosts.')
         parser.add_argument('--list', action='store_true', default=True,
@@ -841,8 +839,8 @@ def _parse_cli_args(self):
                             help='Only get information for a specific container.')
         parser.add_argument('--pretty', action='store_true', default=False,
                             help='Pretty print JSON output(default: False)')
-        parser.add_argument('--config-file', action='store', default=default_config,
-                            help="Name of the config file to use. Default is %s" % (default_config))
+        parser.add_argument('--config-file', action='store', default=None,
+                            help="Name of the config file to use. Default is %s" % (DEFAULT_DOCKER_CONFIG_FILE))
         parser.add_argument('--docker-host', action='store', default=None,
                             help="The base url or Unix sock path to connect to the docker daemon. Defaults to %s"
                                  % (DEFAULT_DOCKER_HOST))


Test patch: