Parsing CLI output and searching JSON, YAML and XML with Ansible

I’ve been meaning to start a thread on this for a while… over the last year or so I have been getting deeper and deeper into generating and searching JSON using Ansible and I’d like to share some of the tools and methods I’ve been using and if anyone else using Ansible (@decentral1se @stephen @matt) have things to share that would be great…

JMESPath

The JMESPath query language for JSON is available as an Ansible filter, community.general.json_query and can using be used on the command line via jp and tested interactively using jpterm, both jp and jpterm can be installed locally using this Ansible role.

This is really handy if you have a big block of JSON and just want to get one thing from it, for example findmnt has the -J option for outputting JSON, findmnt -J

{
   "filesystems": [
      {"target":"/", "source":"/dev/xvda2", "fstype":"ext4", "options":"rw,relatime,errors=remount-ro,stripe=512",
         "children": [
            {"target":"/sys", "source":"sysfs", "fstype":"sysfs", "options":"rw,nosuid,nodev,noexec,relatime",
               "children": [
                  {"target":"/sys/kernel/security", "source":"securityfs", "fstype":"securityfs", "options":"rw,nosuid,nodev,noexec,relatime"},
                  {"target":"/sys/fs/cgroup", "source":"cgroup2", "fstype":"cgroup2", "options":"rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot"},
                  {"target":"/sys/fs/pstore", "source":"pstore", "fstype":"pstore", "options":"rw,nosuid,nodev,noexec,relatime"},
                  {"target":"/sys/fs/bpf", "source":"none", "fstype":"bpf", "options":"rw,nosuid,nodev,noexec,relatime,mode=700"},
                  {"target":"/sys/kernel/debug", "source":"debugfs", "fstype":"debugfs", "options":"rw,nosuid,nodev,noexec,relatime"},
                  {"target":"/sys/kernel/tracing", "source":"tracefs", "fstype":"tracefs", "options":"rw,nosuid,nodev,noexec,relatime"},
                  {"target":"/sys/fs/fuse/connections", "source":"fusectl", "fstype":"fusectl", "options":"rw,nosuid,nodev,noexec,relatime"},
                  {"target":"/sys/kernel/config", "source":"configfs", "fstype":"configfs", "options":"rw,nosuid,nodev,noexec,relatime"}
               ]
            },
            {"target":"/proc", "source":"proc", "fstype":"proc", "options":"rw,relatime",
               "children": [
                  {"target":"/proc/sys/fs/binfmt_misc", "source":"systemd-1", "fstype":"autofs", "options":"rw,relatime,fd=30,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=10400",
                     "children": [
                        {"target":"/proc/sys/fs/binfmt_misc", "source":"binfmt_misc", "fstype":"binfmt_misc", "options":"rw,nosuid,nodev,noexec,relatime"}
                     ]
                  }
               ]
            },
            {"target":"/dev", "source":"udev", "fstype":"devtmpfs", "options":"rw,nosuid,relatime,size=187620k,nr_inodes=46905,mode=755",
               "children": [
                  {"target":"/dev/pts", "source":"devpts", "fstype":"devpts", "options":"rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000"},
                  {"target":"/dev/shm", "source":"tmpfs", "fstype":"tmpfs", "options":"rw,nosuid,nodev"},
                  {"target":"/dev/mqueue", "source":"mqueue", "fstype":"mqueue", "options":"rw,nosuid,nodev,noexec,relatime"}
               ]
            },
            {"target":"/run", "source":"tmpfs", "fstype":"tmpfs", "options":"rw,nosuid,nodev,noexec,relatime,size=48276k,mode=755",
               "children": [
                  {"target":"/run/lock", "source":"tmpfs", "fstype":"tmpfs", "options":"rw,nosuid,nodev,noexec,relatime,size=5120k"},
                  {"target":"/run/user/1001", "source":"tmpfs", "fstype":"tmpfs", "options":"rw,nosuid,nodev,relatime,size=48272k,nr_inodes=12068,mode=700,uid=1001,gid=1001"}
               ]
            }
         ]
      }
   ]
}

And if you want to get the physical device for the root filesystem you can use jp / community.general.json_query to extract it like this:

findmnt -J | jp "filesystems[?target == '/'].source | [0]"
"/dev/xvda2"

Using Ansible:

- name: findmnt
  ansible.builtin.command: findmnt -J
  check_mode: false
  changed_when: false
  register: findmnt_j

- name: set fact
  ansible.builtin.set_fact:
    findmnt: "{{ findmnt_j.stdout | from_json }}"

- name: set fact
  ansible.builtin.set_fact:
    device: "{{ findmnt | community.general.json_query(findmnt_query) }}"
  vars:
    findmnt_query: "filesystems[?target == '/'].source | [0]"

JSON Convert

JSON Convert, or jc is a CLI tool for converting file formats and the output of many commands into JSON and YAML, it is also available as an Ansible filter and it can be installed using this Ansible role.

JC has a lot of parsers and the JC developer is open to adding new ones, one I suggested was the update-alternatives --query parser, for example this text output (which lists the versions of PHP installed and their priority):

update-alternatives --query php
Name: php
Link: /usr/bin/php
Slaves:
 php.1.gz /usr/share/man/man1/php.1.gz
Status: manual
Best: /usr/bin/php8.1
Value: /usr/bin/php7.4

Alternative: /usr/bin/php7.4
Priority: 74
Slaves:
 php.1.gz /usr/share/man/man1/php7.4.1.gz

Alternative: /usr/bin/php8.0
Priority: 80
Slaves:
 php.1.gz /usr/share/man/man1/php8.0.1.gz

Alternative: /usr/bin/php8.1
Priority: 81
Slaves:
 php.1.gz /usr/share/man/man1/php8.1.1.gz

Can be piped through jc:

update-alternatives --query php | jc --update-alt-q -py

To generate YAML like this:

---
name: php
link: /usr/bin/php
slaves:
- name: php.1.gz
  path: /usr/share/man/man1/php.1.gz
status: manual
best: /usr/bin/php8.1
value: /usr/bin/php7.4
alternatives:
- alternative: /usr/bin/php7.4
  priority: 74
  slaves:
  - name: php.1.gz
    path: /usr/share/man/man1/php7.4.1.gz
- alternative: /usr/bin/php8.0
  priority: 80
  slaves:
  - name: php.1.gz
    path: /usr/share/man/man1/php8.0.1.gz
- alternative: /usr/bin/php8.1
  priority: 81
  slaves:
  - name: php.1.gz
    path: /usr/share/man/man1/php8.1.1.gz

The jc --update-alt-q parser has been incorporated into this alternatives role and this role also uses jo to generate JSON, which is something I intend to post about below when I get around to it…

See the Parsing Command Output in Ansible with JC article for some more on using the community.general.jc Ansible filter.

I’ve several other things to add to this thread but that will do for today…

2 Likes