Browse Source

Merge pull request #25025 from cpuguy83/service_inspect_formatter

Add formatter for service inspect
Vincent Demeester 8 years ago
parent
commit
00615efced

+ 4 - 4
cli/command/formatter/formatter.go

@@ -11,11 +11,11 @@ import (
 	"github.com/docker/docker/utils/templates"
 	"github.com/docker/docker/utils/templates"
 )
 )
 
 
+// Format keys used to specify certain kinds of output formats
 const (
 const (
-	// TableFormatKey is the key used to format as a table
-	TableFormatKey = "table"
-	// RawFormatKey is the key used to format as raw JSON
-	RawFormatKey = "raw"
+	TableFormatKey  = "table"
+	RawFormatKey    = "raw"
+	PrettyFormatKey = "pretty"
 
 
 	defaultQuietFormat = "{{.ID}}"
 	defaultQuietFormat = "{{.ID}}"
 )
 )

+ 285 - 0
cli/command/formatter/service.go

@@ -0,0 +1,285 @@
+package formatter
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	mounttypes "github.com/docker/docker/api/types/mount"
+	"github.com/docker/docker/api/types/swarm"
+	"github.com/docker/docker/cli/command/inspect"
+	units "github.com/docker/go-units"
+)
+
+const serviceInspectPrettyTemplate Format = `
+ID:		{{.ID}}
+Name:		{{.Name}}
+{{- if .Labels }}
+Labels:
+{{- range $k, $v := .Labels }}
+ {{ $k }}{{if $v }}={{ $v }}{{ end }}
+{{- end }}{{ end }}
+Mode:
+{{- if .IsModeGlobal }}		Global
+{{- else }}		Replicated
+{{- if .ModeReplicatedReplicas }}
+ Replicas:	{{ .ModeReplicatedReplicas }}
+{{- end }}{{ end }}
+{{- if .HasUpdateStatus }}
+UpdateStatus:
+ State:		{{ .UpdateStatusState }}
+ Started:	{{ .UpdateStatusStarted }}
+{{- if .UpdateIsCompleted }}
+ Completed:	{{ .UpdateStatusCompleted }}
+{{- end }}
+ Message:	{{ .UpdateStatusMessage }}
+{{- end }}
+Placement:
+{{- if .TaskPlacementConstraints -}}
+ Contraints:	{{ .TaskPlacementConstraints }}
+{{- end }}
+{{- if .HasUpdateConfig }}
+UpdateConfig:
+ Parallelism:	{{ .UpdateParallelism }}
+{{- if .HasUpdateDelay -}}
+ Delay:		{{ .UpdateDelay }}
+{{- end }}
+ On failure:	{{ .UpdateOnFailure }}
+{{- end }}
+ContainerSpec:
+ Image:		{{ .ContainerImage }}
+{{- if .ContainerArgs }}
+ Args:		{{ range $arg := .ContainerArgs }}{{ $arg }} {{ end }}
+{{- end -}}
+{{- if .ContainerEnv }}
+ Env:		{{ range $env := .ContainerEnv }}{{ $env }} {{ end }}
+{{- end -}}
+{{- if .ContainerWorkDir }}
+ Dir:		{{ .ContainerWorkDir }}
+{{- end -}}
+{{- if .ContainerUser }}
+ User: {{ .ContainerUser }}
+{{- end }}
+{{- if .ContainerMounts }}
+Mounts:
+{{- end }}
+{{- range $mount := .ContainerMounts }}
+  Target = {{ $mount.Target }}
+   Source = {{ $mount.Source }}
+   ReadOnly = {{ $mount.ReadOnly }}
+   Type = {{ $mount.Type }}
+{{- end -}}
+{{- if .HasResources }}
+Resources:
+{{- if .HasResourceReservations }}
+ Reservations:
+{{- end }}
+{{- if gt .ResourceReservationNanoCPUs 0.0 }}
+  CPU:		{{ .ResourceReservationNanoCPUs }}
+{{- end }}
+{{- if .ResourceReservationMemory }}
+  Memory:	{{ .ResourceReservationMemory }}
+{{- end }}
+{{- if .HasResourceLimits }}
+ Limits:
+{{- end }}
+{{- if gt .ResourceLimitsNanoCPUs 0.0 }}
+  CPU:		{{ .ResourceLimitsNanoCPUs }}
+{{- end }}
+{{- if .ResourceLimitMemory }}
+  Memory:	{{ .ResourceLimitMemory }}
+{{- end }}{{ end }}
+{{- if .Networks }}
+Networks:
+{{- range $network := .Networks }} {{ $network }}{{ end }} {{ end }}
+{{- if .Ports }}
+Ports:
+{{- range $port := .Ports }}
+ PublishedPort {{ $port.PublishedPort }}
+  Protocol = {{ $port.Protocol }}
+  TargetPort = {{ $port.TargetPort }}
+{{- end }} {{ end -}}
+`
+
+// NewServiceFormat returns a Format for rendering using a Context
+func NewServiceFormat(source string) Format {
+	switch source {
+	case PrettyFormatKey:
+		return serviceInspectPrettyTemplate
+	default:
+		return Format(strings.TrimPrefix(source, RawFormatKey))
+	}
+}
+
+// ServiceInspectWrite renders the context for a list of services
+func ServiceInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) error {
+	if ctx.Format != serviceInspectPrettyTemplate {
+		return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef)
+	}
+	render := func(format func(subContext subContext) error) error {
+		for _, ref := range refs {
+			serviceI, _, err := getRef(ref)
+			if err != nil {
+				return err
+			}
+			service, ok := serviceI.(swarm.Service)
+			if !ok {
+				return fmt.Errorf("got wrong object to inspect")
+			}
+			if err := format(&serviceInspectContext{Service: service}); err != nil {
+				return err
+			}
+		}
+		return nil
+	}
+	return ctx.Write(&serviceInspectContext{}, render)
+}
+
+type serviceInspectContext struct {
+	swarm.Service
+	subContext
+}
+
+func (ctx *serviceInspectContext) ID() string {
+	return ctx.Service.ID
+}
+
+func (ctx *serviceInspectContext) Name() string {
+	return ctx.Service.Spec.Name
+}
+
+func (ctx *serviceInspectContext) Labels() map[string]string {
+	return ctx.Service.Spec.Labels
+}
+
+func (ctx *serviceInspectContext) IsModeGlobal() bool {
+	return ctx.Service.Spec.Mode.Global != nil
+}
+
+func (ctx *serviceInspectContext) ModeReplicatedReplicas() *uint64 {
+	return ctx.Service.Spec.Mode.Replicated.Replicas
+}
+
+func (ctx *serviceInspectContext) HasUpdateStatus() bool {
+	return ctx.Service.UpdateStatus.State != ""
+}
+
+func (ctx *serviceInspectContext) UpdateStatusState() swarm.UpdateState {
+	return ctx.Service.UpdateStatus.State
+}
+
+func (ctx *serviceInspectContext) UpdateStatusStarted() string {
+	return units.HumanDuration(time.Since(ctx.Service.UpdateStatus.StartedAt))
+}
+
+func (ctx *serviceInspectContext) UpdateIsCompleted() bool {
+	return ctx.Service.UpdateStatus.State == swarm.UpdateStateCompleted
+}
+
+func (ctx *serviceInspectContext) UpdateStatusCompleted() string {
+	return units.HumanDuration(time.Since(ctx.Service.UpdateStatus.CompletedAt))
+}
+
+func (ctx *serviceInspectContext) UpdateStatusMessage() string {
+	return ctx.Service.UpdateStatus.Message
+}
+
+func (ctx *serviceInspectContext) TaskPlacementConstraints() []string {
+	if ctx.Service.Spec.TaskTemplate.Placement != nil {
+		return ctx.Service.Spec.TaskTemplate.Placement.Constraints
+	}
+	return nil
+}
+
+func (ctx *serviceInspectContext) HasUpdateConfig() bool {
+	return ctx.Service.Spec.UpdateConfig != nil
+}
+
+func (ctx *serviceInspectContext) UpdateParallelism() uint64 {
+	return ctx.Service.Spec.UpdateConfig.Parallelism
+}
+
+func (ctx *serviceInspectContext) HasUpdateDelay() bool {
+	return ctx.Service.Spec.UpdateConfig.Delay.Nanoseconds() > 0
+}
+
+func (ctx *serviceInspectContext) UpdateDelay() time.Duration {
+	return ctx.Service.Spec.UpdateConfig.Delay
+}
+
+func (ctx *serviceInspectContext) UpdateOnFailure() string {
+	return ctx.Service.Spec.UpdateConfig.FailureAction
+}
+
+func (ctx *serviceInspectContext) ContainerImage() string {
+	return ctx.Service.Spec.TaskTemplate.ContainerSpec.Image
+}
+
+func (ctx *serviceInspectContext) ContainerArgs() []string {
+	return ctx.Service.Spec.TaskTemplate.ContainerSpec.Args
+}
+
+func (ctx *serviceInspectContext) ContainerEnv() []string {
+	return ctx.Service.Spec.TaskTemplate.ContainerSpec.Env
+}
+
+func (ctx *serviceInspectContext) ContainerWorkDir() string {
+	return ctx.Service.Spec.TaskTemplate.ContainerSpec.Dir
+}
+
+func (ctx *serviceInspectContext) ContainerUser() string {
+	return ctx.Service.Spec.TaskTemplate.ContainerSpec.User
+}
+
+func (ctx *serviceInspectContext) ContainerMounts() []mounttypes.Mount {
+	return ctx.Service.Spec.TaskTemplate.ContainerSpec.Mounts
+}
+
+func (ctx *serviceInspectContext) HasResources() bool {
+	return ctx.Service.Spec.TaskTemplate.Resources != nil
+}
+
+func (ctx *serviceInspectContext) HasResourceReservations() bool {
+	return ctx.Service.Spec.TaskTemplate.Resources.Reservations.NanoCPUs > 0 || ctx.Service.Spec.TaskTemplate.Resources.Reservations.MemoryBytes > 0
+}
+
+func (ctx *serviceInspectContext) ResourceReservationNanoCPUs() float64 {
+	if ctx.Service.Spec.TaskTemplate.Resources.Reservations.NanoCPUs == 0 {
+		return float64(0)
+	}
+	return float64(ctx.Service.Spec.TaskTemplate.Resources.Reservations.NanoCPUs) / 1e9
+}
+
+func (ctx *serviceInspectContext) ResourceReservationMemory() string {
+	if ctx.Service.Spec.TaskTemplate.Resources.Reservations.MemoryBytes == 0 {
+		return ""
+	}
+	return units.BytesSize(float64(ctx.Service.Spec.TaskTemplate.Resources.Reservations.MemoryBytes))
+}
+
+func (ctx *serviceInspectContext) HasResourceLimits() bool {
+	return ctx.Service.Spec.TaskTemplate.Resources.Limits.NanoCPUs > 0 || ctx.Service.Spec.TaskTemplate.Resources.Limits.MemoryBytes > 0
+}
+
+func (ctx *serviceInspectContext) ResourceLimitsNanoCPUs() float64 {
+	return float64(ctx.Service.Spec.TaskTemplate.Resources.Limits.NanoCPUs) / 1e9
+}
+
+func (ctx *serviceInspectContext) ResourceLimitMemory() string {
+	if ctx.Service.Spec.TaskTemplate.Resources.Limits.MemoryBytes == 0 {
+		return ""
+	}
+	return units.BytesSize(float64(ctx.Service.Spec.TaskTemplate.Resources.Limits.MemoryBytes))
+}
+
+func (ctx *serviceInspectContext) Networks() []string {
+	var out []string
+	for _, n := range ctx.Service.Spec.Networks {
+		out = append(out, n.Target)
+	}
+	return out
+}
+
+func (ctx *serviceInspectContext) Ports() []swarm.PortConfig {
+	return ctx.Service.Endpoint.Ports
+}

