Browse Source

Merge pull request #32116 from mkumatag/inspect_template

Adopt text/template in node inspect
Sebastiaan van Stijn 8 years ago
parent
commit
ec207ce186
2 changed files with 210 additions and 91 deletions
  1. 193 2
      cli/command/formatter/node.go
  2. 17 89
      cli/command/node/inspect.go

+ 193 - 2
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
+}

+ 17 - 89
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))
 
-		// 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")
-		}
+	nodeCtx := formatter.Context{
+		Output: dockerCli.Out(),
+		Format: formatter.NewNodeFormat(f, false),
 	}
-	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)
-		}
+	if err := formatter.NodeInspectWrite(nodeCtx, opts.nodeIds, getRef); err != nil {
+		return cli.StatusError{StatusCode: 1, Status: err.Error()}
 	}
+	return nil
 }