kpod: info subcommand

Design: The output of the `info` subcommand ought to be directly
consumable in a format like JSON or yaml.
The structure being a map of sorts.

Each subsection of information being an individual cluster under the
top-level, like platform info, debug, storage, etc.

Even if there are errors under the top level key, the value will be a
map with the key of "error" and the value as the message of the
`err.Error()`. In this way, the command always returns usable output.

Ideally there will be a means for anything that can register info to do
so independently from it being in the single info.go, so this approach
is having a typed signature for the function that gives info, but i'm
sure it could be better.

Current iteration of this outputs the following as a limited user:

```yaml
host:
  MemFree: 711307264
  MemTotal: 2096222208
  SwapFree: 2147479552
  SwapTotal: 2147479552
  arch: amd64
  cpus: 1
  os: linux
store:
  error: 'mkdir /var/run/containers/storage: permission denied'

```

and as root (`sudo kpod info -D`):

```yaml
debug:
  compiler: gc
  go version: go1.7.6
  goroutines: 3
host:
  MemFree: 717795328
  MemTotal: 2096222208
  SwapFree: 2147479552
  SwapTotal: 2147479552
  arch: amd64
  cpus: 1
  os: linux
store:
  ContainerStore:
    number: 1
  GraphDriverName: overlay2
  GraphRoot: /var/lib/containers/storage
  ImageStore:
    number: 1
```

And with the `--json --debug` flag:

```json
{
  "debug": {
    "compiler": "gc",
    "go version": "go1.7.6",
    "goroutines": 3
  },
  "host": {
    "MemFree": 709402624,
    "MemTotal": 2096222208,
    "SwapFree": 2147479552,
    "SwapTotal": 2147479552,
    "arch": "amd64",
    "cpus": 1,
    "os": "linux"
  },
  "store": {
    "ContainerStore": {
      "number": 1
    },
    "GraphDriverName": "overlay2",
    "GraphRoot": "/var/lib/containers/storage",
    "ImageStore": {
      "number": 1
    }
  }
}
```

Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
This commit is contained in:
Vincent Batts 2017-07-13 09:11:36 -04:00
parent 3f70873aab
commit ab36ad50be
Signed by: vbatts
GPG key ID: 10937E57733F1362
4 changed files with 240 additions and 0 deletions

200
cmd/kpod/info.go Normal file
View file