+ 20 - 124
cli/command/service/inspect.go

@@ -2,19 +2,14 @@ package service
 
 
 import (
 import (
 	"fmt"
 	"fmt"
-	"io"
 	"strings"
 	"strings"
-	"time"
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
-	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command"
-	"github.com/docker/docker/cli/command/inspect"
+	"github.com/docker/docker/cli/command/formatter"
 	apiclient "github.com/docker/docker/client"
 	apiclient "github.com/docker/docker/client"
-	"github.com/docker/docker/pkg/ioutils"
-	"github.com/docker/go-units"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
@@ -51,6 +46,10 @@ func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
 	client := dockerCli.Client()
 	client := dockerCli.Client()
 	ctx := context.Background()
 	ctx := context.Background()
 
 
+	if opts.pretty {
+		opts.format = "pretty"
+	}
+
 	getRef := func(ref string) (interface{}, []byte, error) {
 	getRef := func(ref string) (interface{}, []byte, error) {
 		service, _, err := client.ServiceInspectWithRaw(ctx, ref)
 		service, _, err := client.ServiceInspectWithRaw(ctx, ref)
 		if err == nil || !apiclient.IsErrServiceNotFound(err) {
 		if err == nil || !apiclient.IsErrServiceNotFound(err) {
@@ -59,130 +58,27 @@ func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
 		return nil, nil, fmt.Errorf("Error: no such service: %s", ref)
 		return nil, nil, fmt.Errorf("Error: no such service: %s", ref)
 	}
 	}
 
 
-	if !opts.pretty {
-		return inspect.Inspect(dockerCli.Out(), opts.refs, opts.format, getRef)
-	}
-
-	return printHumanFriendly(dockerCli.Out(), opts.refs, 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
-		}
-		printService(out, obj.(swarm.Service))
-
-		// 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")
-		}
-	}
-	return nil
-}
-
-// TODO: use a template
-func printService(out io.Writer, service swarm.Service) {
-	fmt.Fprintf(out, "ID:\t\t%s\n", service.ID)
-	fmt.Fprintf(out, "Name:\t\t%s\n", service.Spec.Name)
-	if service.Spec.Labels != nil {
-		fmt.Fprintln(out, "Labels:")
-		for k, v := range service.Spec.Labels {
-			fmt.Fprintf(out, " - %s=%s\n", k, v)
-		}
-	}
-
-	if service.Spec.Mode.Global != nil {
-		fmt.Fprintln(out, "Mode:\t\tGlobal")
-	} else {
-		fmt.Fprintln(out, "Mode:\t\tReplicated")
-		if service.Spec.Mode.Replicated.Replicas != nil {
-			fmt.Fprintf(out, " Replicas:\t%d\n", *service.Spec.Mode.Replicated.Replicas)
-		}
-	}
-
-	if service.UpdateStatus.State != "" {
-		fmt.Fprintln(out, "Update status:")
-		fmt.Fprintf(out, " State:\t\t%s\n", service.UpdateStatus.State)
-		fmt.Fprintf(out, " Started:\t%s ago\n", strings.ToLower(units.HumanDuration(time.Since(service.UpdateStatus.StartedAt))))
-		if service.UpdateStatus.State == swarm.UpdateStateCompleted {
-			fmt.Fprintf(out, " Completed:\t%s ago\n", strings.ToLower(units.HumanDuration(time.Since(service.UpdateStatus.CompletedAt))))
-		}
-		fmt.Fprintf(out, " Message:\t%s\n", service.UpdateStatus.Message)
-	}
-
-	fmt.Fprintln(out, "Placement:")
-	if service.Spec.TaskTemplate.Placement != nil && len(service.Spec.TaskTemplate.Placement.Constraints) > 0 {
-		ioutils.FprintfIfNotEmpty(out, " Constraints\t: %s\n", strings.Join(service.Spec.TaskTemplate.Placement.Constraints, ", "))
-	}
-	if service.Spec.UpdateConfig != nil {
-		fmt.Fprintf(out, "UpdateConfig:\n")
-		fmt.Fprintf(out, " Parallelism:\t%d\n", service.Spec.UpdateConfig.Parallelism)
-		if service.Spec.UpdateConfig.Delay.Nanoseconds() > 0 {
-			fmt.Fprintf(out, " Delay:\t\t%s\n", service.Spec.UpdateConfig.Delay)
+	f := opts.format
+	if len(f) == 0 {
+		f = "raw"
+		if len(dockerCli.ConfigFile().ServiceInspectFormat) > 0 {
+			f = dockerCli.ConfigFile().ServiceInspectFormat
 		}
 		}
-		fmt.Fprintf(out, " On failure:\t%s\n", service.Spec.UpdateConfig.FailureAction)
 	}
 	}
 
 
-	fmt.Fprintf(out, "ContainerSpec:\n")
-	printContainerSpec(out, service.Spec.TaskTemplate.ContainerSpec)
-
-	resources := service.Spec.TaskTemplate.Resources
-	if resources != nil {
-		fmt.Fprintln(out, "Resources:")
-		printResources := func(out io.Writer, requirement string, r *swarm.Resources) {
-			if r == nil || (r.MemoryBytes == 0 && r.NanoCPUs == 0) {
-				return
-			}
-			fmt.Fprintf(out, " %s:\n", requirement)
-			if r.NanoCPUs != 0 {
-				fmt.Fprintf(out, "  CPU:\t\t%g\n", float64(r.NanoCPUs)/1e9)
-			}
-			if r.MemoryBytes != 0 {
-				fmt.Fprintf(out, "  Memory:\t%s\n", units.BytesSize(float64(r.MemoryBytes)))
-			}
-		}
-		printResources(out, "Reservations", resources.Reservations)
-		printResources(out, "Limits", resources.Limits)
-	}
-	if len(service.Spec.Networks) > 0 {
-		fmt.Fprintf(out, "Networks:")
-		for _, n := range service.Spec.Networks {
-			fmt.Fprintf(out, " %s", n.Target)
-		}
-		fmt.Fprintln(out, "")
+	// 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")
 	}
 	}
 
 
-	if len(service.Endpoint.Ports) > 0 {
-		fmt.Fprintln(out, "Ports:")
-		for _, port := range service.Endpoint.Ports {
-			ioutils.FprintfIfNotEmpty(out, " Name = %s\n", port.Name)
-			fmt.Fprintf(out, " Protocol = %s\n", port.Protocol)
-			fmt.Fprintf(out, " TargetPort = %d\n", port.TargetPort)
-			fmt.Fprintf(out, " PublishedPort = %d\n", port.PublishedPort)
-		}
+	serviceCtx := formatter.Context{
+		Output: dockerCli.Out(),
+		Format: formatter.NewServiceFormat(f),
 	}
 	}
-}
 
 
-func printContainerSpec(out io.Writer, containerSpec swarm.ContainerSpec) {
-	fmt.Fprintf(out, " Image:\t\t%s\n", containerSpec.Image)
-	if len(containerSpec.Args) > 0 {
-		fmt.Fprintf(out, " Args:\t\t%s\n", strings.Join(containerSpec.Args, " "))
-	}
-	if len(containerSpec.Env) > 0 {
-		fmt.Fprintf(out, " Env:\t\t%s\n", strings.Join(containerSpec.Env, " "))
-	}
-	ioutils.FprintfIfNotEmpty(out, " Dir\t\t%s\n", containerSpec.Dir)
-	ioutils.FprintfIfNotEmpty(out, " User\t\t%s\n", containerSpec.User)
-	if len(containerSpec.Mounts) > 0 {
-		fmt.Fprintln(out, " Mounts:")
-		for _, v := range containerSpec.Mounts {
-			fmt.Fprintf(out, "  Target = %s\n", v.Target)
-			fmt.Fprintf(out, "  Source = %s\n", v.Source)
-			fmt.Fprintf(out, "  ReadOnly = %v\n", v.ReadOnly)
-			fmt.Fprintf(out, "  Type = %v\n", v.Type)
-		}
+	if err := formatter.ServiceInspectWrite(serviceCtx, opts.refs, getRef); err != nil {
+		return cli.StatusError{StatusCode: 1, Status: err.Error()}
 	}
 	}
+	return nil
 }
 }

