Jeff Saremi
Jeff Saremi

Reputation: 3024

Parse virsh output

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

Upvotes: 3

Views: 2887

Answers (3)

maxschlepzig
maxschlepzig

Reputation: 39195

The virsh domifaddr command doesn't provide a machine readable output mode, unfortunately.

However, you can use the libvirt Python API for a one-liner that gets the IP address of your guest:

python -c 'import sys; import libvirt; con = libvirt.open("qemu:///system"); dom = con.lookupByName(sys.argv[1]); print(dom.interfaceAddresses(libvirt.VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE).popitem()[1]["addrs"][-1]["addr"])' myvm

It basically a light version of virsh domifaddr.

NB: The libvirt Python package likely is install on a system where libvirt is available, because virt-install and virt-manager depend on it. (at least on Fedora systems)

Upvotes: 0

a-h
a-h

Reputation: 4284

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"
 }
]

Upvotes: 0

Ali haider
Ali haider

Reputation: 41

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.

Upvotes: 2

Related Questions