Repository: ansible/ansible

Issue:
Ansible 2.0.0.2: Booleans in dictionaries cause to_json to fail
Very weird issue that is easy to reproduce.

If a dictionary has a boolean defined, `to_json` returns a `string` rather than a real `dict`.

The following playbook displays the problem:

``` yaml

---
- hosts: localhost
  gather_facts: no
  vars:
    works:
      key1: 'string'
      key2: 1234
    fails:
      key1: 'string'
      key2: 1234
      key3: false
  tasks:
  - debug: msg={{ works | to_json }}
  - debug: msg={{ fails | to_json }}
```

If you run this, you will see that the second dict fails:

```
[dag@moria ansible.dag]$ ansible-playbook test49.yml 

PLAY ***************************************************************************

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": {
        "key1": "string", 
        "key2": 1234
    }
}

TASK [debug] *******************************************************************
ok: [localhost] => {
    "msg": "{\"key3\": false, \"key2\": 1234, \"key1\": \"string\"}"
}

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0
```

We experience this issue within roles where we assign a dictionary to a role variable.



Discussion:
`to_json` should always return a string as it should avoid 'typing' the contents, if passing a dict it will 'break' anything expecting an actual JSON document (mainly some modules).

@bcoca Sure, but what I meant is that one is correctly handled as a dictionary by Ansible, the other is not.

The problem is not the debug module itself, because it also fails with roles etc. We get errors like:

```
fatal: [system01] => One or more undefined variables: 'unicode' object has no attribute 'tomcat'
```

https://github.com/ansible/ansible/blob/devel/lib/ansible/template/__init__.py#L329 <= this is what we use to force 'to_json' to stay as strings (https://github.com/ansible/ansible/blob/devel/lib/ansible/template/__init__.py#L174 <= sets regex)

when looking at each var w/o the filter i do get dicts in both cases, not sure what is going on but this is a bug.

@bcoca Thanks for the pointers (I was going crazy pinpointing this).
Hopefully I will find some time tomorrow to look at this.

@bcoca Can you look if this is done to your taste ? It works as expected.

I found that the existing code raised a NameError (but somehow ended up corrupting the actual data).

```
[WARNING]: Error when using <bound method CallbackModule.v2_runner_on_ok of </usr/lib/python2.6/site-packages/ansible/plugins/callback/default.CallbackModule object at 0x33bac10>>: NameError("name 'false' is not defined",) is not JSON serializable
```

I guess this in itself is worthy of looking into.


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
@@ -1365,6 +1365,14 @@ def safe_eval(expr, locals={}, include_exceptions=False):
     http://stackoverflow.com/questions/12523516/using-ast-and-whitelists-to-make-pythons-eval-safe
     '''
 
+    # define certain JSON types
+    # eg. JSON booleans are unknown to python eval()
+    JSON_TYPES = {
+        'false': False,
+        'null': None,
+        'true': True,
+    }
+
     # this is the whitelist of AST nodes we are going to
     # allow in the evaluation. Any node type other than
     # those listed here will raise an exception in our custom
@@ -1428,7 +1436,7 @@ def generic_visit(self, node, inside_call=False):
         parsed_tree = ast.parse(expr, mode='eval')
         cnv.visit(parsed_tree)
         compiled = compile(parsed_tree, expr, 'eval')
-        result = eval(compiled, {}, locals)
+        result = eval(compiled, JSON_TYPES, dict(locals))
 
         if include_exceptions:
             return (result, None)


Test patch: