Repository: ansible/ansible

Issue:
yaml inventory plugin fails with a non-helpful error massage
##### ISSUE TYPE
Bug Report

##### COMPONENT NAME
Ansible Inventory Manager (`manager.py`)

##### ANSIBLE VERSION
![image](https://user-images.githubusercontent.com/17345126/31168139-082002d2-a8fd-11e7-9bb0-1b90ba173f98.png)

##### CONFIGURATION
<!---
If using Ansible 2.4 or above, paste the results of "ansible-config dump --only-changed"
-->
Empty output of the command `ansible-config dump --only-changed`.

##### OS / ENVIRONMENT
Ubuntu 16.04 LTS

##### SUMMARY
Running `ansible-playbook -i dev-inventory.yml example-playbook.yml` fails with the following:

```
ansible-playbook 2.4.0.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/edvardelio/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/dist-packages/ansible
  executable location = /usr/bin/ansible-playbook
  python version = 2.7.12 (default, Nov 19 2016, 06:48:10) [GCC 5.4.0 20160609]
Using /etc/ansible/ansible.cfg as config file

ERROR! Unexpected Exception, this is probably a bug: list indices must be integers, not AnsibleUnicode
the full traceback was:

Traceback (most recent call last):
  File "/usr/bin/ansible-playbook", line 106, in <module>
    exit_code = cli.run()
  File "/usr/lib/python2.7/dist-packages/ansible/cli/playbook.py", line 104, in run
    loader, inventory, variable_manager = self._play_prereqs(self.options)
  File "/usr/lib/python2.7/dist-packages/ansible/cli/__init__.py", line 784, in _play_prereqs
    inventory = InventoryManager(loader=loader, sources=options.inventory)
  File "/usr/lib/python2.7/dist-packages/ansible/inventory/manager.py", line 144, in __init__
    self.parse_sources()
  File "/usr/lib/python2.7/dist-packages/ansible/inventory/manager.py", line 205, in parse_sources
    parse = self.parse_source(source, cache=cache)
  File "/usr/lib/python2.7/dist-packages/ansible/inventory/manager.py", line 259, in parse_source
    plugin.parse(self._inventory, self._loader, source, cache=cache)
  File "/usr/lib/python2.7/dist-packages/ansible/plugins/inventory/yaml.py", line 101, in parse
    self._parse_group(group_name, data[group_name])
  File "/usr/lib/python2.7/dist-packages/ansible/plugins/inventory/yaml.py", line 123, in _parse_group
    self._parse_group(subgroup, group_data['children'][subgroup])
  File "/usr/lib/python2.7/dist-packages/ansible/plugins/inventory/yaml.py", line 129, in _parse_group
    self.populate_host_vars(hosts, group_data['hosts'][host_pattern] or {}, group, port)
TypeError: list indices must be integers, not AnsibleUnicode
```

##### STEPS TO REPRODUCE
`dev-inentory.yml` file contents:

```yaml
---

all:
  children:
    kafkaNodes:
      hosts:
        - "kafka_01.com"
        - "kafka_02.com"
    zookeeperNodes:
      hosts:
        zoo_01:
          ansible_host: xxx.xxx.xx.xxx
          ansible_port: 2181
        zoo_02:
```

##### EXPECTED RESULTS
For the command to run or give some reasonable error message of what is wrong.



Discussion:
OK, so the problem is with the part:
```
      hosts:
        - "kafka_01.com"
        - "kafka_02.com"
```
And it shouldn't be a list here but instead two keys with no values. Could it be possible to fix the error message to indicate that the problem is with the format of the inventory file?

Resolving patch:
diff --git a/lib/ansible/plugins/inventory/yaml.py b/lib/ansible/plugins/inventory/yaml.py
--- a/lib/ansible/plugins/inventory/yaml.py
+++ b/lib/ansible/plugins/inventory/yaml.py
@@ -49,7 +49,6 @@
                 last_var: MYVALUE
 '''
 
-import re
 import os
 from collections import MutableMapping
 
@@ -106,8 +105,14 @@ def _parse_group(self, group, group_data):
         if isinstance(group_data, MutableMapping):
             # make sure they are dicts
             for section in ['vars', 'children', 'hosts']:
-                if section in group_data and isinstance(group_data[section], string_types):
-                    group_data[section] = {group_data[section]: None}
+                if section in group_data:
+                    # convert strings to dicts as these are allowed
+                    if isinstance(group_data[section], string_types):
+                        group_data[section] = {group_data[section]: None}
+
+                    if not isinstance(group_data[section], MutableMapping):
+                        raise AnsibleParserError('Invalid %s entry for %s group, requires a dictionary, found %s instead.' %
+                                                 (section, group, type(group_data[section])))
 
             if group_data.get('vars', False):
                 for var in group_data['vars']:


Test patch: