Repository: ansible/ansible

Issue:
Passing a dict to `with_list` causes RuntimeError

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
 - Bug Report


##### COMPONENT NAME
lib/ansible/plugins/lookup/list.py

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
ansible-playbook 2.5.0 (devel e98e757ec4) last updated 2018/01/29 16:17:08 (GMT -500)
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/admiller/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /home/admiller/src/dev/ansible/lib/ansible
  executable location = /home/admiller/.virtualenvs/ansible/bin/ansible-playbook
  python version = 2.7.14 (default, Jan 17 2018, 14:28:32) [GCC 7.2.1 20170915 (Red Hat 7.2.1-2)]

```

##### OS / ENVIRONMENT
Fedora 27

##### SUMMARY

Currently if you run a playbook `with_list` and pass it a `dict`, this results in a `RuntimeError` as opposed to if you pass a `list` to `with_dict`, it gracefully causes a task failure.

##### 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.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
---
- name: Example of loops with incorrect data type
  hosts: localhost
  gather_facts: false
  vars:
    - a_list: ["this", "is", "a", "list"]
    - a_dict: {"this":"is", "a": "dict"}
  tasks:

    - debug:
        msg: "with_list passed a list: {{item}}"
      with_list: "{{ a_list }}"

    - debug:
        msg: "with_dict passed a dict: {{item}}"
      with_dict: "{{ a_dict }}"

    - debug:
        msg: "with_dict passed a list: {{item}}"
      with_dict: "{{ a_list }}"

    - debug:
        msg: "with_list passed a dict: {{item}}"
      with_list: "{{ a_dict }}"
```

```
ansible-playbook loops-with-wrong-datatype.yml
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
```
fatal: [localhost]: FAILED! => {"failed": true, "msg": "with_list expects a list"}
        to retry, use: --limit @/home/admiller/src/abr/with_list_with_dict/loops-with-wrong-datatype.retry 
```

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: RuntimeError: dictionary changed size during iteration                                                                                                                                           
fatal: [localhost]: FAILED! => {"failed": true, "msg": "Unexpected failure during module execution.", "stdout": ""}                              
        to retry, use: --limit @/home/admiller/src/abr/with_list_with_dict/loops-with-wrong-datatype.retry
```



Discussion:
!component = lib/ansible/executor/task_executor.py

Resolving patch:
diff --git a/lib/ansible/plugins/lookup/list.py b/lib/ansible/plugins/lookup/list.py
--- a/lib/ansible/plugins/lookup/list.py
+++ b/lib/ansible/plugins/lookup/list.py
@@ -3,6 +3,7 @@
 
 # Make coding more python3-ish
 from __future__ import (absolute_import, division, print_function)
+
 __metaclass__ = type
 
 DOCUMENTATION = """
@@ -27,10 +28,15 @@
   _list:
     description: basically the same as you fed in
 """
+import collections
+
 from ansible.plugins.lookup import LookupBase
+from ansible.errors import AnsibleError
 
 
 class LookupModule(LookupBase):
 
     def run(self, terms, **kwargs):
+        if not isinstance(terms, collections.Sequence):
+            raise AnsibleError("with_list expects a list")
         return terms


Test patch:
diff --git a/test/integration/targets/loops/tasks/main.yml b/test/integration/targets/loops/tasks/main.yml
--- a/test/integration/targets/loops/tasks/main.yml
+++ b/test/integration/targets/loops/tasks/main.yml
@@ -202,3 +202,24 @@
     that:
       - "output.results[0]['_ansible_item_label'] == 'looped_var foo_label'"
       - "output.results[1]['_ansible_item_label'] == 'looped_var bar_label'"
+
+# The following test cases are to ensure that we don't have a regression on
+# GitHub Issue https://github.com/ansible/ansible/issues/35481
+#
+# This should execute and not cause a RuntimeError
+- debug:
+    msg: "with_dict passed a list: {{item}}"
+  with_dict: "{{ a_list }}"
+  register: with_dict_passed_a_list
+  ignore_errors: True
+- assert:
+    that:
+      - with_dict_passed_a_list is failed
+- debug:
+    msg: "with_list passed a dict: {{item}}"
+  with_list: "{{ a_dict }}"
+  register: with_list_passed_a_dict
+  ignore_errors: True
+- assert:
+    that:
+      - with_list_passed_a_dict is failed