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)