diff --git a/cli/command/formatter/node.go b/cli/command/formatter/node.go index 6a6fb43c1a..4d7f293f5c 100644 --- a/cli/command/formatter/node.go +++ b/cli/command/formatter/node.go @@ -1,14 +1,67 @@ package formatter import ( + "fmt" + "strings" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/cli/command" + "github.com/docker/docker/cli/command/inspect" + units "github.com/docker/go-units" ) const ( - defaultNodeTableFormat = "table {{.ID}} {{if .Self}}*{{else}} {{ end }}\t{{.Hostname}}\t{{.Status}}\t{{.Availability}}\t{{.ManagerStatus}}" - + defaultNodeTableFormat = "table {{.ID}} {{if .Self}}*{{else}} {{ end }}\t{{.Hostname}}\t{{.Status}}\t{{.Availability}}\t{{.ManagerStatus}}" + nodeInspectPrettyTemplate Format = `ID: {{.ID}} +{{- if .Name }} +Name: {{.Name}} +{{- end }} +{{- if .Labels }} +Labels: +{{- range $k, $v := .Labels }} + - {{ $k }}{{if $v }}={{ $v }}{{ end }} +{{- end }}{{ end }} +Hostname: {{.Hostname}} +Joined at: {{.CreatedAt}} +Status: + State: {{.StatusState}} + {{- if .HasStatusMessage}} + Message: {{.StatusMessage}} + {{- end}} + Availability: {{.SpecAvailability}} + {{- if .Status.Addr}} + Address: {{.StatusAddr}} + {{- end}} +{{- if .HasManagerStatus}} +Manager Status: + Address: {{.ManagerStatusAddr}} + Raft Status: {{.ManagerStatusReachability}} + {{- if .IsManagerStatusLeader}} + Leader: Yes + {{- else}} + Leader: No + {{- end}} +{{- end}} +Platform: + Operating System: {{.PlatformOS}} + Architecture: {{.PlatformArchitecture}} +Resources: + CPUs: {{.ResourceNanoCPUs}} + Memory: {{.ResourceMemory}} +{{- if .HasEnginePlugins}} +Plugins: +{{- range $k, $v := .EnginePlugins }} + {{ $k }}:{{if $v }} {{ $v }}{{ end }} +{{- end }} +{{- end }} +Engine Version: {{.EngineVersion}} +{{- if .EngineLabels}} +Engine Labels: +{{- range $k, $v := .EngineLabels }} + - {{ $k }}{{if $v }}={{ $v }}{{ end }} +{{- end }}{{- end }} +` nodeIDHeader = "ID" selfHeader = "" hostnameHeader = "HOSTNAME" @@ -19,6 +72,8 @@ const ( // NewNodeFormat returns a Format for rendering using a node Context func NewNodeFormat(source string, quiet bool) Format { switch source { + case PrettyFormatKey: + return nodeInspectPrettyTemplate case TableFormatKey: if quiet { return defaultQuietFormat @@ -99,3 +154,139 @@ func (c *nodeContext) ManagerStatus() string { } return command.PrettyPrint(reachability) } + +// NodeInspectWrite renders the context for a list of services +func NodeInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) error { + if ctx.Format != nodeInspectPrettyTemplate { + return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef) + } + render := func(format func(subContext subContext) error) error { + for _, ref := range refs { + nodeI, _, err := getRef(ref) + if err != nil { + return err + } + node, ok := nodeI.(swarm.Node) + if !ok { + return fmt.Errorf("got wrong object to inspect :%v", ok) + } + if err := format(&nodeInspectContext{Node: node}); err != nil { + return err + } + } + return nil + } + return ctx.Write(&nodeInspectContext{}, render) +} + +type nodeInspectContext struct { + swarm.Node + subContext +} + +func (ctx *nodeInspectContext) ID() string { + return ctx.Node.ID +} + +func (ctx *nodeInspectContext) Name() string { + return ctx.Node.Spec.Name +} + +func (ctx *nodeInspectContext) Labels() map[string]string { + return ctx.Node.Spec.Labels +} + +func (ctx *nodeInspectContext) Hostname() string { + return ctx.Node.Description.Hostname +} + +func (ctx *nodeInspectContext) CreatedAt() string { + return command.PrettyPrint(ctx.Node.CreatedAt) +} + +func (ctx *nodeInspectContext) StatusState() string { + return command.PrettyPrint(ctx.Node.Status.State) +} + +func (ctx *nodeInspectContext) HasStatusMessage() bool { + return ctx.Node.Status.Message != "" +} + +func (ctx *nodeInspectContext) StatusMessage() string { + return command.PrettyPrint(ctx.Node.Status.Message) +} + +func (ctx *nodeInspectContext) SpecAvailability() string { + return command.PrettyPrint(ctx.Node.Spec.Availability) +} + +func (ctx *nodeInspectContext) HasStatusAddr() bool { + return ctx.Node.Status.Addr != "" +} + +func (ctx *nodeInspectContext) StatusAddr() string { + return ctx.Node.Status.Addr +} + +func (ctx *nodeInspectContext) HasManagerStatus() bool { + return ctx.Node.ManagerStatus != nil +} + +func (ctx *nodeInspectContext) ManagerStatusAddr() string { + return ctx.Node.ManagerStatus.Addr +} + +func (ctx *nodeInspectContext) ManagerStatusReachability() string { + return command.PrettyPrint(ctx.Node.ManagerStatus.Reachability) +} + +func (ctx *nodeInspectContext) IsManagerStatusLeader() bool { + return ctx.Node.ManagerStatus.Leader +} + +func (ctx *nodeInspectContext) PlatformOS() string { + return ctx.Node.Description.Platform.OS +} + +func (ctx *nodeInspectContext) PlatformArchitecture() string { + return ctx.Node.Description.Platform.Architecture +} + +func (ctx *nodeInspectContext) ResourceNanoCPUs() int { + if ctx.Node.Description.Resources.NanoCPUs == 0 { + return int(0) + } + return int(ctx.Node.Description.Resources.NanoCPUs) / 1e9 +} + +func (ctx *nodeInspectContext) ResourceMemory() string { + if ctx.Node.Description.Resources.MemoryBytes == 0 { + return "" + } + return units.BytesSize(float64(ctx.Node.Description.Resources.MemoryBytes)) +} + +func (ctx *nodeInspectContext) HasEnginePlugins() bool { + return len(ctx.Node.Description.Engine.Plugins) > 0 +} + +func (ctx *nodeInspectContext) EnginePlugins() map[string]string { + pluginMap := map[string][]string{} + for _, p := range ctx.Node.Description.Engine.Plugins { + pluginMap[p.Type] = append(pluginMap[p.Type], p.Name) + } + + pluginNamesByType := map[string]string{} + for k, v := range pluginMap { + pluginNamesByType[k] = strings.Join(v, ", ") + } + return pluginNamesByType +} + +func (ctx *nodeInspectContext) EngineLabels() map[string]string { + return ctx.Node.Description.Engine.Labels +} + +func (ctx *nodeInspectContext) EngineVersion() string { + return ctx.Node.Description.Engine.EngineVersion +} diff --git a/cli/command/node/inspect.go b/cli/command/node/inspect.go index a08497003d..39b90bb72e 100644 --- a/cli/command/node/inspect.go +++ b/cli/command/node/inspect.go @@ -2,16 +2,11 @@ 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/docker/docker/cli/command/formatter" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -44,6 +39,11 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command { func runInspect(dockerCli command.Cli, opts inspectOptions) error { client := dockerCli.Client() ctx := context.Background() + + if opts.pretty { + opts.format = "pretty" + } + getRef := func(ref string) (interface{}, []byte, error) { nodeRef, err := Reference(ctx, client, ref) if err != nil { @@ -52,93 +52,21 @@ func runInspect(dockerCli command.Cli, opts inspectOptions) error { node, _, err := client.NodeInspectWithRaw(ctx, nodeRef) return node, nil, err } + f := opts.format - if !opts.pretty { - return inspect.Inspect(dockerCli.Out(), opts.nodeIds, opts.format, getRef) + // check if the user is trying to apply a template to the pretty format, which + // is not supported + if strings.HasPrefix(f, "pretty") && f != "pretty" { + return fmt.Errorf("Cannot supply extra formatting options to the pretty template") } - 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)) + nodeCtx := formatter.Context{ + Output: dockerCli.Out(), + Format: formatter.NewNodeFormat(f, false), + } - // 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") - } + if err := formatter.NodeInspectWrite(nodeCtx, opts.nodeIds, getRef); err != nil { + return cli.StatusError{StatusCode: 1, Status: err.Error()} } 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) - } - } -}