Filter elements containing string with JMESPath
Asked Answered
D

2

5

I want to get a list of addresses of a defined interface type.
I found some info here.

Here is my playbook:

- name: Test JMESPath
  hosts: localhost
  gather_facts: no

  vars:
    interfaces:
    - name: em0
      address: 10.127.37.89/29
    - name: bge0
      address: 10.112.171.81/28
    - name: bge1
      address: 10.112.171.65/28
    - name: bge2
      address: 10.112.171.97/28
  tasks:
    - name: JMESPath query
      set_fact:
        result: "{{ interfaces | json_query(query) }}"
      vars:
        query: "[?name.contains(@, 'bge')].address"

    - debug:
        var: result

I'd like to get:

[
  "10.112.171.81/28",
  "10.112.171.65/28",
  "10.112.171.97/28"
]

It works on JMESPath website, but my playbook fails :

ansible-playbook play-testJMESPath.yml [WARNING]: provided hosts list
is empty, only localhost is available. Note that the implicit
localhost does not match 'all'

PLAY [Test JMESPath]
**************************************************************************************************************************************************************************************************

TASK [JMESPath query]
************************************************************************************************************************************************************************************************* fatal: [localhost]: FAILED! => {"msg": "JMESPathError in json_query
filter plugin:\nIn function contains(), invalid type for value:
external, expected one of: ['array', 'string'], received:
\"unknown\""}

PLAY RECAP
************************************************************************************************************************************************************************************************************ localhost                  : ok=0    changed=0    unreachable=0   
failed=1    skipped=0    rescued=0    ignored=0

Could someone explain me why?

Desilva answered 1/12, 2020 at 16:57 Comment(0)
C
5

For the JMESPath issue you are seeing, this is explained here:

The problem is related to the fact that Ansible uses own types for strings: AnsibleUnicode and AnsibleUnsafeText. And as long as jmespath library has very strict type-checking, it fails to accept this types as string literals.

Source: https://github.com/ansible/ansible/issues/27299#issuecomment-331068246


The trick to make it work, as explained in the same issue, is to use a to_json | from_json filter pair, in order to force back the right type.

So, the task:

- debug:
    msg: "{{ interfaces | to_json | from_json | json_query(query) }}"
  vars:
    query: "[?name.contains(@, 'bge')].address"
    interfaces:
      - name: em0
        address: 10.127.37.89/29
      - name: bge0
        address: 10.112.171.81/28
      - name: bge1
        address: 10.112.171.65/28
      - name: bge2
        address: 10.112.171.97/28

Gives the expected:

ok: [localhost] => 
  msg:
  - 10.112.171.81/28
  - 10.112.171.65/28
  - 10.112.171.97/28
Cuticula answered 1/12, 2020 at 18:34 Comment(0)
T
3

Jinja filters can use Python regex tests. This makes them a stronger tool compared to JMESPath contains function which only " returns true if the string contains the provided $search argument".

The tasks below

    - set_fact:
        result: "{{ interfaces|
                    selectattr('name', 'search', 'bge')|
                    map(attribute='address')|
                    list }}"
    - debug:
        var: result

give

    "result": [
        "10.112.171.81/28",
        "10.112.171.65/28",
        "10.112.171.97/28"
    ]
Thermistor answered 1/12, 2020 at 18:25 Comment(2)
good, you can also use the match attribute of selectattr: ``` "{{ interfaces | selectattr('name', 'match', '^bge') | map(attribute='address')| list }}" ```Awhile
Thank you, this might come in handyDesilva

© 2022 - 2024 — McMap. All rights reserved.