@ -0,0 +1,200 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"runtime"
"github.com/docker/docker/pkg/system"
"github.com/ghodss/yaml"
"github.com/urfave/cli"
)
var (
infoDescription = "display system information"
infoCommand = cli.Command{
Name: "info",
Usage: infoDescription,
Description: `Information display here pertain to the host, current storage stats, and build of kpod. Useful for the user and when reporting issues.`,
Flags: infoFlags,
Action: infoCmd,
ArgsUsage: "",
}
infoFlags = []cli.Flag{
cli.BoolFlag{
Name: "debug, D",
Usage: "display additional debug information",
},
cli.BoolFlag{
Name: "json",
Usage: "output as JSON instead of the default YAML",
},
}
)
func infoCmd(c *cli.Context) error {
info := map[string]interface{}{}
infoGivers := []infoGiverFunc{
storeInfo,
hostInfo,
}
if c.Bool("debug") {
infoGivers = append(infoGivers, debugInfo)
}
for _, giver := range infoGivers {
thisName, thisInfo, err := giver(c)
if err != nil {
info[thisName] = infoErr(err)
continue
}
info[thisName] = thisInfo
}
var buf []byte
var err error
if c.Bool("json") {
buf, err = json.MarshalIndent(info, "", " ")
} else {
buf, err = yaml.Marshal(info)
}
if err != nil {
return err
}
fmt.Println(string(buf))
return nil
}
func infoErr(err error) map[string]interface{} {
return map[string]interface{}{
"error": err.Error(),
}
}
type infoGiverFunc func(c *cli.Context) (name string, info map[string]interface{}, err error)
// top-level "debug" info
func debugInfo(c *cli.Context) (string, map[string]interface{}, error) {
info := map[string]interface{}{}
info["compiler"] = runtime.Compiler
info["go version"] = runtime.Version()
return "debug", info, nil
}
// top-level "host" info
func hostInfo(c *cli.Context) (string, map[string]interface{}, error) {
// lets say OS, arch, number of cpus, amount of memory, maybe os distribution/version, hostname, kernel version, uptime
info := map[string]interface{}{}
info["os"] = runtime.GOOS
info["arch"] = runtime.GOARCH
info["cpus"] = runtime.NumCPU()
mi, err := system.ReadMemInfo()
if err != nil {
info["meminfo"] = infoErr(err)
} else {
// TODO this might be a place for github.com/dustin/go-humanize
info["MemTotal"] = mi.MemTotal
info["MemFree"] = mi.MemFree
info["SwapTotal"] = mi.SwapTotal
info["SwapFree"] = mi.SwapFree
}
if kv, err := readKernelVersion(); err != nil {
info["kernel"] = infoErr(err)
} else {
info["kernel"] = kv
}
if up, err := readUptime(); err != nil {
info["uptime"] = infoErr(err)
} else {
info["uptime"] = up
}
if host, err := os.Hostname(); err != nil {
info["hostname"] = infoErr(err)
} else {
info["hostname"] = host
}
return "host", info, nil
}
// top-level "store" info
func storeInfo(c *cli.Context) (string, map[string]interface{}, error) {
store, err := getStore(c)
if err != nil {
return "store", nil, err
}
// lets say storage driver in use, number of images, number of containers
info := map[string]interface{}{}
info["GraphRoot"] = store.GraphRoot()
info["GraphDriverName"] = store.GraphDriverName()
if is, err := store.ImageStore(); err != nil {
info["ImageStore"] = infoErr(err)
} else {
images, err := is.Images()
if err != nil {
info["ImageStore"] = infoErr(err)
} else {
info["ImageStore"] = map[string]interface{}{
"number": len(images),
}
}
}
/* Oh this is in master on containers/storage, rebase later
if is, err := store.ROImageStores(); err != nil {
info["ROImageStore"] = infoErr(err)
} else {
images, err := is.Images()
if err != nil {
info["ROImageStore"] = infoErr(err)
} else {
info["ROImageStore"] = map[string]interface{}{
"number": len(images),
}
}
}
*/
if cs, err := store.ContainerStore(); err != nil {
info["ContainerStore"] = infoErr(err)
} else {
containers, err := cs.Containers()
if err != nil {
info["ContainerStore"] = infoErr(err)
} else {
info["ContainerStore"] = map[string]interface{}{
"number": len(containers),
}
}
}
return "store", info, nil
}
func readKernelVersion() (string, error) {
buf, err := ioutil.ReadFile("/proc/version")
if err != nil {
return "", err
}
f := bytes.Fields(buf)
if len(f) < 2 {
return string(bytes.TrimSpace(buf)), nil
}
return string(f[2]), nil
}
func readUptime() (string, error) {
buf, err := ioutil.ReadFile("/proc/uptime")
if err != nil {
return "", err
}
f := bytes.Fields(buf)
if len(f) < 1 {
return "", fmt.Errorf("invalid uptime")
}
return string(f[0]), nil
}

View file

@ -23,6 +23,7 @@ func main() {
app.Commands = []cli.Command{ app.Commands = []cli.Command{
imagesCommand, imagesCommand,
infoCommand,
rmiCommand, rmiCommand,
tagCommand, tagCommand,
versionCommand, versionCommand,

36
docs/kpod-info.1.md Normal file
View file

@ -0,0 +1,36 @@
% kpod(8) # kpod-version - Simple tool to view version information
% Vincent Batts
% JULY 2017
# NAME
kpod-info - Display System Information
# SYNOPSIS
**kpod** **info** [*options* [...]]
# DESCRIPTION
Information display here pertain to the host, current storage stats, and build of kpod. Useful for the user and when reporting issues.
## OPTIONS
**--debug, -D**
Show additional information
**--debug, -D**
Show additional information
## EXAMPLE
`kpod info`
`kpod info --debug --json | jq .host.kernel`
# SEE ALSO
crio(8), crio.conf(5)

View file

@ -38,6 +38,9 @@ Removes one or more locally stored images
### tag ### tag
Add one or more additional names to locally-stored image Add one or more additional names to locally-stored image
### info
Displays system information
## SEE ALSO ## SEE ALSO
crio(8), crio.conf(5) crio(8), crio.conf(5)