Repository: ansible/ansible

Issue:
json_query filter failing with type error
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
filter - json_query

##### ANSIBLE VERSION
```
ansible 2.2.1.0
  config file = 
  configured module search path = Default w/o overrides
```

##### CONFIGURATION
pip install, stock otherwise


##### OS / ENVIRONMENT
Debian Jessie

##### SUMMARY
Ansible 2.2.1 started throwing the below error when trying to use the json_query filter. The task below worked in 2.2.0 and started failing with 2.2.1

##### STEPS TO REPRODUCE
The +app_var_git_branch+ is a set_fact in the playbook.

```yaml
  - name: "Delete Application Version for current branch if more than 3 exist."
    elasticbeanstalk_version:
      region: "{{ region }}"
      app_name: "{{ application_name }}"
      version_label: "{{ item }}"
      delete_source: "{{ application_version_delete_source }}"
      state: "absent"
    when: elasticbeanstalk_versions.ApplicationVersions is defined
    with_items: >-
      {{ elasticbeanstalk_versions | json_query('ApplicationVersions[].VersionLabel[] | [?starts_with(@, '+app_var_git_branch+')] | [2:]') }}
```

##### EXPECTED RESULTS

```
TASK [Delete Application Version for current branch if more than 3 exist.] *****
changed: [localhost] => (item=multi-deployment-400-743)
```

Should either delete some application versions if it finds more than 3 of a given item that starts with app_var_git_branch, skip the task altogether or the task should receive no item and continue after 1 loop.

##### ACTUAL RESULTS
Task fails. This is running in a CI/CD environment in a python:2 docker container (which is based on debian jessie), I tried explicitly installing 2.2.0 instead of 2.2.1 and 2.2.0 still worked. I also checked on the dependencies that were installed and it appeared that JMESPath is using the same versions with both versions of Ansible. It also appears that it only fails when the JMESPath has something to evaluate, if the app_var_git_branch doesn't have anything to compare it completes without the error.
```
TASK [Delete Application Version for current branch if more than 3 exist.] *****
task path: /builds/cloud-platform/gitlab_ci_elasticbeanstalk/.ci/deploy.yml:101
An exception occurred during task execution. The full traceback is:
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/site-packages/ansible/executor/task_executor.py", line 90, in run
    items = self._get_loop_items()
  File "/usr/local/lib/python2.7/site-packages/ansible/executor/task_executor.py", line 193, in _get_loop_items
    loop_terms = listify_lookup_plugin_terms(terms=self._task.loop_args, templar=templar, loader=self._loader, fail_on_undefined=True, convert_bare=False)
  File "/usr/local/lib/python2.7/site-packages/ansible/utils/listify.py", line 34, in listify_lookup_plugin_terms
    terms = templar.template(terms.strip(), convert_bare=convert_bare, fail_on_undefined=fail_on_undefined)
  File "/usr/local/lib/python2.7/site-packages/ansible/template/__init__.py", line 383, in template
    disable_lookups=disable_lookups,
  File "/usr/local/lib/python2.7/site-packages/ansible/template/__init__.py", line 583, in do_template
    res = j2_concat(rf)
  File "<template>", line 10, in root
  File "/usr/local/lib/python2.7/site-packages/ansible/plugins/filter/json_query.py", line 40, in json_query
    return jmespath.search(expr, data)
  File "/usr/local/lib/python2.7/site-packages/jmespath/__init__.py", line 12, in search
    return parser.Parser().parse(expression).search(data, options=options)
  File "/usr/local/lib/python2.7/site-packages/jmespath/parser.py", line 524, in search
    result = interpreter.visit(self.parsed, value)
  File "/usr/local/lib/python2.7/site-packages/jmespath/visitor.py", line 66, in visit
    return method(node, *args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/jmespath/visitor.py", line 227, in visit_pipe
    result = self.visit(node, result)
  File "/usr/local/lib/python2.7/site-packages/jmespath/visitor.py", line 66, in visit
    return method(node, *args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/jmespath/visitor.py", line 227, in visit_pipe
    result = self.visit(node, result)
  File "/usr/local/lib/python2.7/site-packages/jmespath/visitor.py", line 66, in visit
    return method(node, *args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/jmespath/visitor.py", line 138, in visit_filter_projection
    if self._is_true(self.visit(comparator_node, element)):
  File "/usr/local/lib/python2.7/site-packages/jmespath/visitor.py", line 66, in visit
    return method(node, *args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/jmespath/visitor.py", line 129, in visit_function_expression
    return self._functions.call_function(node['value'], resolved_args)
  File "/usr/local/lib/python2.7/site-packages/jmespath/functions.py", line 89, in call_function
    self._validate_arguments(resolved_args, signature, function_name)
  File "/usr/local/lib/python2.7/site-packages/jmespath/functions.py", line 100, in _validate_arguments
    return self._type_check(args, signature, function_name)
  File "/usr/local/lib/python2.7/site-packages/jmespath/functions.py", line 107, in _type_check
    function_name)
  File "/usr/local/lib/python2.7/site-packages/jmespath/functions.py", line 122, in _type_check_single
    self._convert_to_jmespath_type(actual_typename), types)
JMESPathTypeError: In function starts_with(), invalid type for value: new-modules-561, expected one of: ['string'], received: "unknown"
fatal: [localhost]: FAILED! => {
    "failed": true, 
    "msg": "Unexpected failure during module execution.", 
    "stdout": ""
}
```
Ansible 2.2.1
```
Successfully installed MarkupSafe-0.23 PyYAML-3.12 ansible-2.2.1.0 boto-2.45.0 boto3-1.4.4 botocore-1.5.1 cffi-1.9.1 cryptography-1.7.1 docutils-0.13.1 enum34-1.1.6 futures-3.0.5 idna-2.2 ipaddress-1.0.18 jinja2-2.8.1 jmespath-0.9.0 paramiko-2.1.1 pyasn1-0.1.9 pycparser-2.17 pycrypto-2.6.1 python-dateutil-2.6.0 s3transfer-0.1.10 six-1.10.0
```
Ansible 2.2.0
```
Successfully installed MarkupSafe-0.23 PyYAML-3.12 ansible-2.2.0.0 boto-2.45.0 boto3-1.4.4 botocore-1.5.1 cffi-1.9.1 cryptography-1.7.1 docutils-0.13.1 enum34-1.1.6 futures-3.0.5 idna-2.2 ipaddress-1.0.18 jinja2-2.9.4 jmespath-0.9.0 paramiko-2.1.1 pyasn1-0.1.9 pycparser-2.17 pycrypto-2.6.1 python-dateutil-2.6.0 s3transfer-0.1.10 six-1.10.0
```

