Repository: ansible/ansible

Issue:
Enhance exception on yaml inventory typo
##### ISSUE TYPE
 - Enhancement

##### COMPONENT NAME
plugins/inventory/yaml

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
ansible 2.5.0 (devel b444332412) last updated 2017/09/27 17:41:21 (GMT +200)
  config file = None
  configured module search path = ['/home/julien/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/julien/ansible/lib/ansible
  executable location = /home/julien/.venvs/my-ansible/bin/ansible
  python version = 3.5.3 (default, Jan 19 2017, 14:11:04) [GCC 6.3.0 20170118]

```

##### SUMMARY

With a yaml inventory, on host variables, doing the "typo" of using an equal sign instead of a column (copying from ini format, typically), I mean:

```
ansible_host=127.0.0.1
```

Instead of

```
ansible_host: 127.0.0.1
```


##### 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.
-->

Example inventory:

```yaml
---

all:
  children:
    webs:
      hosts:
        web1:
          ansible_host=1.2.3.4

```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
Some readable error.

##### ACTUAL RESULTS
Got:
```
TypeError: string indices must be integers
```
<!--- Paste verbatim command output between quotes below -->
```
ansible-playbook -vvvv playbook.yml
ansible-playbook 2.5.0 (devel b444332412) last updated 2017/09/27 17:41:21 (GMT +200)
  config file = /home/julien/my-ansible/ansible.cfg
  configured module search path = ['/home/julien/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/julien/ansible/lib/ansible
  executable location = /home/julien/ansible/bin/ansible-playbook
  python version = 3.5.3 (default, Jan 19 2017, 14:11:04) [GCC 6.3.0 20170118]
Using /home/julien/my-ansible/ansible.cfg as config file
setting up inventory plugins
ERROR! Unexpected Exception, this is probably a bug: string indices must be integers
the full traceback was:

Traceback (most recent call last):
  File "/home/julien/ansible/bin/ansible-playbook", line 109, in <module>
    exit_code = cli.run()
  File "/home/julien/ansible/lib/ansible/cli/playbook.py", line 104, in run
    loader, inventory, variable_manager = self._play_prereqs(self.options)
  File "/home/julien/ansible/lib/ansible/cli/__init__.py", line 788, in _play_prereqs
    inventory = InventoryManager(loader=loader, sources=options.inventory)
  File "/home/julien/ansible/lib/ansible/inventory/manager.py", line 144, in __init__
    self.parse_sources()
  File "/home/julien/ansible/lib/ansible/inventory/manager.py", line 205, in parse_sources
    parse = self.parse_source(source, cache=cache)
  File "/home/julien/ansible/lib/ansible/inventory/manager.py", line 259, in parse_source
    plugin.parse(self._inventory, self._loader, source, cache=cache)
  File "/home/julien/ansible/lib/ansible/plugins/inventory/yaml.py", line 98, in parse
    self._parse_group(group_name, data[group_name])
  File "/home/julien/ansible/lib/ansible/plugins/inventory/yaml.py", line 118, in _parse_group
    self._parse_group(subgroup, group_data['children'][subgroup])
  File "/home/julien/ansible/lib/ansible/plugins/inventory/yaml.py", line 124, in _parse_group
    self.populate_host_vars(hosts, group_data['hosts'][host_pattern] or {}, group, port)
  File "/home/julien/ansible/lib/ansible/plugins/inventory/__init__.py", line 86, in populate_host_vars
    self.inventory.set_variable(host, k, variables[k])
TypeError: string indices must be integers
```



Discussion:

Resolving patch:
diff --git a/lib/ansible/plugins/inventory/__init__.py b/lib/ansible/plugins/inventory/__init__.py
--- a/lib/ansible/plugins/inventory/__init__.py
+++ b/lib/ansible/plugins/inventory/__init__.py
@@ -19,12 +19,13 @@
 from __future__ import (absolute_import, division, print_function)
 __metaclass__ = type
 
+from collections import MutableMapping
 import hashlib
 import os
 import re
 import string
 
-from ansible.errors import AnsibleError, AnsibleOptionsError
+from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
 from ansible.module_utils._text import to_bytes, to_native
 from ansible.module_utils.parsing.convert_bool import boolean
 from ansible.module_utils.six import string_types
@@ -80,6 +81,9 @@ def clear_cache(self):
         pass
 
     def populate_host_vars(self, hosts, variables, group=None, port=None):
+        if not isinstance(variables, MutableMapping):
+            raise AnsibleParserError("Invalid data from file, expected dictionary and got:\n\n%s" % to_native(variables))
+
         for host in hosts:
             self.inventory.add_host(host, group=group, port=port)
             for k in variables:


Test patch: