Vincent Batts
ab36ad50be
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>
200 lines
4.5 KiB
Go
200 lines
4.5 KiB
Go
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
|
|
}
|