Could this be related to #20098?


Discussion:
@sidewinder12s this does not appear to be related to #20098 
I can confirm this issue and that downgrading ansible fixes the problem entirely.

Is there anything we can do to help get this fixed?
Is this happening with all json_query uses, or just specific ones? If the later, could someone please share a full example which reproduces this?
Possibly just specific ones. Here is what I was trying to use, which is pretty complicated: 

```yaml
  - name: "Delete Application Version for current branch if more than 3 exist."
    elasticbeanstalk_version:
      region: "{{ region }}"
      app_name: "{{ application_name }}"
      version_label: "{{ item }}"
      delete_source: "{{ application_version_delete_source }}"
      state: "absent"
    when: elasticbeanstalk_versions.ApplicationVersions is defined
    with_items: >-
      {{ elasticbeanstalk_versions | json_query('ApplicationVersions[].VersionLabel[] | [?starts_with(@, '+app_var_git_branch+')] | [2:]') }}
```
@sidewinder12s i see that above, however I'd like some JSON data to test with if possible.
@jimi-c I believe this should be equivalent to what my ansible playbook pulls. The app_var_git_branch is a variable based off the git branch this playbook is working within. The version label starts with the name the git branch, so in these examples is test-npm-check, terminate & master. So we are seeing how many of each kind of VersionLabel and if there are more than 3 of say master-NUMBER-NUMBER, we delete the oldest one.

