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>
2017-07-13 13:11:36 +00:00
|
|
|
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()
|
2017-07-24 15:14:18 +00:00
|
|
|
images, err := store.Images()
|
|
|
|
if err != nil {
|
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>
2017-07-13 13:11:36 +00:00
|
|
|
info["ImageStore"] = infoErr(err)
|
|
|
|
} else {
|
2017-07-24 15:14:18 +00:00
|
|
|
info["ImageStore"] = map[string]interface{}{
|
|
|
|
"number": len(images),
|
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>
2017-07-13 13:11:36 +00:00
|
|
|
}
|
|
|
|
}
|
2017-07-24 15:14:18 +00:00
|
|
|
containers, err := store.Containers()
|
|
|
|
if err != nil {
|
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>
2017-07-13 13:11:36 +00:00
|
|
|
info["ContainerStore"] = infoErr(err)
|
|
|
|
} else {
|
2017-07-24 15:14:18 +00:00
|
|
|
info["ContainerStore"] = map[string]interface{}{
|
|
|
|
"number": len(containers),
|
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>
2017-07-13 13:11:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|