From ab36ad50bee6146d44d5a37f3275c75cbe61f61f Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Thu, 13 Jul 2017 09:11:36 -0400 Subject: [PATCH] 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 --- cmd/kpod/info.go | 200 ++++++++++++++++++++++++++++++++++++++++++++ cmd/kpod/main.go | 1 + docs/kpod-info.1.md | 36 ++++++++ docs/kpod.1.md | 3 + 4 files changed, 240 insertions(+) create mode 100644 cmd/kpod/info.go create mode 100644 docs/kpod-info.1.md diff --git a/cmd/kpod/info.go b/cmd/kpod/info.go new file mode 100644 index 00000000..5a30c0ff --- /dev/null +++ b/cmd/kpod/info.go @@ -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 +} diff --git a/cmd/kpod/main.go b/cmd/kpod/main.go index 7997a6ce..bc5845fd 100644 --- a/cmd/kpod/main.go +++ b/cmd/kpod/main.go @@ -23,6 +23,7 @@ func main() { app.Commands = []cli.Command{ imagesCommand, + infoCommand, rmiCommand, tagCommand, versionCommand, diff --git a/docs/kpod-info.1.md b/docs/kpod-info.1.md new file mode 100644 index 00000000..b2cc16f4 --- /dev/null +++ b/docs/kpod-info.1.md @@ -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) diff --git a/docs/kpod.1.md b/docs/kpod.1.md index 6e6dd8db..b7025bea 100644 --- a/docs/kpod.1.md +++ b/docs/kpod.1.md @@ -38,6 +38,9 @@ Removes one or more locally stored images ### tag Add one or more additional names to locally-stored image +### info +Displays system information + ## SEE ALSO crio(8), crio.conf(5)