Part of a series: Install · Configure · DHCP · Commission · Deploy · jq · SSH · More jq
The JSON output from MAAS CLI commands is both consistent and comprehensive, but hard for humans to process — especially at scale. Even one machine’s allocate or deploy output runs many pages. With 10 or 12 machines, it becomes unworkable.
Enter jq: a command-line tool for filtering and formatting JSON. With jq and a couple of Unix utilities, you can turn that wall of JSON into something like this:
HOSTNAME SYSID POWER STATUS OWNER TAGS POOL VLAN FABRIC SUBNET
-------- ----- ----- ------ ----- ---- ---- ---- ------ ------
lxd-vm-1 r8d6yp off Deployed admin pod-console-logging default untagged fabric-1 10.124.141.0/24
lxd-vm-2 tfftrx off Allocated admin pod-console-logging default untagged fabric-1 10.124.141.0/24
lxd-vm-3 grwpwc off Ready - pod-console-logging default untagged fabric-1 10.124.141.0/24
lxd-vm-4 6s8dt4 off Deployed admin pod-console-logging default untagged fabric-1 10.124.141.0/24
lxd-vm-5 pyebgm off Allocated admin pod-console-logging default untagged fabric-1 10.124.141.0/24
lxd-vm-6 ebnww6 off New - pod-console-logging default untagged fabric-1
libvirt-vm-1 m7ffsg off Ready - pod-console-logging default untagged fabric-1 10.124.141.0/24
libvirt-vm-2 kpawad off Ready - pod-console-logging default untagged fabric-1 10.124.141.0/24
libvirt-vm-3 r44hr6 error Ready - pod-console-logging default untagged fabric-1 10.124.141.0/24
libvirt-vm-4 s3sdkw off Ready - pod-console-logging default untagged fabric-1 10.124.141.0/24
libvirt-vm-5 48dg8m off Ready - pod-console-logging default untagged fabric-1 10.124.141.0/24
libvirt-vm-6 bacx77 on Deployed admin pod-console-logging default untagged fabric-1 10.124.141.0/24
The command that produces this covers 99% of routine MAAS information needs. Let’s build it from scratch.
Watch it live
This material comes from a live Canonical show-and-tell session. If you’d rather watch than read:
MAAS Show and Tell: Using jq to make human-readable MAAS CLI output (Canonical MAAS Discourse, January 2021)
Basic jq usage
Pull hostnames from all machines, no formatting:
stormrider@wintermute:~$ maas admin machines read | jq '(.[] | [.hostname])'
Output:
[
"lxd-vm-1"
]
[
"lxd-vm-2"
]
...
Two things to note. First, jq instructions are enclosed in single quotes — they can span lines without line continuations:
maas admin machines read | jq '(.[]
| [.hostname])'
Second, .[] tells jq it’s decoding an array of datasets and should iterate through each one. The pipe (|) completes the “for each” construct. The outer brackets in the output represent each machine’s dataset boundary.
Adding a second field is just as simple:
stormrider@wintermute:~$ maas admin machines read | \
jq '(.[] | [.hostname, .status_name])'
Output:
[
"lxd-vm-1",
"Deployed"
]
[
"lxd-vm-2",
"Allocated"
]
...
Improved formatting
Most Unix text-processing commands use tabs as field delimiters. The jq @tsv filter transforms output records into tab-separated lines:
maas admin machines read | jq '(.[] | [.hostname, .status_name]) | @tsv'
Output:
"lxd-vm-1\tDeployed"
"lxd-vm-2\tAllocated"
...
Close, but the quotes and \t representations aren’t human-readable. The -r (raw output) flag fixes that:
maas admin machines read | jq -r '(.[] | [.hostname, .status_name]) | @tsv'
Output:
lxd-vm-1 Deployed
lxd-vm-2 Allocated
...
Pipe to column -t to normalize column widths to the longest value in each column:
maas admin machines read | jq -r \
'(.[] | [.hostname, .status_name]) | @tsv' | column -t
Adding headers
Pass a literal row to jq as the first expression:
maas admin machines read | jq -r \
'(["HOSTNAME","STATUS"]),
(.[] | [.hostname, .status_name]) | @tsv' | column -t
Output:
HOSTNAME STATUS
lxd-vm-1 Deployed
lxd-vm-2 Allocated
...
Add a horizontal rule using map(length*"-"):
maas admin machines read | jq -r \
'(["HOSTNAME","STATUS"] | (.,map(length*"-"))),
(.[] | [.hostname, .status_name]) | @tsv' | column -t
Output:
HOSTNAME STATUS
-------- ------
lxd-vm-1 Deployed
...
Handling null values
Only allocated or deployed machines have an owner. Unowned machines return null, which breaks column alignment. The jq alternate-value construct a // "b" means “if not a, use b”:
maas admin machines read | jq -r \
'(["HOSTNAME","STATUS","OWNER","SYSTEM-ID"] | (.,map(length*"-"))),
(.[] | [.hostname, .status_name, .owner // "-", .system_id])
| @tsv' | column -t
Output:
HOSTNAME STATUS OWNER SYSTEM-ID
-------- ------ ----- ---------
lxd-vm-1 Deployed admin r8d6yp
lxd-vm-3 Ready - grwpwc
...
Nested arrays
Tags are stored as a nested array:
"tag_names": [
"pod-console-logging",
"virtual"
],
Use tag_names[0] to get the first tag:
.tag_names[0] // "-"
Note: use underscores in column headers to prevent column -t from splitting on spaces — FIRST_TAG not FIRST TAG.
Nested keys
Some values aren’t top-level key-value pairs. The pool, for example:
"pool": {
"name": "default",
"description": "Default pool",
"id": 0,
...
}
Asking for .pool directly causes an error. Use dot notation to reach the actual value:
.pool.name
Doubly-nested keys
VLAN and fabric names are nested inside boot_interface:
"boot_interface": {
"vlan": {
"name": "untagged",
"fabric": "fabric-1",
...
}
}
Access them with double indirection:
.boot_interface.vlan.name
.boot_interface.vlan.fabric
Deeply nested values
The subnet CIDR is buried inside boot_interface.links[]:
"boot_interface": {
"links": [
{
"subnet": {
"name": "10.124.141.0/24",
...
}
}
]
}
Access the first element of the links array:
.boot_interface.links[0].subnet.name
The complete table command
Putting it all together:
maas admin machines read | jq -r '(["HOSTNAME","SYSID","POWER","STATUS",
"OWNER", "TAGS", "POOL", "VLAN","FABRIC","SUBNET"] | (., map(length*"-"))),
(.[] | [.hostname, .system_id, .power_state, .status_name, .owner // "-",
.tag_names[0] // "-", .pool.name,
.boot_interface.vlan.name, .boot_interface.vlan.fabric,
.boot_interface.links[0].subnet.name]) | @tsv' | column -t
Sorting with awk
To sort by hostname while keeping headers pinned at the top, use awk to sort only rows after the first two:
maas admin machines read | jq -r '(["HOSTNAME","SYSID","POWER","STATUS","OWNER",
"TAGS", "POOL", "VLAN","FABRIC","SUBNET"] | (., map(length*"-"))),(.[] |
[.hostname, .system_id, .power_state, .status_name, .owner // "-",
.tag_names[0] // "-", .pool.name, .boot_interface.vlan.name,
.boot_interface.vlan.fabric,.boot_interface.links[0].subnet.name])
| @tsv' | column -t | awk 'NR<3{print $0;next}{print $0| "sort -k 1"}'
Change -k 1 to -k 4 to sort by status, -k 5 for owner, and so on.
jq is a simple but powerful tool for working with MAAS CLI output. And since it outputs plain text, anything you can do with text output in Unix, you can do with jq results. Next up: SSH and SCP with deployed machines.