Add formatter for service inspect
| ... | ... |
@@ -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 |
|