Parse virsh output

2.1k views Asked by At

Is there a way to force virsh to print information in a parseable way? like json?

I want to write a one-liner shell command that gets the IP address of a VM but the way virsh prints it out is not very friendly to scripts:

# virsh domifaddr myvm
 Name       MAC address          Protocol     Address
-------------------------------------------------------------------------------
 vnet1      52:54:00:b9:58:64    ipv4         192.168.130.156/24

I'm looking for a way to force it to not print the headers at least so I can get '192.168.130.156' from the output easily

This is the best I could do:

# virsh -q domifaddr myvm | awk '{print $4}' | cut -d/ -f 1
192.168.130.156
2

There are 2 answers

0
Ali haider On

One option is to install qemu-guest-agent on the domains you would like to extract IP information from.

From there, you can execute the following command on the host to get a detailed network interface listing in JSON:

ubuntu@host:~$ virsh qemu-agent-command my-guest '{"execute":"guest-network-get-interfaces"}'
{"return":[{"name":"lo","ip-addresses":[{"ip-address-type":"ipv4","ip-address":"127.0.0.1","prefix":8},{"ip-address-type":"ipv6","ip-address":"::1","prefix":128}],"statistics":{"tx-packets":22,"tx-errs":0,"rx-bytes":2816,"rx-dropped":0,"rx-packets":22,"rx-errs":0,"tx-bytes":2816,"tx-dropped":0},"hardware-address":"00:00:00:00:00:00"},{"name":"eth0","ip-addresses":[{"ip-address-type":"ipv4","ip-address":"1.2.3.4","prefix":22},{"ip-address-type":"ipv6","ip-address":"abcd::1234:ee:ab12:e31d","prefix":64}],"statistics":{"tx-packets":11231,"tx-errs":0,"rx-bytes":40717370,"rx-dropped":0,"rx-packets":19744,"rx-errs":0,"tx-bytes":890354,"tx-dropped":0},"hardware-address":"01:02:00:03:04:05"}]}

Your json can be parsed however you'd like from there.

0
a-h On

I ran into this today, so I wrote a quick program to do it.

https://github.com/a-h/virshjson

The output tables are fairly easy to parse, by looking for the header which contains the column names, and noting the positions, skipping the line of hyphens which is output, and then using the stored positions to extract fields.

package virshjson

import (
    "bufio"
    "errors"
    "io"
    "regexp"
    "strings"
)

var ErrMalformedHeader = errors.New("malformed input header")
var ErrMalformedSeparator = errors.New("malformed input separator")
var ErrMalformedBody = errors.New("malformed input body")

type field struct {
    name  string
    start int
}

var fieldsRegexp = regexp.MustCompile(`.+?(\s{2,}|$)`)

func getFields(s string) (fields []field) {
    matches := fieldsRegexp.FindAllStringIndex(s, -1)
    for _, match := range matches {
        start, end := match[0], match[1]
        fields = append(fields, field{
            name:  strings.TrimSpace(s[start:end]),
            start: start,
        })
    }
    return fields
}

var separatorRegexp = regexp.MustCompile(`^\-+$`)

func Convert(input io.Reader) ([]map[string]any, error) {
    scanner := bufio.NewScanner(input)
    // Read headers.
    scanner.Scan()
    if scanner.Err() != nil {
        return nil, scanner.Err()
    }
    fields := getFields(scanner.Text())
    if len(fields) == 0 {
        return nil, ErrMalformedHeader
    }

    // Read the line of hyphens.
    scanner.Scan()
    if scanner.Err() != nil {
        return nil, scanner.Err()
    }
    if !separatorRegexp.MatchString(scanner.Text()) {
        return nil, ErrMalformedSeparator
    }

    // Read lines until an empty one.
    data := make([]map[string]any, 0)
    for scanner.Scan() {
        if scanner.Err() != nil {
            return nil, scanner.Err()
        }
        if len(scanner.Text()) == 0 {
            continue
        }
        value := make(map[string]any)
        for i, field := range fields {
            end := len(scanner.Text())
            if i < len(fields)-1 {
                end = fields[i+1].start
            }
            value[field.name] = strings.TrimSpace(scanner.Text()[field.start:end])
        }
        data = append(data, value)
    }

    return data, nil
}

Given input:

 Name       MAC address          Protocol     Address
-------------------------------------------------------------------------------
 vnet1      52:54:00:c9:ae:a5    ipv4         192.168.122.110/24

The tool outputs:

[
 {
  "Address": "192.168.122.110/24",
  "MAC address": "52:54:00:c9:ae:a5",
  "Name": "vnet1",
  "Protocol": "ipv4"
 }
]