+ 13 - 1
cli/command/service/inspect_test.go

@@ -7,6 +7,7 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/docker/docker/api/types/swarm"
 	"github.com/docker/docker/api/types/swarm"
+	"github.com/docker/docker/cli/command/formatter"
 )
 )
 
 
 func TestPrettyPrintWithNoUpdateConfig(t *testing.T) {
 func TestPrettyPrintWithNoUpdateConfig(t *testing.T) {
@@ -77,7 +78,18 @@ func TestPrettyPrintWithNoUpdateConfig(t *testing.T) {
 		},
 		},
 	}
 	}
 
 
-	printService(b, s)
+	ctx := formatter.Context{
+		Output: b,
+		Format: formatter.NewServiceFormat("pretty"),
+	}
+
+	err := formatter.ServiceInspectWrite(ctx, []string{"de179gar9d0o7ltdybungplod"}, func(ref string) (interface{}, []byte, error) {
+		return s, nil, nil
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+
 	if strings.Contains(b.String(), "UpdateStatus") {
 	if strings.Contains(b.String(), "UpdateStatus") {
 		t.Fatal("Pretty print failed before parsing UpdateStatus")
 		t.Fatal("Pretty print failed before parsing UpdateStatus")
 	}
 	}

+ 10 - 9
cliconfig/configfile/file.go

@@ -22,15 +22,16 @@ const (
 
 
 // ConfigFile ~/.docker/config.json file info
 // ConfigFile ~/.docker/config.json file info
 type ConfigFile struct {
 type ConfigFile struct {
-	AuthConfigs      map[string]types.AuthConfig `json:"auths"`
-	HTTPHeaders      map[string]string           `json:"HttpHeaders,omitempty"`
-	PsFormat         string                      `json:"psFormat,omitempty"`
-	ImagesFormat     string                      `json:"imagesFormat,omitempty"`
-	NetworksFormat   string                      `json:"networksFormat,omitempty"`
-	VolumesFormat    string                      `json:"volumesFormat,omitempty"`
-	DetachKeys       string                      `json:"detachKeys,omitempty"`
-	CredentialsStore string                      `json:"credsStore,omitempty"`
-	Filename         string                      `json:"-"` // Note: for internal use only
+	AuthConfigs          map[string]types.AuthConfig `json:"auths"`
+	HTTPHeaders          map[string]string           `json:"HttpHeaders,omitempty"`
+	PsFormat             string                      `json:"psFormat,omitempty"`
+	ImagesFormat         string                      `json:"imagesFormat,omitempty"`
+	NetworksFormat       string                      `json:"networksFormat,omitempty"`
+	VolumesFormat        string                      `json:"volumesFormat,omitempty"`
+	DetachKeys           string                      `json:"detachKeys,omitempty"`
+	CredentialsStore     string                      `json:"credsStore,omitempty"`
+	Filename             string                      `json:"-"` // Note: for internal use only
+	ServiceInspectFormat string                      `json:"serviceInspectFormat,omitempty"`
 }
 }
 
 
 // LegacyLoadFromReader reads the non-nested configuration data given and sets up the
 // LegacyLoadFromReader reads the non-nested configuration data given and sets up the

+ 8 - 0
docs/reference/commandline/cli.md

@@ -143,6 +143,13 @@ Docker's client uses this property. If this property is not set, the client
 falls back to the default table format. For a list of supported formatting
 falls back to the default table format. For a list of supported formatting
 directives, see the [**Formatting** section in the `docker images` documentation](images.md)
 directives, see the [**Formatting** section in the `docker images` documentation](images.md)
 
 
+The property `serviceInspectFormat` specifies the default format for `docker
+service inspect` output. When the `--format` flag is not provided with the
+`docker service inspect` command, Docker's client uses this property. If this
+property is not set, the client falls back to the default json format. For a
+list of supported formatting directives, see the
+[**Formatting** section in the `docker service inspect` documentation](service_inspect.md)
+
 Following is a sample `config.json` file:
 Following is a sample `config.json` file:
 
 
     {
     {
@@ -151,6 +158,7 @@ Following is a sample `config.json` file:
       },
       },
       "psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}",
       "psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}",
       "imagesFormat": "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}",
       "imagesFormat": "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}",
+      "serviceInspectFormat": "pretty",
       "detachKeys": "ctrl-e,e"
       "detachKeys": "ctrl-e,e"
     }
     }
 
 

+ 2 - 0
docs/reference/commandline/service_inspect.md

@@ -130,6 +130,8 @@ Ports:
  PublishedPort = 4443
  PublishedPort = 4443
 ```
 ```
 
 
+You can also use `--format pretty` for the same effect.
+
 
 
 ### Finding the number of tasks running as part of a service
 ### Finding the number of tasks running as part of a service