```json
{
    "ApplicationVersions": [
        {
            "ApplicationName": "gitlab_ci_elasticbeanstalk",
            "Status": "UNPROCESSED",
            "VersionLabel": "test-npm-check-626-1313",
            "Description": "bla",
            "DateCreated": "2017-01-22T02:02:31.798Z",
            "DateUpdated": "2017-01-22T02:02:31.798Z",
            "SourceBundle": {
                "S3Bucket": "bla",
                "S3Key": "ci/beanstalk/gitlab_ci_elasticbeanstalk/gitlab_ci_elasticbeanstalk-626-1313.war"
            }
        },
        {
            "ApplicationName": "gitlab_ci_elasticbeanstalk",
            "Status": "UNPROCESSED",
            "VersionLabel": "terminate-611-1289",
            "Description": "bla",
            "DateCreated": "2017-01-20T00:34:29.864Z",
            "DateUpdated": "2017-01-20T00:34:29.864Z",
            "SourceBundle": {
                "S3Bucket": "bla",
                "S3Key": "ci/beanstalk/gitlab_ci_elasticbeanstalk/gitlab_ci_elasticbeanstalk-611-1289.war"
            }
        },
        {
            "ApplicationName": "gitlab_ci_elasticbeanstalk",
            "Status": "UNPROCESSED",
            "VersionLabel": "terminate-610-1286",
            "Description": "bla",
            "DateCreated": "2017-01-20T00:22:02.229Z",
            "DateUpdated": "2017-01-20T00:22:02.229Z",
            "SourceBundle": {
                "S3Bucket": "bla",
                "S3Key": "ci/beanstalk/gitlab_ci_elasticbeanstalk/gitlab_ci_elasticbeanstalk-610-1286.war"
            }
        },
        {
            "ApplicationName": "gitlab_ci_elasticbeanstalk",
            "Status": "UNPROCESSED",
            "VersionLabel": "master-609-1284",
            "Description": "bla",
            "DateCreated": "2017-01-19T23:54:32.902Z",
            "DateUpdated": "2017-01-19T23:54:32.902Z",
            "SourceBundle": {
                "S3Bucket": "bla",
                "S3Key": "ci/beanstalk/gitlab_ci_elasticbeanstalk/gitlab_ci_elasticbeanstalk-609-1284.war"
            }
        },
        {
            "ApplicationName": "gitlab_ci_elasticbeanstalk",
            "Status": "UNPROCESSED",
            "VersionLabel": "master-608-1282",
            "Description": "bla",
            "DateCreated": "2017-01-19T23:02:44.902Z",
            "DateUpdated": "2017-01-19T23:02:44.902Z",
            "SourceBundle": {
                "S3Bucket": "bla",
                "S3Key": "ci/beanstalk/gitlab_ci_elasticbeanstalk/gitlab_ci_elasticbeanstalk-608-1282.war"
            }
        },
        {
            "ApplicationName": "gitlab_ci_elasticbeanstalk",
            "Status": "UNPROCESSED",
            "VersionLabel": "master-606-1278",
            "Description": "bla'",
            "DateCreated": "2017-01-19T22:47:57.741Z",
            "DateUpdated": "2017-01-19T22:47:57.741Z",
            "SourceBundle": {
                "S3Bucket": "bla",
                "S3Key": "ci/beanstalk/gitlab_ci_elasticbeanstalk/gitlab_ci_elasticbeanstalk-606-1278.war"
            }
        }
    ]
}
```

Resolving patch:
diff --git a/lib/ansible/playbook/base.py b/lib/ansible/playbook/base.py
--- a/lib/ansible/playbook/base.py
+++ b/lib/ansible/playbook/base.py
@@ -438,7 +438,6 @@ def post_validate(self, templar):
 
                 # and assign the massaged value back to the attribute field
                 setattr(self, name, value)
-
             except (TypeError, ValueError) as e:
                 raise AnsibleParserError("the field '%s' has an invalid value (%s), and could not be converted to an %s. "
                                          "The error was: %s" % (name, value, attribute.isa, e), obj=self.get_ds())
diff --git a/lib/ansible/plugins/filter/json_query.py b/lib/ansible/plugins/filter/json_query.py
--- a/lib/ansible/plugins/filter/json_query.py
+++ b/lib/ansible/plugins/filter/json_query.py
@@ -18,9 +18,7 @@
 from __future__ import (absolute_import, division, print_function)
 __metaclass__ = type
 
-from ansible.errors import AnsibleError
-from ansible.plugins.lookup import LookupBase
-from ansible.utils.listify import listify_lookup_plugin_terms
+from ansible.errors import AnsibleError, AnsibleFilterError
 
 try:
     import jmespath
@@ -37,7 +35,13 @@ def json_query(data, expr):
         raise AnsibleError('You need to install "jmespath" prior to running '
                            'json_query filter')
 
-    return jmespath.search(expr, data)
+    try:
+        return jmespath.search(expr, data)
+    except jmespath.exceptions.JMESPathError as e:
+        raise AnsibleFilterError('JMESPathError in json_query filter plugin:\n%s' % e)
+    except Exception as e:
+        # For older jmespath, we can get ValueError and TypeError without much info.
+        raise AnsibleFilterError('Error in jmespath.search in json_query filter plugin:\n%s' % e)
 
 
 class FilterModule(object):


Test patch:
diff --git a/test/integration/targets/filters/tasks/main.yml b/test/integration/targets/filters/tasks/main.yml
--- a/test/integration/targets/filters/tasks/main.yml
+++ b/test/integration/targets/filters/tasks/main.yml
@@ -120,6 +120,20 @@
     that:
       - "users | json_query('[*].hosts[].host') == ['host_a', 'host_b', 'host_c', 'host_d']"
 
+- name: "20379 - set_fact app_var_git_branch "
+  set_fact:
+    app_var_git_branch: multi-deployment-400-743
+
+- name: "20379 - trigger a error in jmespath via json_query filter to test error handling"
+  debug:
+    msg: "{{ example_20379 | json_query('ApplicationVersions[].VersionLabel[] | [?starts_with(@, `multi`)]') }}"
+  ignore_errors: true
+
+- name: "20379 - Test errors related to https://github.com/ansible/ansible/issues/20379"
+  assert:
+    that: "example_20379 | json_query('ApplicationVersions[].VersionLabel[] | [?starts_with(@, '+app_var_git_branch+')] | [2:]') == multisdfsdf"
+  ignore_errors: true
+
 - name: Test hash filter
   assert:
     that:
