package node import ( "fmt" "io" "sort" "strings" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command/inspect" "github.com/docker/docker/pkg/ioutils" "github.com/docker/go-units" "github.com/spf13/cobra" "golang.org/x/net/context" ) type inspectOptions struct { nodeIds []string format string pretty bool } func newInspectCommand(dockerCli command.Cli) *cobra.Command { var opts inspectOptions cmd := &cobra.Command{ Use: "inspect [OPTIONS] self|NODE [NODE...]", Short: "Display detailed information on one or more nodes", Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { opts.nodeIds = args return runInspect(dockerCli, opts) }, } flags := cmd.Flags() flags.StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template") flags.BoolVar(&opts.pretty, "pretty", false, "Print the information in a human friendly format") return cmd } func runInspect(dockerCli command.Cli, opts inspectOptions) error { client := dockerCli.Client() ctx := context.Background() getRef := func(ref string) (interface{}, []byte, error) { nodeRef, err := Reference(ctx, client, ref) if err != nil { return nil, nil, err } node, _, err := client.NodeInspectWithRaw(ctx, nodeRef) return node, nil, err } if !opts.pretty { return inspect.Inspect(dockerCli.Out(), opts.nodeIds, opts.format, getRef) } return printHumanFriendly(dockerCli.Out(), opts.nodeIds, getRef) } func printHumanFriendly(out io.Writer, refs []string, getRef inspect.GetRefFunc) error { for idx, ref := range refs { obj, _, err := getRef(ref) if err != nil { return err } printNode(out, obj.(swarm.Node)) // TODO: better way to do this? // print extra space between objects, but not after the last one if idx+1 != len(refs) { fmt.Fprintf(out, "\n\n") } else { fmt.Fprintf(out, "\n") } } return nil } // TODO: use a template func printNode(out io.Writer, node swarm.Node) { fmt.Fprintf(out, "ID:\t\t\t%s\n", node.ID) ioutils.FprintfIfNotEmpty(out, "Name:\t\t\t%s\n", node.Spec.Name) if node.Spec.Labels != nil { fmt.Fprintln(out, "Labels:") for k, v := range node.Spec.Labels { fmt.Fprintf(out, " - %s = %s\n", k, v) } } ioutils.FprintfIfNotEmpty(out, "Hostname:\t\t%s\n", node.Description.Hostname) fmt.Fprintf(out, "Joined at:\t\t%s\n", command.PrettyPrint(node.CreatedAt)) fmt.Fprintln(out, "Status:") fmt.Fprintf(out, " State:\t\t\t%s\n", command.PrettyPrint(node.Status.State)) ioutils.FprintfIfNotEmpty(out, " Message:\t\t%s\n", command.PrettyPrint(node.Status.Message)) fmt.Fprintf(out, " Availability:\t\t%s\n", command.PrettyPrint(node.Spec.Availability)) ioutils.FprintfIfNotEmpty(out, " Address:\t\t%s\n", command.PrettyPrint(node.Status.Addr)) if node.ManagerStatus != nil { fmt.Fprintln(out, "Manager Status:") fmt.Fprintf(out, " Address:\t\t%s\n", node.ManagerStatus.Addr) fmt.Fprintf(out, " Raft Status:\t\t%s\n", command.PrettyPrint(node.ManagerStatus.Reachability)) leader := "No" if node.ManagerStatus.Leader { leader = "Yes" } fmt.Fprintf(out, " Leader:\t\t%s\n", leader) } fmt.Fprintln(out, "Platform:") fmt.Fprintf(out, " Operating System:\t%s\n", node.Description.Platform.OS) fmt.Fprintf(out, " Architecture:\t\t%s\n", node.Description.Platform.Architecture) fmt.Fprintln(out, "Resources:") fmt.Fprintf(out, " CPUs:\t\t\t%d\n", node.Description.Resources.NanoCPUs/1e9) fmt.Fprintf(out, " Memory:\t\t%s\n", units.BytesSize(float64(node.Description.Resources.MemoryBytes))) var pluginTypes []string pluginNamesByType := map[string][]string{} for _, p := range node.Description.Engine.Plugins { // append to pluginTypes only if not done previously if _, ok := pluginNamesByType[p.Type]; !ok { pluginTypes = append(pluginTypes, p.Type) } pluginNamesByType[p.Type] = append(pluginNamesByType[p.Type], p.Name) } if len(pluginTypes) > 0 { fmt.Fprintln(out, "Plugins:") sort.Strings(pluginTypes) // ensure stable output for _, pluginType := range pluginTypes { fmt.Fprintf(out, " %s:\t\t%s\n", pluginType, strings.Join(pluginNamesByType[pluginType], ", ")) } } fmt.Fprintf(out, "Engine Version:\t\t%s\n", node.Description.Engine.EngineVersion) if len(node.Description.Engine.Labels) != 0 { fmt.Fprintln(out, "Engine Labels:") for k, v := range node.Description.Engine.Labels { fmt.Fprintf(out, " - %s = %s\n", k, v) } } }