Pragmatic approach to extract a value from my inventory data object

Hello everyone,

I have a fairly large inventory data structure from which I need to extract a value within a playbook. I’m wondering if this is possible with native Ansible, for example, by directly applying a filter plugin in the playbook or so…

Here is my data structure represented as YAML for illustration:

hostvars[inventory_hostname]:
  interfaces:
    - name: lo0
      ipaddr:
        - addr: 192.0.2.1
          family:
            label: IPv4

I need the IP address (here “192.0.2.1”) from the key “addr”.

Short summary of the above data structure:

  • the list “interfaces” has an element with a dict
  • this dict has a key of “name” that must be of the value “lo0
  • it has also a key “ipaddr” that contains a list
  • this list contains a dictionary that has the key “family
  • that key is a dict that has a key “label” that must be of the value “IPv4.”

As far as I know I could write a custom filter plugin for this, but I would prefer to have it natively within the playbook for better portability and understanding by operational colleagues.

Any help much appreciated!

Thanks,
Alex

I’d use JMESPath for this via the community.general.json_query filter see Selecting JSON data: JSON queries, the JMESPath Terminal makes working out queries a little easier.

This isn’t the answer but something like this should be possible work out:

"hostvars[inventory_hostname]".interfaces[?name=='lo0'].ipaddr|[].addr|[0]
3 Likes

{{ hostvars[inventory_hostname].interfaces }} is the same as {{ interfaces }}.
With that simplification, the expression you want is… complicated. Here it is, built up one step at a time.

    - name: interfaces
      ansible.builtin.debug:
        msg: '{{ interfaces }}'

    - name: interfaces.name==lo0
      ansible.builtin.debug:
        msg: '{{ interfaces
                 | selectattr("name","eq","lo0") }}'

    - name: interfaces.name==lo0 ipaddr
      ansible.builtin.debug:
        msg: '{{ interfaces
                 | selectattr("name","eq","lo0")
                 | map(attribute="ipaddr") }}'

    - name: interfaces.name==lo0 ipaddr flatten
      ansible.builtin.debug:
        msg: '{{ interfaces
                 | selectattr("name","eq","lo0")
                 | map(attribute="ipaddr")
                 | flatten }}'

    - name: interfaces.name==lo0 ipaddr flatten family.label==IPv4
      ansible.builtin.debug:
        msg: '{{ interfaces
                 | selectattr("name","eq","lo0")
                 | map(attribute="ipaddr")
                 | flatten
                 | selectattr("family.label", "eq", "IPv4") }}'

    - name: interfaces.name==lo0 ipaddr flatten family.label==IPv4 addr
      ansible.builtin.debug:
        msg: '{{ interfaces
                 | selectattr("name","eq","lo0")
                 | map(attribute="ipaddr")
                 | flatten
                 | selectattr("family.label", "eq", "IPv4")
                 | map(attribute="addr") }}'

Cheers!

5 Likes

Thanks a lot for the great solutions. I’d prefer Todd’s ‘native’ solution if it didn’t require so many individual steps. I’ve installed JMESPath and got it to work with the following task and expression:

tasks:
  - name: Setting routerid to value of ipv4 loopback address
    ansible.builtin.set_fact:
      routerid: "{{ interfaces | community.general.json_query('[?name == `lo0`].ipaddr[] | [?family.label == `IPv4`].addr') }}"

I think I need to ponder a bit more about this really efficient looking expression… :slight_smile:

A big thanks to both of you!

2 Likes

If by steps you mean the five filters in the pipeline, that’s the nature of the beast.

But if by steps you mean playbook tasks, all but the last one are there just so you can see what each stage in the pipeline produces. It’s the expression in the last debug step that solves your initial ask.

My problem with a json_query solution is, now you have to grok both Jinja2 and JMESPath queries.

4 Likes

Oh, mea culpa, what nonsense I’ve written. I’ve got a lot on my plate, sorry Todd. I believe the native solution, in its expression, is on par with the JSON query, obviously!

Now I’m torn because this decision will likely set the direction for hundreds of similar requests.

I wonder which implementation is more efficiently handled in software and executed in hardware. Perhaps I should conduct some performance tests between them.

Compared to all the things that have to work – on the controller and any remote hosts – for any playbook of any significance at all, you could optimize either of those expressions down to a single cycle and not notice any improvement.

Far more important is the number of seconds/minutes you and those who follow will have to spend looking at however you end up spelling the solution to this and other problems.

(1) Readability, (2) Maintainability, … (17) vi-vs.-emacs, … (276) Efficiency of Jinja2 expressions.

Okay, maybe I exaggerated. But in 100,000,000 runs you’ll not gain back the time it’s taken you to read this message.

OTOH, familiarity with JMESPath is certainly worthwhile, and Jinja2 is expressly “limited as a feature”. (I’ve never bought the “not designed to program with” argument, from Jinja2, Ansible, or elsewhere. It’s trotted out whenever someone pushes a tool beyond what the inventors envisioned. It’s the same as Apple’s old argument that color wasn’t important — until they released a color Mac. Hm.)

1 Like

In terms of readabilty in the playbook, I’d use Chris’ solution, but define a second variable in the host_vars:

lo0_ipv4_address: "hostvars[inventory_hostname]".interfaces[?name=='lo0'].ipaddr|[].addr|[0] (something like this, I’m in mobile so cannot test).

This would make the playbook more readable with the caveat that you have another layer of abstraction.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.