diff --git a/test/integration/targets/filters/vars/main.yml b/test/integration/targets/filters/vars/main.yml
--- a/test/integration/targets/filters/vars/main.yml
+++ b/test/integration/targets/filters/vars/main.yml
@@ -16,3 +16,103 @@ users:
       - host: host_c
         password: default
       - host: host_d
+
+other_data:
+  level1:
+    foo: bar
+    blip: baz
+    nested:
+        abc: def
+        ghi: xyz
+        alist:
+          - alpha
+          - beta
+          - charlie
+          - delta
+  level2:
+    asd: df
+    xc: dsdfsfsd
+    nested:
+      abc: foo
+      alist:
+        - zebra
+        - yellow
+        - xray
+
+# from https://github.com/ansible/ansible/issues/20379#issuecomment-280492883
+example_20379: {
+    "ApplicationVersions": [
+        {
+            "ApplicationName": "gitlab_ci_elasticbeanstalk",
+            "Status": "UNPROCESSED",
+            "VersionLabel": "test-npm-check-626-1313",
+            "Description": "bla",
+            "DateCreated": "2017-01-22T02:02:31.798Z",
+            "DateUpdated": "2017-01-22T02:02:31.798Z",
+            "SourceBundle": {
+                "S3Bucket": "bla",
+                "S3Key": "ci/beanstalk/gitlab_ci_elasticbeanstalk/gitlab_ci_elasticbeanstalk-626-1313.war"
+            }
+        },
+        {
+            "ApplicationName": "gitlab_ci_elasticbeanstalk",
+            "Status": "UNPROCESSED",
+            "VersionLabel": "terminate-611-1289",
+            "Description": "bla",
+            "DateCreated": "2017-01-20T00:34:29.864Z",
+            "DateUpdated": "2017-01-20T00:34:29.864Z",
+            "SourceBundle": {
+                "S3Bucket": "bla",
+                "S3Key": "ci/beanstalk/gitlab_ci_elasticbeanstalk/gitlab_ci_elasticbeanstalk-611-1289.war"
+            }
+        },
+        {
+            "ApplicationName": "gitlab_ci_elasticbeanstalk",
+            "Status": "UNPROCESSED",
+            "VersionLabel": "terminate-610-1286",
+            "Description": "bla",
+            "DateCreated": "2017-01-20T00:22:02.229Z",
+            "DateUpdated": "2017-01-20T00:22:02.229Z",
+            "SourceBundle": {
+                "S3Bucket": "bla",
+                "S3Key": "ci/beanstalk/gitlab_ci_elasticbeanstalk/gitlab_ci_elasticbeanstalk-610-1286.war"
+            }
+        },
+        {
+            "ApplicationName": "gitlab_ci_elasticbeanstalk",
+            "Status": "UNPROCESSED",
+            "VersionLabel": "master-609-1284",
+            "Description": "bla",
+            "DateCreated": "2017-01-19T23:54:32.902Z",
+            "DateUpdated": "2017-01-19T23:54:32.902Z",
+            "SourceBundle": {
+                "S3Bucket": "bla",
+                "S3Key": "ci/beanstalk/gitlab_ci_elasticbeanstalk/gitlab_ci_elasticbeanstalk-609-1284.war"
+            }
+        },
+        {
+            "ApplicationName": "gitlab_ci_elasticbeanstalk",
+            "Status": "UNPROCESSED",
+            "VersionLabel": "master-608-1282",
+            "Description": "bla",
+            "DateCreated": "2017-01-19T23:02:44.902Z",
+            "DateUpdated": "2017-01-19T23:02:44.902Z",
+            "SourceBundle": {
+                "S3Bucket": "bla",
+                "S3Key": "ci/beanstalk/gitlab_ci_elasticbeanstalk/gitlab_ci_elasticbeanstalk-608-1282.war"
+            }
+        },
+        {
+            "ApplicationName": "gitlab_ci_elasticbeanstalk",
+            "Status": "UNPROCESSED",
+            "VersionLabel": "master-606-1278",
+            "Description": "bla'",
+            "DateCreated": "2017-01-19T22:47:57.741Z",
+            "DateUpdated": "2017-01-19T22:47:57.741Z",
+            "SourceBundle": {
+                "S3Bucket": "bla",
+                "S3Key": "ci/beanstalk/gitlab_ci_elasticbeanstalk/gitlab_ci_elasticbeanstalk-606-1278.war"
+            }
+        }
+    ]
+}