Browse code

Merge pull request #25025 from cpuguy83/service_inspect_formatter

Add formatter for service inspect

Vincent Demeester authored on 2016/09/20 21:49:45
Showing 7 changed files
... ...
@@ -11,11 +11,11 @@ import (
11 11
 	"github.com/docker/docker/utils/templates"
12 12
 )
13 13
 
14
+// Format keys used to specify certain kinds of output formats
14 15
 const (
15
-	// TableFormatKey is the key used to format as a table
16
-	TableFormatKey = "table"
17
-	// RawFormatKey is the key used to format as raw JSON
18
-	RawFormatKey = "raw"
16
+	TableFormatKey  = "table"
17
+	RawFormatKey    = "raw"
18
+	PrettyFormatKey = "pretty"
19 19
 
20 20
 	defaultQuietFormat = "{{.ID}}"
21 21
 )
22 22
new file mode 100644
... ...
@@ -0,0 +1,285 @@
0
+package formatter
1
+
2
+import (
3
+	"fmt"
4
+	"strings"
5
+	"time"
6
+
7
+	mounttypes "github.com/docker/docker/api/types/mount"
8
+	"github.com/docker/docker/api/types/swarm"
9
+	"github.com/docker/docker/cli/command/inspect"
10
+	units "github.com/docker/go-units"
11
+)
12
+
13
+const serviceInspectPrettyTemplate Format = `
14
+ID:		{{.ID}}
15
+Name:		{{.Name}}
16
+{{- if .Labels }}
17
+Labels:
18
+{{- range $k, $v := .Labels }}
19
+ {{ $k }}{{if $v }}={{ $v }}{{ end }}
20
+{{- end }}{{ end }}
21
+Mode:
22
+{{- if .IsModeGlobal }}		Global
23
+{{- else }}		Replicated
24
+{{- if .ModeReplicatedReplicas }}
25
+ Replicas:	{{ .ModeReplicatedReplicas }}
26
+{{- end }}{{ end }}
27
+{{- if .HasUpdateStatus }}
28
+UpdateStatus:
29
+ State:		{{ .UpdateStatusState }}
30
+ Started:	{{ .UpdateStatusStarted }}
31
+{{- if .UpdateIsCompleted }}
32
+ Completed:	{{ .UpdateStatusCompleted }}
33
+{{- end }}
34
+ Message:	{{ .UpdateStatusMessage }}
35
+{{- end }}
36
+Placement:
37
+{{- if .TaskPlacementConstraints -}}
38
+ Contraints:	{{ .TaskPlacementConstraints }}
39
+{{- end }}
40
+{{- if .HasUpdateConfig }}
41
+UpdateConfig:
42
+ Parallelism:	{{ .UpdateParallelism }}
43
+{{- if .HasUpdateDelay -}}
44
+ Delay:		{{ .UpdateDelay }}
45
+{{- end }}
46
+ On failure:	{{ .UpdateOnFailure }}
47
+{{- end }}
48
+ContainerSpec:
49
+ Image:		{{ .ContainerImage }}
50
+{{- if .ContainerArgs }}
51
+ Args:		{{ range $arg := .ContainerArgs }}{{ $arg }} {{ end }}
52
+{{- end -}}
53
+{{- if .ContainerEnv }}
54
+ Env:		{{ range $env := .ContainerEnv }}{{ $env }} {{ end }}
55
+{{- end -}}
56
+{{- if .ContainerWorkDir }}
57
+ Dir:		{{ .ContainerWorkDir }}
58
+{{- end -}}
59
+{{- if .ContainerUser }}
60
+ User: {{ .ContainerUser }}
61
+{{- end }}
62
+{{- if .ContainerMounts }}
63
+Mounts:
64
+{{- end }}
65
+{{- range $mount := .ContainerMounts }}
66
+  Target = {{ $mount.Target }}
67
+   Source = {{ $mount.Source }}
68
+   ReadOnly = {{ $mount.ReadOnly }}
69
+   Type = {{ $mount.Type }}
70
+{{- end -}}
71
+{{- if .HasResources }}
72
+Resources:
73
+{{- if .HasResourceReservations }}
74
+ Reservations:
75
+{{- end }}
76
+{{- if gt .ResourceReservationNanoCPUs 0.0 }}
77
+  CPU:		{{ .ResourceReservationNanoCPUs }}
78
+{{- end }}
79
+{{- if .ResourceReservationMemory }}
80
+  Memory:	{{ .ResourceReservationMemory }}
81
+{{- end }}
82
+{{- if .HasResourceLimits }}
83
+ Limits:
84
+{{- end }}
85
+{{- if gt .ResourceLimitsNanoCPUs 0.0 }}
86
+  CPU:		{{ .ResourceLimitsNanoCPUs }}
87
+{{- end }}
88
+{{- if .ResourceLimitMemory }}
89
+  Memory:	{{ .ResourceLimitMemory }}
90
+{{- end }}{{ end }}
91
+{{- if .Networks }}
92
+Networks:
93
+{{- range $network := .Networks }} {{ $network }}{{ end }} {{ end }}
94
+{{- if .Ports }}
95
+Ports:
96
+{{- range $port := .Ports }}
97
+ PublishedPort {{ $port.PublishedPort }}
98
+  Protocol = {{ $port.Protocol }}
99
+  TargetPort = {{ $port.TargetPort }}
100
+{{- end }} {{ end -}}
101
+`
102
+
103
+// NewServiceFormat returns a Format for rendering using a Context
104
+func NewServiceFormat(source string) Format {
105
+	switch source {
106
+	case PrettyFormatKey:
107
+		return serviceInspectPrettyTemplate
108
+	default:
109
+		return Format(strings.TrimPrefix(source, RawFormatKey))
110
+	}
111
+}
112
+
113
+// ServiceInspectWrite renders the context for a list of services
114
+func ServiceInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) error {
115
+	if ctx.Format != serviceInspectPrettyTemplate {
116
+		return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef)
117
+	}
118
+	render := func(format func(subContext subContext) error) error {
119
+		for _, ref := range refs {
120
+			serviceI, _, err := getRef(ref)
121
+			if err != nil {
122
+				return err
123
+			}
124
+			service, ok := serviceI.(swarm.Service)
125
+			if !ok {
126
+				return fmt.Errorf("got wrong object to inspect")
127
+			}
128
+			if err := format(&serviceInspectContext{Service: service}); err != nil {
129
+				return err
130
+			}
131
+		}
132
+		return nil
133
+	}
134
+	return ctx.Write(&serviceInspectContext{}, render)
135
+}
136
+
137
+type serviceInspectContext struct {
138
+	swarm.Service
139
+	subContext
140
+}
141
+
142
+func (ctx *serviceInspectContext) ID() string {
143
+	return ctx.Service.ID
144
+}
145
+
146
+func (ctx *serviceInspectContext) Name() string {
147
+	return ctx.Service.Spec.Name
148
+}
149
+
150
+func (ctx *serviceInspectContext) Labels() map[string]string {
151
+	return ctx.Service.Spec.Labels
152
+}
153
+
154
+func (ctx *serviceInspectContext) IsModeGlobal() bool {
155
+	return ctx.Service.Spec.Mode.Global != nil
156
+}
157
+
158
+func (ctx *serviceInspectContext) ModeReplicatedReplicas() *uint64 {
159
+	return ctx.Service.Spec.Mode.Replicated.Replicas
160
+}
161
+
162
+func (ctx *serviceInspectContext) HasUpdateStatus() bool {
163
+	return ctx.Service.UpdateStatus.State != ""
164
+}
165
+
166
+func (ctx *serviceInspectContext) UpdateStatusState() swarm.UpdateState {
167
+	return ctx.Service.UpdateStatus.State
168
+}
169
+
170
+func (ctx *serviceInspectContext) UpdateStatusStarted() string {
171
+	return units.HumanDuration(time.Since(ctx.Service.UpdateStatus.StartedAt))
172
+}
173
+
174
+func (ctx *serviceInspectContext) UpdateIsCompleted() bool {
175
+	return ctx.Service.UpdateStatus.State == swarm.UpdateStateCompleted
176
+}
177
+
178
+func (ctx *serviceInspectContext) UpdateStatusCompleted() string {
179
+	return units.HumanDuration(time.Since(ctx.Service.UpdateStatus.CompletedAt))
180
+}
181
+
182
+func (ctx *serviceInspectContext) UpdateStatusMessage() string {
183
+	return ctx.Service.UpdateStatus.Message
184
+}
185
+
186
+func (ctx *serviceInspectContext) TaskPlacementConstraints() []string {
187
+	if ctx.Service.Spec.TaskTemplate.Placement != nil {
188
+		return ctx.Service.Spec.TaskTemplate.Placement.Constraints
189
+	}
190
+	return nil
191
+}
192
+
193
+func (ctx *serviceInspectContext) HasUpdateConfig() bool {
194
+	return ctx.Service.Spec.UpdateConfig != nil
195
+}
196
+
197
+func (ctx *serviceInspectContext) UpdateParallelism() uint64 {
198
+	return ctx.Service.Spec.UpdateConfig.Parallelism
199
+}
200
+
201
+func (ctx *serviceInspectContext) HasUpdateDelay() bool {
202
+	return ctx.Service.Spec.UpdateConfig.Delay.Nanoseconds() > 0
203
+}
204
+
205
+func (ctx *serviceInspectContext) UpdateDelay() time.Duration {
206
+	return ctx.Service.Spec.UpdateConfig.Delay
207
+}
208
+
209
+func (ctx *serviceInspectContext) UpdateOnFailure() string {
210
+	return ctx.Service.Spec.UpdateConfig.FailureAction
211
+}
212
+
213
+func (ctx *serviceInspectContext) ContainerImage() string {
214
+	return ctx.Service.Spec.TaskTemplate.ContainerSpec.Image
215
+}
216
+
217
+func (ctx *serviceInspectContext) ContainerArgs() []string {
218
+	return ctx.Service.Spec.TaskTemplate.ContainerSpec.Args
219
+}
220
+
221
+func (ctx *serviceInspectContext) ContainerEnv() []string {
222
+	return ctx.Service.Spec.TaskTemplate.ContainerSpec.Env
223
+}
224
+
225
+func (ctx *serviceInspectContext) ContainerWorkDir() string {
226
+	return ctx.Service.Spec.TaskTemplate.ContainerSpec.Dir
227
+}
228
+
229
+func (ctx *serviceInspectContext) ContainerUser() string {
230
+	return ctx.Service.Spec.TaskTemplate.ContainerSpec.User
231
+}
232
+
233
+func (ctx *serviceInspectContext) ContainerMounts() []mounttypes.Mount {
234
+	return ctx.Service.Spec.TaskTemplate.ContainerSpec.Mounts
235
+}
236
+
237
+func (ctx *serviceInspectContext) HasResources() bool {
238
+	return ctx.Service.Spec.TaskTemplate.Resources != nil
239
+}
240
+
241
+func (ctx *serviceInspectContext) HasResourceReservations() bool {
242
+	return ctx.Service.Spec.TaskTemplate.Resources.Reservations.NanoCPUs > 0 || ctx.Service.Spec.TaskTemplate.Resources.Reservations.MemoryBytes > 0
243
+}
244
+
245
+func (ctx *serviceInspectContext) ResourceReservationNanoCPUs() float64 {
246
+	if ctx.Service.Spec.TaskTemplate.Resources.Reservations.NanoCPUs == 0 {
247
+		return float64(0)
248
+	}
249
+	return float64(ctx.Service.Spec.TaskTemplate.Resources.Reservations.NanoCPUs) / 1e9
250
+}
251
+
252
+func (ctx *serviceInspectContext) ResourceReservationMemory() string {
253
+	if ctx.Service.Spec.TaskTemplate.Resources.Reservations.MemoryBytes == 0 {
254
+		return ""
255
+	}
256
+	return units.BytesSize(float64(ctx.Service.Spec.TaskTemplate.Resources.Reservations.MemoryBytes))
257
+}
258
+
259
+func (ctx *serviceInspectContext) HasResourceLimits() bool {
260
+	return ctx.Service.Spec.TaskTemplate.Resources.Limits.NanoCPUs > 0 || ctx.Service.Spec.TaskTemplate.Resources.Limits.MemoryBytes > 0
261
+}
262
+
263
+func (ctx *serviceInspectContext) ResourceLimitsNanoCPUs() float64 {
264
+	return float64(ctx.Service.Spec.TaskTemplate.Resources.Limits.NanoCPUs) / 1e9
265
+}
266
+
267
+func (ctx *serviceInspectContext) ResourceLimitMemory() string {
268
+	if ctx.Service.Spec.TaskTemplate.Resources.Limits.MemoryBytes == 0 {
269
+		return ""
270
+	}
271
+	return units.BytesSize(float64(ctx.Service.Spec.TaskTemplate.Resources.Limits.MemoryBytes))
272
+}
273
+
274
+func (ctx *serviceInspectContext) Networks() []string {
275
+	var out []string
276
+	for _, n := range ctx.Service.Spec.Networks {
277
+		out = append(out, n.Target)
278
+	}
279
+	return out
280
+}
281
+
282
+func (ctx *serviceInspectContext) Ports() []swarm.PortConfig {
283
+	return ctx.Service.Endpoint.Ports
284
+}
... ...
@@ -2,19 +2,14 @@ package service
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"io"
6 5
 	"strings"
