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:
parent
3f70873aab
commit
ab36ad50be
4 changed files with 240 additions and 0 deletions
200
cmd/kpod/info.go
Normal file
200
cmd/kpod/info.go
Normal 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
|
||||
}
|
|
@ -23,6 +23,7 @@ func main() {
|
|||
|
||||
app.Commands = []cli.Command{
|
||||
imagesCommand,
|
||||
infoCommand,
|
||||
rmiCommand,
|
||||
tagCommand,
|
||||
versionCommand,
|
||||
|
|
36
docs/kpod-info.1.md
Normal file
36
docs/kpod-info.1.md
Normal 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)
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in a new issue