From e54a9d3a514c6fbd0214209a069372af3a2150ed Mon Sep 17 00:00:00 2001 From: Filipe Niero Felisbino Date: Mon, 8 Aug 2016 12:55:59 -0300 Subject: [PATCH] Add generic data structures querying (#13684) * Query lookup plugin * Add license and docstrings * Add python3-ish imports * Change query plugin type from lookup to filter * Switch from dq to jsonpath_rw * Add integration test for query filter * Rename query filter to json_query * Add jsonpath-rw * Rename query filter to json_query * Switch query implementation from jsonpath-rw to jmespath --- lib/ansible/plugins/filter/json_query.py | 48 +++++++++++++++++++ .../roles/test_filters/tasks/main.yml | 16 +++++-- .../roles/test_filters/vars/main.yml | 14 +++++- test/utils/shippable/integration.sh | 2 + test/utils/shippable/remote-integration.sh | 3 +- 5 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 lib/ansible/plugins/filter/json_query.py diff --git a/lib/ansible/plugins/filter/json_query.py b/lib/ansible/plugins/filter/json_query.py new file mode 100644 index 00000000000..479721e9b74 --- /dev/null +++ b/lib/ansible/plugins/filter/json_query.py @@ -0,0 +1,48 @@ +# (c) 2015, Filipe Niero Felisbino +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +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 + +try: + import jmespath + HAS_LIB = True +except ImportError: + HAS_LIB = False + + +def json_query(data, expr): + '''Query data using jmespath query language ( http://jmespath.org ). Example: + - debug: msg="{{ instance | json_query(tagged_instances[*].block_device_mapping.*.volume_id') }}" + ''' + if not HAS_LIB: + raise AnsibleError('You need to install "jmespath" prior to running ' + 'json_query filter') + + return jmespath.search(expr, data) + +class FilterModule(object): + ''' Query filter ''' + + def filters(self): + return { + 'json_query': json_query + } diff --git a/test/integration/roles/test_filters/tasks/main.yml b/test/integration/roles/test_filters/tasks/main.yml index 6d75c0d81cf..3e500ae6042 100644 --- a/test/integration/roles/test_filters/tasks/main.yml +++ b/test/integration/roles/test_filters/tasks/main.yml @@ -40,8 +40,8 @@ register: diff_result_9851 - name: 9851 - verify generated file matches known good - assert: - that: + assert: + that: - 'diff_result_9851.stdout == ""' - name: fill in a basic template @@ -56,9 +56,9 @@ register: diff_result - name: verify templated file matches known good - assert: - that: - - 'diff_result.stdout == ""' + assert: + that: + - 'diff_result.stdout == ""' - name: Verify human_readable assert: @@ -77,3 +77,9 @@ - "31 == ['x','y']|map('extract',{'x':42,'y':31})|list|last" - "'local' == ['localhost']|map('extract',hostvars,'ansible_connection')|list|first" - "'local' == ['localhost']|map('extract',hostvars,['ansible_connection'])|list|first" + +- name: Test json_query filter + assert: + that: + - "users | json_query('[*].hosts[].host') == ['host_a', 'host_b', 'host_c', 'host_d']" + diff --git a/test/integration/roles/test_filters/vars/main.yml b/test/integration/roles/test_filters/vars/main.yml index 133c2b613b8..7b9f609c959 100644 --- a/test/integration/roles/test_filters/vars/main.yml +++ b/test/integration/roles/test_filters/vars/main.yml @@ -1,6 +1,18 @@ some_structure: - "this is a list element" - - + - this: "is a hash element in a list" warp: 9 where: endor + +users: + - name: steve + hosts: + - host: host_a + password: abc + - host: host_b + - name: bill + hosts: + - host: host_c + password: default + - host: host_d diff --git a/test/utils/shippable/integration.sh b/test/utils/shippable/integration.sh index eea87b465c2..c74ba35883d 100755 --- a/test/utils/shippable/integration.sh +++ b/test/utils/shippable/integration.sh @@ -85,6 +85,8 @@ container_id=$(docker run -d \ show_environment +docker exec "${container_id}" pip install jmespath + if [ "${copy_source}" ]; then docker exec "${container_id}" cp -a "${test_shared_dir}" "${test_ansible_dir}" fi diff --git a/test/utils/shippable/remote-integration.sh b/test/utils/shippable/remote-integration.sh index 3f7663029a4..b3d7b844ef0 100644 --- a/test/utils/shippable/remote-integration.sh +++ b/test/utils/shippable/remote-integration.sh @@ -44,7 +44,8 @@ pkg install -y \ # TODO: bootstrap.sh should install these pip install \ junit-xml \ - virtualenv + virtualenv \ + jmespath # FIXME: tests assume bash is in /bin/bash if [ ! -f /bin/bash ]; then