7
-	"time"
8 6
 
9 7
 	"golang.org/x/net/context"
10 8
 
11
-	"github.com/docker/docker/api/types/swarm"
12 9
 	"github.com/docker/docker/cli"
13 10
 	"github.com/docker/docker/cli/command"
14
-	"github.com/docker/docker/cli/command/inspect"
11
+	"github.com/docker/docker/cli/command/formatter"
15 12
 	apiclient "github.com/docker/docker/client"
16
-	"github.com/docker/docker/pkg/ioutils"
17
-	"github.com/docker/go-units"
18 13
 	"github.com/spf13/cobra"
19 14
 )
20 15
 
... ...
@@ -51,6 +46,10 @@ func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
51 51
 	client := dockerCli.Client()
52 52
 	ctx := context.Background()
53 53
 
54
+	if opts.pretty {
55
+		opts.format = "pretty"
56
+	}
57
+
54 58
 	getRef := func(ref string) (interface{}, []byte, error) {
55 59
 		service, _, err := client.ServiceInspectWithRaw(ctx, ref)
56 60
 		if err == nil || !apiclient.IsErrServiceNotFound(err) {
... ...
@@ -59,130 +58,27 @@ func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
59 59
 		return nil, nil, fmt.Errorf("Error: no such service: %s", ref)
60 60
 	}
61 61
 
62
-	if !opts.pretty {
63
-		return inspect.Inspect(dockerCli.Out(), opts.refs, opts.format, getRef)
64
-	}
65
-
66
-	return printHumanFriendly(dockerCli.Out(), opts.refs, getRef)
67
-}
68
-
69
-func printHumanFriendly(out io.Writer, refs []string, getRef inspect.GetRefFunc) error {
70
-	for idx, ref := range refs {
71
-		obj, _, err := getRef(ref)
72
-		if err != nil {
73
-			return err
74
-		}
75
-		printService(out, obj.(swarm.Service))
76
-
77
-		// TODO: better way to do this?
78
-		// print extra space between objects, but not after the last one
79
-		if idx+1 != len(refs) {
80
-			fmt.Fprintf(out, "\n\n")
81
-		}
82
-	}
83
-	return nil
84
-}
85
-
86
-// TODO: use a template
87
-func printService(out io.Writer, service swarm.Service) {
88
-	fmt.Fprintf(out, "ID:\t\t%s\n", service.ID)
89
-	fmt.Fprintf(out, "Name:\t\t%s\n", service.Spec.Name)
90
-	if service.Spec.Labels != nil {
91
-		fmt.Fprintln(out, "Labels:")
92
-		for k, v := range service.Spec.Labels {
93
-			fmt.Fprintf(out, " - %s=%s\n", k, v)
94
-		}
95
-	}
96
-
97
-	if service.Spec.Mode.Global != nil {
98
-		fmt.Fprintln(out, "Mode:\t\tGlobal")
99
-	} else {
100
-		fmt.Fprintln(out, "Mode:\t\tReplicated")
101
-		if service.Spec.Mode.Replicated.Replicas != nil {
102
-			fmt.Fprintf(out, " Replicas:\t%d\n", *service.Spec.Mode.Replicated.Replicas)
103
-		}
104
-	}
105
-
106
-	if service.UpdateStatus.State != "" {
107
-		fmt.Fprintln(out, "Update status:")
108
-		fmt.Fprintf(out, " State:\t\t%s\n", service.UpdateStatus.State)
109
-		fmt.Fprintf(out, " Started:\t%s ago\n", strings.ToLower(units.HumanDuration(time.Since(service.UpdateStatus.StartedAt))))
110
-		if service.UpdateStatus.State == swarm.UpdateStateCompleted {
111
-			fmt.Fprintf(out, " Completed:\t%s ago\n", strings.ToLower(units.HumanDuration(time.Since(service.UpdateStatus.CompletedAt))))
112
-		}
113
-		fmt.Fprintf(out, " Message:\t%s\n", service.UpdateStatus.Message)
114
-	}
115
-
116
-	fmt.Fprintln(out, "Placement:")
117
-	if service.Spec.TaskTemplate.Placement != nil && len(service.Spec.TaskTemplate.Placement.Constraints) > 0 {
118
-		ioutils.FprintfIfNotEmpty(out, " Constraints\t: %s\n", strings.Join(service.Spec.TaskTemplate.Placement.Constraints, ", "))
119
-	}
120
-	if service.Spec.UpdateConfig != nil {
121
-		fmt.Fprintf(out, "UpdateConfig:\n")
122
-		fmt.Fprintf(out, " Parallelism:\t%d\n", service.Spec.UpdateConfig.Parallelism)
123
-		if service.Spec.UpdateConfig.Delay.Nanoseconds() > 0 {
124
-			fmt.Fprintf(out, " Delay:\t\t%s\n", service.Spec.UpdateConfig.Delay)
62
+	f := opts.format
63
+	if len(f) == 0 {
64
+		f = "raw"
65
+		if len(dockerCli.ConfigFile().ServiceInspectFormat) > 0 {
66
+			f = dockerCli.ConfigFile().ServiceInspectFormat
125 67
 		}
126
-		fmt.Fprintf(out, " On failure:\t%s\n", service.Spec.UpdateConfig.FailureAction)
127 68
 	}
128 69
 
129
-	fmt.Fprintf(out, "ContainerSpec:\n")
130
-	printContainerSpec(out, service.Spec.TaskTemplate.ContainerSpec)
131
-
132
-	resources := service.Spec.TaskTemplate.Resources
133
-	if resources != nil {
134
-		fmt.Fprintln(out, "Resources:")
135
-		printResources := func(out io.Writer, requirement string, r *swarm.Resources) {
136
-			if r == nil || (r.MemoryBytes == 0 && r.NanoCPUs == 0) {
137
-				return
138
-			}
139
-			fmt.Fprintf(out, " %s:\n", requirement)
140
-			if r.NanoCPUs != 0 {
141
-				fmt.Fprintf(out, "  CPU:\t\t%g\n", float64(r.NanoCPUs)/1e9)
142
-			}
143
-			if r.MemoryBytes != 0 {
144
-				fmt.Fprintf(out, "  Memory:\t%s\n", units.BytesSize(float64(r.MemoryBytes)))
145
-			}
146
-		}
147
-		printResources(out, "Reservations", resources.Reservations)
148
-		printResources(out, "Limits", resources.Limits)
149
-	}
150
-	if len(service.Spec.Networks) > 0 {
151
-		fmt.Fprintf(out, "Networks:")
152
-		for _, n := range service.Spec.Networks {
153
-			fmt.Fprintf(out, " %s", n.Target)
154
-		}
155
-		fmt.Fprintln(out, "")
70
+	// check if the user is trying to apply a template to the pretty format, which
71
+	// is not supported
72
+	if strings.HasPrefix(f, "pretty") && f != "pretty" {
73
+		return fmt.Errorf("Cannot supply extra formatting options to the pretty template")
156 74
 	}
157 75
 
158
-	if len(service.Endpoint.Ports) > 0 {
159
-		fmt.Fprintln(out, "Ports:")
160
-		for _, port := range service.Endpoint.Ports {
161
-			ioutils.FprintfIfNotEmpty(out, " Name = %s\n", port.Name)
162
-			fmt.Fprintf(out, " Protocol = %s\n", port.Protocol)
163
-			fmt.Fprintf(out, " TargetPort = %d\n", port.TargetPort)
164
-			fmt.Fprintf(out, " PublishedPort = %d\n", port.PublishedPort)
165
-		}
76
+	serviceCtx := formatter.Context{
77
+		Output: dockerCli.Out(),
78
+		Format: formatter.NewServiceFormat(f),
166 79
 	}
167
-}
168 80
 
169
-func printContainerSpec(out io.Writer, containerSpec swarm.ContainerSpec) {
170
-	fmt.Fprintf(out, " Image:\t\t%s\n", containerSpec.Image)
171
-	if len(containerSpec.Args) > 0 {
172
-		fmt.Fprintf(out, " Args:\t\t%s\n", strings.Join(containerSpec.Args, " "))
173
-	}
174
-	if len(containerSpec.Env) > 0 {
175
-		fmt.Fprintf(out, " Env:\t\t%s\n", strings.Join(containerSpec.Env, " "))
176
-	}
177
-	ioutils.FprintfIfNotEmpty(out, " Dir\t\t%s\n", containerSpec.Dir)
178
-	ioutils.FprintfIfNotEmpty(out, " User\t\t%s\n", containerSpec.User)
179
-	if len(containerSpec.Mounts) > 0 {
180
-		fmt.Fprintln(out, " Mounts:")
181
-		for _, v := range containerSpec.Mounts {
182
-			fmt.Fprintf(out, "  Target = %s\n", v.Target)
183
-			fmt.Fprintf(out, "  Source = %s\n", v.Source)
184
-			fmt.Fprintf(out, "  ReadOnly = %v\n", v.ReadOnly)
185
-			fmt.Fprintf(out, "  Type = %v\n", v.Type)
186
-		}
81
+	if err := formatter.ServiceInspectWrite(serviceCtx, opts.refs, getRef); err != nil {
82
+		return cli.StatusError{StatusCode: 1, Status: err.Error()}
187 83
 	}
84
+	return nil
188 85
 }
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"time"
8 8
 
9 9
 	"github.com/docker/docker/api/types/swarm"
10
+	"github.com/docker/docker/cli/command/formatter"
10 11
 )
11 12
 
12 13
 func TestPrettyPrintWithNoUpdateConfig(t *testing.T) {
... ...
@@ -77,7 +78,18 @@ func TestPrettyPrintWithNoUpdateConfig(t *testing.T) {
77 77
 		},
78 78
 	}
79 79
 
80
-	printService(b, s)
80
+	ctx := formatter.Context{
81
+		Output: b,
82
+		Format: formatter.NewServiceFormat("pretty"),
83
+	}
84
+
85
+	err := formatter.ServiceInspectWrite(ctx, []string{"de179gar9d0o7ltdybungplod"}, func(ref string) (interface{}, []byte, error) {
86
+		return s, nil, nil
87
+	})
88
+	if err != nil {
89
+		t.Fatal(err)
90
+	}
91
+
81 92
 	if strings.Contains(b.String(), "UpdateStatus") {
82 93
 		t.Fatal("Pretty print failed before parsing UpdateStatus")
83 94
 	}
... ...
@@ -22,15 +22,16 @@ const (
22 22
 
23 23
 // ConfigFile ~/.docker/config.json file info
24 24
 type ConfigFile struct {
25
-	AuthConfigs      map[string]types.AuthConfig `json:"auths"`
26
-	HTTPHeaders      map[string]string           `json:"HttpHeaders,omitempty"`
27
-	PsFormat         string                      `json:"psFormat,omitempty"`
28
-	ImagesFormat     string                      `json:"imagesFormat,omitempty"`
29
-	NetworksFormat   string                      `json:"networksFormat,omitempty"`
30
-	VolumesFormat    string                      `json:"volumesFormat,omitempty"`
31
-	DetachKeys       string                      `json:"detachKeys,omitempty"`
32
-	CredentialsStore string                      `json:"credsStore,omitempty"`
33
-	Filename         string                      `json:"-"` // Note: for internal use only
25
+	AuthConfigs          map[string]types.AuthConfig `json:"auths"`
26
+	HTTPHeaders          map[string]string           `json:"HttpHeaders,omitempty"`
27
+	PsFormat             string                      `json:"psFormat,omitempty"`
28
+	ImagesFormat         string                      `json:"imagesFormat,omitempty"`
29
+	NetworksFormat       string                      `json:"networksFormat,omitempty"`
30
+	VolumesFormat        string                      `json:"volumesFormat,omitempty"`
31
+	DetachKeys           string                      `json:"detachKeys,omitempty"`
32
+	CredentialsStore     string                      `json:"credsStore,omitempty"`
33
+	Filename             string                      `json:"-"` // Note: for internal use only
34
+	ServiceInspectFormat string                      `json:"serviceInspectFormat,omitempty"`
34 35
 }
35 36
 
36 37
 // LegacyLoadFromReader reads the non-nested configuration data given and sets up the
... ...
@@ -143,6 +143,13 @@ Docker's client uses this property. If this property is not set, the client
143 143
 falls back to the default table format. For a list of supported formatting
144 144
 directives, see the [**Formatting** section in the `docker images` documentation](images.md)
145 145
 
146
+The property `serviceInspectFormat` specifies the default format for `docker
147
+service inspect` output. When the `--format` flag is not provided with the
148
+`docker service inspect` command, Docker's client uses this property. If this
149
+property is not set, the client falls back to the default json format. For a
150
+list of supported formatting directives, see the
151
+[**Formatting** section in the `docker service inspect` documentation](service_inspect.md)
152
+
146 153
 Following is a sample `config.json` file:
147 154
 
148 155
     {
... ...
@@ -151,6 +158,7 @@ Following is a sample `config.json` file:
151 151
       },
152 152
       "psFormat": "table {{.ID}}\\t{{.Image}}\\t{{.Command}}\\t{{.Labels}}",
153 153
       "imagesFormat": "table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}",
154
+      "serviceInspectFormat": "pretty",
154 155
       "detachKeys": "ctrl-e,e"
155 156
     }
156 157
 
... ...
@@ -130,6 +130,8 @@ Ports:
130 130
  PublishedPort = 4443
131 131
 ```
132 132
 
133
+You can also use `--format pretty` for the same effect.
134
+
133 135
 
134 136
 ### Finding the number of tasks running as part of a service
135 137