Repository: ansible/ansible

Issue:
(edited) particular traceback involving filter
Version: ansible 1.4.4

The following playbook executes the set_fact and debug tasks successfully. The uri task then throws an UnboundLocalError. Traceback is below.

```

---
- name: testguy
  gather_facts: no
  sudo: no
  hosts: local

  tasks:
  - set_fact:
      stash_json: { text: "some thing" }
  - debug:
      msg: '{{ stash_json | to_json }}'
  - uri: >
      url=http://www.example.com
      method=POST
      body='{{ stash_json | to_json }}'
      return_content=yes
      HEADER_Content-Type="application/json"
```

I then execute:

```
$ ansible-playbook -i hosts test.yml
```

Result:

```
PLAY [test] **************************************************************** 

TASK: [set_fact ] ************************************************************* 
ok: [localhost]

TASK: [debug ] **************************************************************** 
ok: [localhost] => {
    "msg": {
        "text": "some thing"
    }
}
Traceback (most recent call last):
  File "/tmp/jim/env/bin/ansible-playbook", line 269, in <module>
    sys.exit(main(sys.argv[1:]))
  File "/tmp/jim/env/bin/ansible-playbook", line 209, in main
    pb.run()
  File "/tmp/jim/env/local/lib/python2.7/site-packages/ansible/playbook/__init__.py", line 263, in run
    if not self._run_play(play):
  File "/tmp/jim/env/local/lib/python2.7/site-packages/ansible/playbook/__init__.py", line 592, in _run_play
    if not self._run_task(play, task, False):
  File "/tmp/jim/env/local/lib/python2.7/site-packages/ansible/playbook/__init__.py", line 357, in _run_task
    self.callbacks.on_task_start(template(play.basedir, name, task.module_vars, lookup_fatal=False, filter_fatal=False), is_handler)
  File "/tmp/jim/env/local/lib/python2.7/site-packages/ansible/utils/template.py", line 319, in template
    varname = template_from_string(basedir, varname, vars, fail_on_undefined)
  File "/tmp/jim/env/local/lib/python2.7/site-packages/ansible/utils/template.py", line 543, in template_from_string
    return res
UnboundLocalError: local variable 'res' referenced before assignment
```



Discussion:
This is not actually the url module but elsewhere, but yes, this shouldn't traceback.

I created a simple test for this:

``` yaml
---
- hosts: localhost
  gather_facts: no
  connection: local
  tasks:
  - set_fact:
      stash_json:
        text: "some thing"
  - debug:
      msg: "{{ stash_json | to_json }}"
  - debug: msg="{{ stash_json }}"
  - debug: msg="{{ stash_json | to_json }}"
```

Both the 1st and 2nd debug work, but the 3rd fails.

I made the following change in the code (there are several tickets referencing this exception) to find out what the real issue is:

``` diff
diff --git a/lib/ansible/utils/template.py b/lib/ansible/utils/template.py
index 2491035..4655bb1 100644
--- a/lib/ansible/utils/template.py
+++ b/lib/ansible/utils/template.py
@@ -547,6 +547,8 @@ def template_from_string(basedir, data, vars, fail_on_undefined=False):
         except TypeError, te:
             if 'StrictUndefined' in str(te):
                 raise errors.AnsibleUndefinedVariable("unable to look up a name or access an attribute in template string")
+            else:
+                raise
         return res
     except (jinja2.exceptions.UndefinedError, errors.AnsibleUndefinedVariable):
         if fail_on_undefined
```

What I found is:

```
Traceback (most recent call last):
  File "/path/to/ansible/bin/ansible-playbook", line 283, in <module>
    sys.exit(main(sys.argv[1:]))
  File "/path/to/ansible/bin/ansible-playbook", line 223, in main
    pb.run()
  File "/path/to/ansible/lib/ansible/playbook/__init__.py", line 270, in run
    if not self._run_play(play):
  File "/path/to/ansible/lib/ansible/playbook/__init__.py", line 636, in _run_play
    if not self._run_task(play, task, False):
  File "/path/to/ansible/lib/ansible/playbook/__init__.py", line 384, in _run_task
    self.callbacks.on_task_start(template(play.basedir, name, task.module_vars, lookup_fatal=False, filter_fatal=False), is_handler)
  File "/path/to/ansible/lib/ansible/utils/template.py", line 319, in template
    varname = template_from_string(basedir, varname, vars, fail_on_undefined)
  File "/path/to/ansible/lib/ansible/utils/template.py", line 546, in template_from_string
    res = jinja2.utils.concat(rf)
  File "<template>", line 10, in root
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.py", line 243, in dumps
    return _default_encoder.encode(obj)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.py", line 207, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.py", line 270, in iterencode
    return _iterencode(o, 0)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.py", line 184, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: Undefined is not JSON serializable
```

I haven't had time for any deeper digging.

So it seems the filter logic here just needs something to grok the undefined error, or _really_ what we need to do is catch the Undefined and raise an errors.AnsibleError or something.

Doesn't seem too serious.

Thanks @sivel!

Why is there even an error in the first place? Isn't all the syntax in the playbook perfectly legal?


Resolving patch:
diff --git a/lib/ansible/runner/filter_plugins/core.py b/lib/ansible/runner/filter_plugins/core.py
--- a/lib/ansible/runner/filter_plugins/core.py
+++ b/lib/ansible/runner/filter_plugins/core.py
@@ -30,9 +30,13 @@ def to_nice_yaml(*a, **kw):
     '''Make verbose, human readable yaml'''
     return yaml.safe_dump(*a, indent=4, allow_unicode=True, default_flow_style=False, **kw)
 
-def to_nice_json(*a, **kw):
+def to_json(a, *args, **kw):
+    ''' Convert the value to JSON '''
+    return json.dumps(a, *args, **kw)
+
+def to_nice_json(a, *args, **kw):
     '''Make verbose, human readable JSON'''
-    return json.dumps(*a, indent=4, sort_keys=True, **kw)
+    return json.dumps(a, indent=4, sort_keys=True, *args, **kw)
 
 def failed(*a, **kw):
     ''' Test if task result yields failed '''
@@ -148,7 +152,7 @@ def filters(self):
             'b64encode': base64.b64encode,
 
             # json
-            'to_json': json.dumps,
+            'to_json': to_json,
             'to_nice_json': to_nice_json,
             'from_json': json.loads,
 
diff --git a/lib/ansible/utils/template.py b/lib/ansible/utils/template.py
--- a/lib/ansible/utils/template.py
+++ b/lib/ansible/utils/template.py
@@ -547,6 +547,8 @@ def my_lookup(*args, **kwargs):
         except TypeError, te:
             if 'StrictUndefined' in str(te):
                 raise errors.AnsibleUndefinedVariable("unable to look up a name or access an attribute in template string")
+            else:
+                raise errors.AnsibleError("an unexpected type error occured. Error was %s" % te)
         return res
     except (jinja2.exceptions.UndefinedError, errors.AnsibleUndefinedVariable):
         if fail_on_undefined:


Test patch: