Search code examples
ansiblejmespath

Usage of Ansible JMESPath in queries


I am using following JSON file:

sample.json:

{
    "lldp_output['gathered']": [
        {
            "mode": "trunk",
            "name": "GigabitEthernet0/0",
            "trunk": {
                "encapsulation": "dot1q"
            }
        },
        {
            "access": {
                "vlan": 12
            },
            "mode": "access",
            "name": "GigabitEthernet0/1"
        },
        {
            "name": "GigabitEthernet0/2"
        }
    ]
}

And the playbook:

---
- hosts: localhost
  gather_facts: no
  vars:
    tmpdata: "{{ lookup('file','sample.json') | from_json }}"

  tasks:
    - name: Take 4
      debug:
        msg: "{{ tmpdata | community.general.json_query(lldp_output['gathered']) }}"

I get the following error:

TASK [Take 4] ********************************************************************************************
task path: /root/scripts/atest.yml:18
fatal: [localhost]: FAILED! => {
    "msg": "Error in jmespath.search in json_query filter plugin:\n'lldp_output' is undefined"
}

How do I query the JSON shown so I get a list of all ports that have mode: trunk

When I run in a playbook:

---
- name: Find trunk ports
  hosts: ios

  tasks:
    - name: Collect interface output
      cisco.ios.ios_l2_interfaces:
        config:
        state: gathered
      register:
         "intf_output"

    - debug:
        var=intf_output

    - name: Take 4
      debug:
        msg: "{{ intf_output | json_query(query) }}"
      vars:
        query: >-
          "lldp_output['gathered']"[?mode=='trunk']

The structure returned is like following:

{
    "intf_output": {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python3"
        },
        "changed": false,
        "failed": false,
        "gathered": [
            {
                "name": "GigabitEthernet0/0"
            },
            {
                "mode": "trunk",
                "name": "GigabitEthernet0/1",
                "trunk": {
                    "allowed_vlans": [
                        "10",
                        "20",
                        "30",
                        "100"
                    ],
                    "encapsulation": "dot1q"
                }
            },
            {
                "mode": "trunk",
                "name": "GigabitEthernet0/2",
                "trunk": {
                    "allowed_vlans": [
                        "10",
                        "20",
                        "30",
                        "100"
                    ],
                    "encapsulation": "dot1q"
                }
            },
            {
                "mode": "trunk",
                "name": "GigabitEthernet0/3",
                "trunk": {
                    "allowed_vlans": [
                        "10",
                        "20",
                        "30",
                        "100"
                    ],
                    "encapsulation": "dot1q"
                }
            },
            {
                "mode": "trunk",
                "name": "GigabitEthernet1/0",
                "trunk": {
                    "allowed_vlans": [
                        "10",
                        "20",
                        "30",
                        "100"
                    ],
                    "encapsulation": "dot1q"
                }
            },
            {
                "mode": "trunk",
                "name": "GigabitEthernet1/1",
                "trunk": {
                    "allowed_vlans": [
                        "10",
                        "20",
                        "30",
                        "100"
                    ],
                    "encapsulation": "dot1q"
                }
            },
            {
                "name": "GigabitEthernet1/2"
            },
            {
                "name": "GigabitEthernet1/3"
            },
            {
                "name": "GigabitEthernet2/0"
            },
            {
                "name": "GigabitEthernet2/1"
            },
            {
                "name": "GigabitEthernet2/2"
            },
            {
                "name": "GigabitEthernet2/3"
            },
            {
                "name": "GigabitEthernet3/0"
            },
            {
                "name": "GigabitEthernet3/1"
            },
            {
                "name": "GigabitEthernet3/2"
            },
            {
                "name": "GigabitEthernet3/3"
            }
        ]
    }
}

For each host I run against the playbook against.


Solution

  • The argument to json_query must be a string. Because you haven't quoted your argument, Ansible is looking for a variable named lldp_output. But you've got additonal problems, since you're trying to access a key named lldp_output['gathered'], but [ is a syntactically significant character in JSON (and JMESPath queries), so that needs to be escaped as well.

    In order to avoid all sorts of quote escaping contortions, we can place the query itself into a variable, so that we have:

    - hosts: localhost
      vars:
        tmpdata: "{{ lookup('file','sample.json') | from_json }}"
    
      tasks:
        - name: Take 4
          debug:
            msg: "{{ tmpdata | json_query(query) }}"
          vars:
            query: >-
              "lldp_output['gathered']"
    

    Note that we are using the >- block quote operator, which means that the value of query is the literal string "lldp_output['gathered']", including the outer quotes.

    That playbook outputs:

    TASK [Take 4] *********************************************************************************
    ok: [localhost] => {
        "msg": [
            {
                "mode": "trunk",
                "name": "GigabitEthernet0/0",
                "trunk": {
                    "encapsulation": "dot1q"
                }
            },
            {
                "access": {
                    "vlan": 12
                },
                "mode": "access",
                "name": "GigabitEthernet0/1"
            },
            {
                "name": "GigabitEthernet0/2"
            }
        ]
    }
    

    To get just those systems with mode equal to trunk, just add that criteria to your query:

    - hosts: localhost
      vars:
        tmpdata: "{{ lookup('file','sample.json') | from_json }}"
    
      tasks:
        - name: Take 4
          debug:
            msg: "{{ tmpdata | json_query(query) }}"
          vars:
            query: >-
              "lldp_output['gathered']"[?mode=='trunk']
    

    This will output:

    TASK [Take 4] *********************************************************************************
    ok: [localhost] => {
        "msg": [
            {
                "mode": "trunk",
                "name": "GigabitEthernet0/0",
                "trunk": {
                    "encapsulation": "dot1q"
                }
            }
        ]
    }
    

    Update

    Given the data you've shown in your updated question, things are much simpler, because you don't have the weird quoting you had in the original question. With intf_output defined as shown, you can write:

      tasks:
        - name: Take 4
          debug:
            msg: "{{ intf_output | json_query(query) }}"
          vars:
            query: >-
              gathered[?mode=='trunk']