Repository: ansible/ansible

Issue:
Bug when combining vars_files from main playbook and included playbooks
##### Issue Type:

Bug Report
##### Ansible Version:

ansible 1.7 (devel 188ab7b952) last updated 2014/05/30 06:53:46 (GMT +200)
##### Environment:

OS X Mavericks host - Debian wheezy guests (but this should be irrelevant)
##### Summary:

I have a playbook which uses vars_files to load some variables. Then it includes another playbook which uses "conditional imports" (http://docs.ansible.com/playbooks_variables.html#conditional-imports) to load more variables from external files. Ansible fails when trying to combine both these vars_files definitions with a `TypeError: unhashable type: 'list'`.
##### Steps To Reproduce:

Create empty files `vars/default`, `vars/a`, and `vars/b`. Then define this two playbooks:
**play.yml**:

```

---
- hosts: all
  vars_files: 
    - vars/default
- include: play-included.yml
```

**play-included.yml**:

```

---
- hosts: all
  vars_files: 
    - [ "vars/a", "vars/b" ]
  tasks:
    - fail: msg="This is actually success."
```

Now try to run `playbook.yml` with `ansible-playbook` and you get the error.
##### Expected Results:

Something like the following:

```
PLAY [all] ******************************************************************** 

GATHERING FACTS *************************************************************** 
ok: [web.dev.local]

PLAY [all] ******************************************************************** 

GATHERING FACTS *************************************************************** 
ok: [web.dev.local]

TASK: [fail msg="This is actually success."] ********************************** 
failed: [web.dev.local] => {"failed": true}
msg: This is actually success.

FATAL: all hosts have already failed -- aborting

PLAY RECAP ******************************************************************** 
           to retry, use: --limit @/Users/marc/play.retry

web.dev.local              : ok=2    changed=0    unreachable=0    failed=1   
```
##### Actual Results:

Ansible fails with the following error:

```
Traceback (most recent call last):
  File "/Users/marc/Documents/Projects/ansible/ansible/bin/ansible-playbook", line 317, in <module>
    sys.exit(main(sys.argv[1:]))
  File "/Users/marc/Documents/Projects/ansible/ansible/bin/ansible-playbook", line 200, in main
    force_handlers=options.force_handlers
  File "/Users/marc/Documents/Projects/ansible/ansible/lib/ansible/playbook/__init__.py", line 176, in __init__
    (self.playbook, self.play_basedirs) = self._load_playbook_from_file(playbook, vars)
  File "/Users/marc/Documents/Projects/ansible/ansible/lib/ansible/playbook/__init__.py", line 261, in _load_playbook_from_file
    p['vars_files'] = utils.list_union(p.get('vars_files', []), play_vars_files)
  File "/Users/marc/Documents/Projects/ansible/ansible/lib/ansible/utils/__init__.py", line 1014, in list_union
    set_a = set(_listify(a))
TypeError: unhashable type: 'list'
```
##### Reporter's comments:

The error is pretty clear in itself. The list_union function (introduced in 85bd6810) is defined as:

```
def list_union(a, b):
    set_a = set(_listify(a))
    set_b = set(_listify(b))
    return list(set_a.union(set_b))
```

... but this is conceptually wrong. You cannot insert lists into sets because they are not hashable. Moreover, sets have no guaranteed order, so using set_union to combine lists may break the variable load ordering as intended by the user. 



Discussion:

Resolving patch:
diff --git a/lib/ansible/utils/__init__.py b/lib/ansible/utils/__init__.py
--- a/lib/ansible/utils/__init__.py
+++ b/lib/ansible/utils/__init__.py
@@ -1003,21 +1003,22 @@ def is_list_of_strings(items):
             return False
     return True
 
-def _listify(a):
-    if not isinstance(a, (list, tuple)):
-        return [a,]
-    else:
-        return a
-
 def list_union(a, b):
-    set_a = set(_listify(a))
-    set_b = set(_listify(b))
-    return list(set_a.union(set_b))
+    result = []
+    for x in a:
+        if x not in result:
+            result.append(x)
+    for x in b:
+        if x not in result:
+            result.append(x)
+    return result
 
 def list_intersection(a, b):
-    set_a = set(_listify(a))
-    set_b = set(_listify(b))
-    return list(set_a.intersection(set_b))
+    result = []
+    for x in a:
+        if x in b and x not in result:
+            result.append(x)
+    return result
 
 def safe_eval(expr, locals={}, include_exceptions=False):
     '''


Test patch: