github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/cli/command/service/formatter.go (about) 1 package service 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 "time" 8 9 "github.com/docker/cli/cli/command/formatter" 10 "github.com/docker/cli/cli/command/inspect" 11 "github.com/docker/distribution/reference" 12 "github.com/docker/docker/api/types" 13 "github.com/docker/docker/api/types/container" 14 mounttypes "github.com/docker/docker/api/types/mount" 15 "github.com/docker/docker/api/types/swarm" 16 "github.com/docker/docker/pkg/stringid" 17 units "github.com/docker/go-units" 18 "github.com/fvbommel/sortorder" 19 "github.com/pkg/errors" 20 ) 21 22 const serviceInspectPrettyTemplate formatter.Format = ` 23 ID: {{.ID}} 24 Name: {{.Name}} 25 {{- if .Labels }} 26 Labels: 27 {{- range $k, $v := .Labels }} 28 {{ $k }}{{if $v }}={{ $v }}{{ end }} 29 {{- end }}{{ end }} 30 Service Mode: 31 {{- if .IsModeGlobal }} Global 32 {{- else if .IsModeReplicated }} Replicated 33 {{- if .ModeReplicatedReplicas }} 34 Replicas: {{ .ModeReplicatedReplicas }} 35 {{- end }}{{ end }} 36 {{- if .HasUpdateStatus }} 37 UpdateStatus: 38 State: {{ .UpdateStatusState }} 39 {{- if .HasUpdateStatusStarted }} 40 Started: {{ .UpdateStatusStarted }} 41 {{- end }} 42 {{- if .UpdateIsCompleted }} 43 Completed: {{ .UpdateStatusCompleted }} 44 {{- end }} 45 Message: {{ .UpdateStatusMessage }} 46 {{- end }} 47 Placement: 48 {{- if .TaskPlacementConstraints }} 49 Constraints: {{ .TaskPlacementConstraints }} 50 {{- end }} 51 {{- if .TaskPlacementPreferences }} 52 Preferences: {{ .TaskPlacementPreferences }} 53 {{- end }} 54 {{- if .MaxReplicas }} 55 Max Replicas Per Node: {{ .MaxReplicas }} 56 {{- end }} 57 {{- if .HasUpdateConfig }} 58 UpdateConfig: 59 Parallelism: {{ .UpdateParallelism }} 60 {{- if .HasUpdateDelay}} 61 Delay: {{ .UpdateDelay }} 62 {{- end }} 63 On failure: {{ .UpdateOnFailure }} 64 {{- if .HasUpdateMonitor}} 65 Monitoring Period: {{ .UpdateMonitor }} 66 {{- end }} 67 Max failure ratio: {{ .UpdateMaxFailureRatio }} 68 Update order: {{ .UpdateOrder }} 69 {{- end }} 70 {{- if .HasRollbackConfig }} 71 RollbackConfig: 72 Parallelism: {{ .RollbackParallelism }} 73 {{- if .HasRollbackDelay}} 74 Delay: {{ .RollbackDelay }} 75 {{- end }} 76 On failure: {{ .RollbackOnFailure }} 77 {{- if .HasRollbackMonitor}} 78 Monitoring Period: {{ .RollbackMonitor }} 79 {{- end }} 80 Max failure ratio: {{ .RollbackMaxFailureRatio }} 81 Rollback order: {{ .RollbackOrder }} 82 {{- end }} 83 ContainerSpec: 84 Image: {{ .ContainerImage }} 85 {{- if .ContainerArgs }} 86 Args: {{ range $arg := .ContainerArgs }}{{ $arg }} {{ end }} 87 {{- end -}} 88 {{- if .ContainerEnv }} 89 Env: {{ range $env := .ContainerEnv }}{{ $env }} {{ end }} 90 {{- end -}} 91 {{- if .ContainerWorkDir }} 92 Dir: {{ .ContainerWorkDir }} 93 {{- end -}} 94 {{- if .HasContainerInit }} 95 Init: {{ .ContainerInit }} 96 {{- end -}} 97 {{- if .ContainerUser }} 98 User: {{ .ContainerUser }} 99 {{- end }} 100 {{- if .HasCapabilities }} 101 Capabilities: 102 {{- if .HasCapabilityAdd }} 103 Add: {{ .CapabilityAdd }} 104 {{- end }} 105 {{- if .HasCapabilityDrop }} 106 Drop: {{ .CapabilityDrop }} 107 {{- end }} 108 {{- end }} 109 {{- if .ContainerSysCtls }} 110 SysCtls: 111 {{- range $k, $v := .ContainerSysCtls }} 112 {{ $k }}{{if $v }}: {{ $v }}{{ end }} 113 {{- end }}{{ end }} 114 {{- if .ContainerUlimits }} 115 Ulimits: 116 {{- range $k, $v := .ContainerUlimits }} 117 {{ $k }}: {{ $v }} 118 {{- end }}{{ end }} 119 {{- if .ContainerMounts }} 120 Mounts: 121 {{- end }} 122 {{- range $mount := .ContainerMounts }} 123 Target: {{ $mount.Target }} 124 Source: {{ $mount.Source }} 125 ReadOnly: {{ $mount.ReadOnly }} 126 Type: {{ $mount.Type }} 127 {{- end -}} 128 {{- if .Configs}} 129 Configs: 130 {{- range $config := .Configs }} 131 Target: {{$config.File.Name}} 132 Source: {{$config.ConfigName}} 133 {{- end }}{{ end }} 134 {{- if .Secrets }} 135 Secrets: 136 {{- range $secret := .Secrets }} 137 Target: {{$secret.File.Name}} 138 Source: {{$secret.SecretName}} 139 {{- end }}{{ end }} 140 {{- if .HasLogDriver }} 141 Log Driver: 142 {{- if .HasLogDriverName }} 143 Name: {{ .LogDriverName }} 144 {{- end }} 145 {{- if .LogOpts }} 146 LogOpts: 147 {{- range $k, $v := .LogOpts }} 148 {{ $k }}{{if $v }}: {{ $v }}{{ end }} 149 {{- end }}{{ end }} 150 {{ end }} 151 {{- if .HasResources }} 152 Resources: 153 {{- if .HasResourceReservations }} 154 Reservations: 155 {{- if gt .ResourceReservationNanoCPUs 0.0 }} 156 CPU: {{ .ResourceReservationNanoCPUs }} 157 {{- end }} 158 {{- if .ResourceReservationMemory }} 159 Memory: {{ .ResourceReservationMemory }} 160 {{- end }}{{ end }} 161 {{- if .HasResourceLimits }} 162 Limits: 163 {{- if gt .ResourceLimitsNanoCPUs 0.0 }} 164 CPU: {{ .ResourceLimitsNanoCPUs }} 165 {{- end }} 166 {{- if .ResourceLimitMemory }} 167 Memory: {{ .ResourceLimitMemory }} 168 {{- end }}{{ end }}{{ end }} 169 {{- if gt .ResourceLimitPids 0 }} 170 PIDs: {{ .ResourceLimitPids }} 171 {{- end }} 172 {{- if .Networks }} 173 Networks: 174 {{- range $network := .Networks }} {{ $network }}{{ end }} {{ end }} 175 Endpoint Mode: {{ .EndpointMode }} 176 {{- if .Ports }} 177 Ports: 178 {{- range $port := .Ports }} 179 PublishedPort = {{ $port.PublishedPort }} 180 Protocol = {{ $port.Protocol }} 181 TargetPort = {{ $port.TargetPort }} 182 PublishMode = {{ $port.PublishMode }} 183 {{- end }} {{ end -}} 184 {{- if .Healthcheck }} 185 Healthcheck: 186 Interval = {{ .Healthcheck.Interval }} 187 Retries = {{ .Healthcheck.Retries }} 188 StartPeriod = {{ .Healthcheck.StartPeriod }} 189 Timeout = {{ .Healthcheck.Timeout }} 190 {{- if .Healthcheck.Test }} 191 Tests: 192 {{- range $test := .Healthcheck.Test }} 193 Test = {{ $test }} 194 {{- end }} {{ end -}} 195 {{- end }} 196 ` 197 198 // NewFormat returns a Format for rendering using a Context 199 func NewFormat(source string) formatter.Format { 200 switch source { 201 case formatter.PrettyFormatKey: 202 return serviceInspectPrettyTemplate 203 default: 204 return formatter.Format(strings.TrimPrefix(source, formatter.RawFormatKey)) 205 } 206 } 207 208 func resolveNetworks(service swarm.Service, getNetwork inspect.GetRefFunc) map[string]string { 209 networkNames := make(map[string]string) 210 for _, network := range service.Spec.TaskTemplate.Networks { 211 if resolved, _, err := getNetwork(network.Target); err == nil { 212 if resolvedNetwork, ok := resolved.(types.NetworkResource); ok { 213 networkNames[resolvedNetwork.ID] = resolvedNetwork.Name 214 } 215 } 216 } 217 return networkNames 218 } 219 220 // InspectFormatWrite renders the context for a list of services 221 func InspectFormatWrite(ctx formatter.Context, refs []string, getRef, getNetwork inspect.GetRefFunc) error { 222 if ctx.Format != serviceInspectPrettyTemplate { 223 return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef) 224 } 225 render := func(format func(subContext formatter.SubContext) error) error { 226 for _, ref := range refs { 227 serviceI, _, err := getRef(ref) 228 if err != nil { 229 return err 230 } 231 service, ok := serviceI.(swarm.Service) 232 if !ok { 233 return errors.Errorf("got wrong object to inspect") 234 } 235 if err := format(&serviceInspectContext{Service: service, networkNames: resolveNetworks(service, getNetwork)}); err != nil { 236 return err 237 } 238 } 239 return nil 240 } 241 return ctx.Write(&serviceInspectContext{}, render) 242 } 243 244 type serviceInspectContext struct { 245 swarm.Service 246 formatter.SubContext 247 248 // networkNames is a map from network IDs (as found in 249 // Networks[x].Target) to network names. 250 networkNames map[string]string 251 } 252 253 func (ctx *serviceInspectContext) MarshalJSON() ([]byte, error) { 254 return formatter.MarshalJSON(ctx) 255 } 256 257 func (ctx *serviceInspectContext) ID() string { 258 return ctx.Service.ID 259 } 260 261 func (ctx *serviceInspectContext) Name() string { 262 return ctx.Service.Spec.Name 263 } 264 265 func (ctx *serviceInspectContext) Labels() map[string]string { 266 return ctx.Service.Spec.Labels 267 } 268 269 func (ctx *serviceInspectContext) HasLogDriver() bool { 270 return ctx.Service.Spec.TaskTemplate.LogDriver != nil 271 } 272 273 func (ctx *serviceInspectContext) HasLogDriverName() bool { 274 return ctx.Service.Spec.TaskTemplate.LogDriver.Name != "" 275 } 276 func (ctx *serviceInspectContext) LogDriverName() string { 277 return ctx.Service.Spec.TaskTemplate.LogDriver.Name 278 } 279 280 func (ctx *serviceInspectContext) LogOpts() map[string]string { 281 return ctx.Service.Spec.TaskTemplate.LogDriver.Options 282 } 283 284 func (ctx *serviceInspectContext) Configs() []*swarm.ConfigReference { 285 return ctx.Service.Spec.TaskTemplate.ContainerSpec.Configs 286 } 287 288 func (ctx *serviceInspectContext) Secrets() []*swarm.SecretReference { 289 return ctx.Service.Spec.TaskTemplate.ContainerSpec.Secrets 290 } 291 292 func (ctx *serviceInspectContext) Healthcheck() *container.HealthConfig { 293 return ctx.Service.Spec.TaskTemplate.ContainerSpec.Healthcheck 294 } 295 296 func (ctx *serviceInspectContext) IsModeGlobal() bool { 297 return ctx.Service.Spec.Mode.Global != nil 298 } 299 300 func (ctx *serviceInspectContext) IsModeReplicated() bool { 301 return ctx.Service.Spec.Mode.Replicated != nil 302 } 303 304 func (ctx *serviceInspectContext) ModeReplicatedReplicas() *uint64 { 305 return ctx.Service.Spec.Mode.Replicated.Replicas 306 } 307 308 func (ctx *serviceInspectContext) HasUpdateStatus() bool { 309 return ctx.Service.UpdateStatus != nil && ctx.Service.UpdateStatus.State != "" 310 } 311 312 func (ctx *serviceInspectContext) UpdateStatusState() swarm.UpdateState { 313 return ctx.Service.UpdateStatus.State 314 } 315 316 func (ctx *serviceInspectContext) HasUpdateStatusStarted() bool { 317 return ctx.Service.UpdateStatus.StartedAt != nil 318 } 319 320 func (ctx *serviceInspectContext) UpdateStatusStarted() string { 321 return units.HumanDuration(time.Since(*ctx.Service.UpdateStatus.StartedAt)) + " ago" 322 } 323 324 func (ctx *serviceInspectContext) UpdateIsCompleted() bool { 325 return ctx.Service.UpdateStatus.State == swarm.UpdateStateCompleted && ctx.Service.UpdateStatus.CompletedAt != nil 326 } 327 328 func (ctx *serviceInspectContext) UpdateStatusCompleted() string { 329 return units.HumanDuration(time.Since(*ctx.Service.UpdateStatus.CompletedAt)) + " ago" 330 } 331 332 func (ctx *serviceInspectContext) UpdateStatusMessage() string { 333 return ctx.Service.UpdateStatus.Message 334 } 335 336 func (ctx *serviceInspectContext) TaskPlacementConstraints() []string { 337 if ctx.Service.Spec.TaskTemplate.Placement != nil { 338 return ctx.Service.Spec.TaskTemplate.Placement.Constraints 339 } 340 return nil 341 } 342 343 func (ctx *serviceInspectContext) TaskPlacementPreferences() []string { 344 if ctx.Service.Spec.TaskTemplate.Placement == nil { 345 return nil 346 } 347 var strings []string 348 for _, pref := range ctx.Service.Spec.TaskTemplate.Placement.Preferences { 349 if pref.Spread != nil { 350 strings = append(strings, "spread="+pref.Spread.SpreadDescriptor) 351 } 352 } 353 return strings 354 } 355 356 func (ctx *serviceInspectContext) MaxReplicas() uint64 { 357 if ctx.Service.Spec.TaskTemplate.Placement != nil { 358 return ctx.Service.Spec.TaskTemplate.Placement.MaxReplicas 359 } 360 return 0 361 } 362 363 func (ctx *serviceInspectContext) HasUpdateConfig() bool { 364 return ctx.Service.Spec.UpdateConfig != nil 365 } 366 367 func (ctx *serviceInspectContext) UpdateParallelism() uint64 { 368 return ctx.Service.Spec.UpdateConfig.Parallelism 369 } 370 371 func (ctx *serviceInspectContext) HasUpdateDelay() bool { 372 return ctx.Service.Spec.UpdateConfig.Delay.Nanoseconds() > 0 373 } 374 375 func (ctx *serviceInspectContext) UpdateDelay() time.Duration { 376 return ctx.Service.Spec.UpdateConfig.Delay 377 } 378 379 func (ctx *serviceInspectContext) UpdateOnFailure() string { 380 return ctx.Service.Spec.UpdateConfig.FailureAction 381 } 382 383 func (ctx *serviceInspectContext) UpdateOrder() string { 384 return ctx.Service.Spec.UpdateConfig.Order 385 } 386 387 func (ctx *serviceInspectContext) HasUpdateMonitor() bool { 388 return ctx.Service.Spec.UpdateConfig.Monitor.Nanoseconds() > 0 389 } 390 391 func (ctx *serviceInspectContext) UpdateMonitor() time.Duration { 392 return ctx.Service.Spec.UpdateConfig.Monitor 393 } 394 395 func (ctx *serviceInspectContext) UpdateMaxFailureRatio() float32 { 396 return ctx.Service.Spec.UpdateConfig.MaxFailureRatio 397 } 398 399 func (ctx *serviceInspectContext) HasRollbackConfig() bool { 400 return ctx.Service.Spec.RollbackConfig != nil 401 } 402 403 func (ctx *serviceInspectContext) RollbackParallelism() uint64 { 404 return ctx.Service.Spec.RollbackConfig.Parallelism 405 } 406 407 func (ctx *serviceInspectContext) HasRollbackDelay() bool { 408 return ctx.Service.Spec.RollbackConfig.Delay.Nanoseconds() > 0 409 } 410 411 func (ctx *serviceInspectContext) RollbackDelay() time.Duration { 412 return ctx.Service.Spec.RollbackConfig.Delay 413 } 414 415 func (ctx *serviceInspectContext) RollbackOnFailure() string { 416 return ctx.Service.Spec.RollbackConfig.FailureAction 417 } 418 419 func (ctx *serviceInspectContext) HasRollbackMonitor() bool { 420 return ctx.Service.Spec.RollbackConfig.Monitor.Nanoseconds() > 0 421 } 422 423 func (ctx *serviceInspectContext) RollbackMonitor() time.Duration { 424 return ctx.Service.Spec.RollbackConfig.Monitor 425 } 426 427 func (ctx *serviceInspectContext) RollbackMaxFailureRatio() float32 { 428 return ctx.Service.Spec.RollbackConfig.MaxFailureRatio 429 } 430 431 func (ctx *serviceInspectContext) RollbackOrder() string { 432 return ctx.Service.Spec.RollbackConfig.Order 433 } 434 435 func (ctx *serviceInspectContext) ContainerImage() string { 436 return ctx.Service.Spec.TaskTemplate.ContainerSpec.Image 437 } 438 439 func (ctx *serviceInspectContext) ContainerArgs() []string { 440 return ctx.Service.Spec.TaskTemplate.ContainerSpec.Args 441 } 442 443 func (ctx *serviceInspectContext) ContainerEnv() []string { 444 return ctx.Service.Spec.TaskTemplate.ContainerSpec.Env 445 } 446 447 func (ctx *serviceInspectContext) ContainerWorkDir() string { 448 return ctx.Service.Spec.TaskTemplate.ContainerSpec.Dir 449 } 450 451 func (ctx *serviceInspectContext) ContainerUser() string { 452 return ctx.Service.Spec.TaskTemplate.ContainerSpec.User 453 } 454 455 func (ctx *serviceInspectContext) HasContainerInit() bool { 456 return ctx.Service.Spec.TaskTemplate.ContainerSpec.Init != nil 457 } 458 459 func (ctx *serviceInspectContext) ContainerInit() bool { 460 return *ctx.Service.Spec.TaskTemplate.ContainerSpec.Init 461 } 462 463 func (ctx *serviceInspectContext) ContainerMounts() []mounttypes.Mount { 464 return ctx.Service.Spec.TaskTemplate.ContainerSpec.Mounts 465 } 466 467 func (ctx *serviceInspectContext) ContainerSysCtls() map[string]string { 468 return ctx.Service.Spec.TaskTemplate.ContainerSpec.Sysctls 469 } 470 471 func (ctx *serviceInspectContext) HasContainerSysCtls() bool { 472 return len(ctx.Service.Spec.TaskTemplate.ContainerSpec.Sysctls) > 0 473 } 474 475 func (ctx *serviceInspectContext) ContainerUlimits() map[string]string { 476 ulimits := map[string]string{} 477 478 for _, u := range ctx.Service.Spec.TaskTemplate.ContainerSpec.Ulimits { 479 ulimits[u.Name] = fmt.Sprintf("%d:%d", u.Soft, u.Hard) 480 } 481 482 return ulimits 483 } 484 485 func (ctx *serviceInspectContext) HasContainerUlimits() bool { 486 return len(ctx.Service.Spec.TaskTemplate.ContainerSpec.Ulimits) > 0 487 } 488 489 func (ctx *serviceInspectContext) HasResources() bool { 490 return ctx.Service.Spec.TaskTemplate.Resources != nil 491 } 492 493 func (ctx *serviceInspectContext) HasResourceReservations() bool { 494 if ctx.Service.Spec.TaskTemplate.Resources == nil || ctx.Service.Spec.TaskTemplate.Resources.Reservations == nil { 495 return false 496 } 497 return ctx.Service.Spec.TaskTemplate.Resources.Reservations.NanoCPUs > 0 || ctx.Service.Spec.TaskTemplate.Resources.Reservations.MemoryBytes > 0 498 } 499 500 func (ctx *serviceInspectContext) ResourceReservationNanoCPUs() float64 { 501 if ctx.Service.Spec.TaskTemplate.Resources.Reservations.NanoCPUs == 0 { 502 return float64(0) 503 } 504 return float64(ctx.Service.Spec.TaskTemplate.Resources.Reservations.NanoCPUs) / 1e9 505 } 506 507 func (ctx *serviceInspectContext) ResourceReservationMemory() string { 508 if ctx.Service.Spec.TaskTemplate.Resources.Reservations.MemoryBytes == 0 { 509 return "" 510 } 511 return units.BytesSize(float64(ctx.Service.Spec.TaskTemplate.Resources.Reservations.MemoryBytes)) 512 } 513 514 func (ctx *serviceInspectContext) HasResourceLimits() bool { 515 if ctx.Service.Spec.TaskTemplate.Resources == nil || ctx.Service.Spec.TaskTemplate.Resources.Limits == nil { 516 return false 517 } 518 return ctx.Service.Spec.TaskTemplate.Resources.Limits.NanoCPUs > 0 || ctx.Service.Spec.TaskTemplate.Resources.Limits.MemoryBytes > 0 || ctx.Service.Spec.TaskTemplate.Resources.Limits.Pids > 0 519 } 520 521 func (ctx *serviceInspectContext) ResourceLimitsNanoCPUs() float64 { 522 return float64(ctx.Service.Spec.TaskTemplate.Resources.Limits.NanoCPUs) / 1e9 523 } 524 525 func (ctx *serviceInspectContext) ResourceLimitMemory() string { 526 if ctx.Service.Spec.TaskTemplate.Resources.Limits.MemoryBytes == 0 { 527 return "" 528 } 529 return units.BytesSize(float64(ctx.Service.Spec.TaskTemplate.Resources.Limits.MemoryBytes)) 530 } 531 532 func (ctx *serviceInspectContext) ResourceLimitPids() int64 { 533 if ctx.Service.Spec.TaskTemplate.Resources == nil || ctx.Service.Spec.TaskTemplate.Resources.Limits == nil { 534 return 0 535 } 536 return ctx.Service.Spec.TaskTemplate.Resources.Limits.Pids 537 } 538 539 func (ctx *serviceInspectContext) Networks() []string { 540 var out []string 541 for _, n := range ctx.Service.Spec.TaskTemplate.Networks { 542 if name, ok := ctx.networkNames[n.Target]; ok { 543 out = append(out, name) 544 } else { 545 out = append(out, n.Target) 546 } 547 } 548 return out 549 } 550 551 func (ctx *serviceInspectContext) EndpointMode() string { 552 if ctx.Service.Spec.EndpointSpec == nil { 553 return "" 554 } 555 556 return string(ctx.Service.Spec.EndpointSpec.Mode) 557 } 558 559 func (ctx *serviceInspectContext) Ports() []swarm.PortConfig { 560 return ctx.Service.Endpoint.Ports 561 } 562 563 func (ctx *serviceInspectContext) HasCapabilities() bool { 564 return len(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityAdd) > 0 || len(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityDrop) > 0 565 } 566 567 func (ctx *serviceInspectContext) HasCapabilityAdd() bool { 568 return len(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityAdd) > 0 569 } 570 571 func (ctx *serviceInspectContext) HasCapabilityDrop() bool { 572 return len(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityDrop) > 0 573 } 574 575 func (ctx *serviceInspectContext) CapabilityAdd() string { 576 return strings.Join(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityAdd, ", ") 577 } 578 579 func (ctx *serviceInspectContext) CapabilityDrop() string { 580 return strings.Join(ctx.Service.Spec.TaskTemplate.ContainerSpec.CapabilityDrop, ", ") 581 } 582 583 const ( 584 defaultServiceTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Mode}}\t{{.Replicas}}\t{{.Image}}\t{{.Ports}}" 585 586 serviceIDHeader = "ID" 587 modeHeader = "MODE" 588 replicasHeader = "REPLICAS" 589 ) 590 591 // NewListFormat returns a Format for rendering using a service Context 592 func NewListFormat(source string, quiet bool) formatter.Format { 593 switch source { 594 case formatter.TableFormatKey: 595 if quiet { 596 return formatter.DefaultQuietFormat 597 } 598 return defaultServiceTableFormat 599 case formatter.RawFormatKey: 600 if quiet { 601 return `id: {{.ID}}` 602 } 603 return `id: {{.ID}}\nname: {{.Name}}\nmode: {{.Mode}}\nreplicas: {{.Replicas}}\nimage: {{.Image}}\nports: {{.Ports}}\n` 604 } 605 return formatter.Format(source) 606 } 607 608 // ListFormatWrite writes the context 609 func ListFormatWrite(ctx formatter.Context, services []swarm.Service) error { 610 render := func(format func(subContext formatter.SubContext) error) error { 611 sort.Slice(services, func(i, j int) bool { 612 return sortorder.NaturalLess(services[i].Spec.Name, services[j].Spec.Name) 613 }) 614 for _, service := range services { 615 serviceCtx := &serviceContext{service: service} 616 if err := format(serviceCtx); err != nil { 617 return err 618 } 619 } 620 return nil 621 } 622 serviceCtx := serviceContext{} 623 serviceCtx.Header = formatter.SubHeaderContext{ 624 "ID": serviceIDHeader, 625 "Name": formatter.NameHeader, 626 "Mode": modeHeader, 627 "Replicas": replicasHeader, 628 "Image": formatter.ImageHeader, 629 "Ports": formatter.PortsHeader, 630 } 631 return ctx.Write(&serviceCtx, render) 632 } 633 634 type serviceContext struct { 635 formatter.HeaderContext 636 service swarm.Service 637 } 638 639 func (c *serviceContext) MarshalJSON() ([]byte, error) { 640 return formatter.MarshalJSON(c) 641 } 642 643 func (c *serviceContext) ID() string { 644 return stringid.TruncateID(c.service.ID) 645 } 646 647 func (c *serviceContext) Name() string { 648 return c.service.Spec.Name 649 } 650 651 func (c *serviceContext) Mode() string { 652 switch { 653 case c.service.Spec.Mode.Global != nil: 654 return "global" 655 case c.service.Spec.Mode.Replicated != nil: 656 return "replicated" 657 case c.service.Spec.Mode.ReplicatedJob != nil: 658 return "replicated job" 659 case c.service.Spec.Mode.GlobalJob != nil: 660 return "global job" 661 default: 662 return "" 663 } 664 } 665 666 func (c *serviceContext) Replicas() string { 667 s := &c.service 668 669 var running, desired, completed uint64 670 if s.ServiceStatus != nil { 671 running = c.service.ServiceStatus.RunningTasks 672 desired = c.service.ServiceStatus.DesiredTasks 673 completed = c.service.ServiceStatus.CompletedTasks 674 } 675 // for jobs, we will not include the max per node, even if it is set. jobs 676 // include instead the progress of the job as a whole, in addition to the 677 // current running state. the system respects max per node, but if we 678 // included it in the list output, the lines for jobs would be entirely too 679 // long and make the UI look bad. 680 if s.Spec.Mode.ReplicatedJob != nil { 681 return fmt.Sprintf( 682 "%d/%d (%d/%d completed)", 683 running, desired, completed, *s.Spec.Mode.ReplicatedJob.TotalCompletions, 684 ) 685 } 686 if s.Spec.Mode.GlobalJob != nil { 687 // for global jobs, we need to do a little math. desired tasks are only 688 // the tasks that have not yet actually reached the Completed state. 689 // Completed tasks have reached the completed state. the TOTAL number 690 // of tasks to run is the sum of the tasks desired to still complete, 691 // and the tasks actually completed. 692 return fmt.Sprintf( 693 "%d/%d (%d/%d completed)", 694 running, desired, completed, desired+completed, 695 ) 696 } 697 if r := c.maxReplicas(); r > 0 { 698 return fmt.Sprintf("%d/%d (max %d per node)", running, desired, r) 699 } 700 return fmt.Sprintf("%d/%d", running, desired) 701 } 702 703 func (c *serviceContext) maxReplicas() uint64 { 704 if c.Mode() != "replicated" || c.service.Spec.TaskTemplate.Placement == nil { 705 return 0 706 } 707 return c.service.Spec.TaskTemplate.Placement.MaxReplicas 708 } 709 710 func (c *serviceContext) Image() string { 711 var image string 712 if c.service.Spec.TaskTemplate.ContainerSpec != nil { 713 image = c.service.Spec.TaskTemplate.ContainerSpec.Image 714 } 715 if ref, err := reference.ParseNormalizedNamed(image); err == nil { 716 // update image string for display, (strips any digest) 717 if nt, ok := ref.(reference.NamedTagged); ok { 718 if namedTagged, err := reference.WithTag(reference.TrimNamed(nt), nt.Tag()); err == nil { 719 image = reference.FamiliarString(namedTagged) 720 } 721 } 722 } 723 724 return image 725 } 726 727 type portRange struct { 728 pStart uint32 729 pEnd uint32 730 tStart uint32 731 tEnd uint32 732 protocol swarm.PortConfigProtocol 733 } 734 735 func (pr portRange) String() string { 736 var ( 737 pub string 738 tgt string 739 ) 740 741 if pr.pEnd > pr.pStart { 742 pub = fmt.Sprintf("%d-%d", pr.pStart, pr.pEnd) 743 } else { 744 pub = fmt.Sprintf("%d", pr.pStart) 745 } 746 if pr.tEnd > pr.tStart { 747 tgt = fmt.Sprintf("%d-%d", pr.tStart, pr.tEnd) 748 } else { 749 tgt = fmt.Sprintf("%d", pr.tStart) 750 } 751 return fmt.Sprintf("*:%s->%s/%s", pub, tgt, pr.protocol) 752 } 753 754 // Ports formats published ports on the ingress network for output. 755 // 756 // Where possible, ranges are grouped to produce a compact output: 757 // - multiple ports mapped to a single port (80->80, 81->80); is formatted as *:80-81->80 758 // - multiple consecutive ports on both sides; (80->80, 81->81) are formatted as: *:80-81->80-81 759 // 760 // The above should not be grouped together, i.e.: 761 // - 80->80, 81->81, 82->80 should be presented as : *:80-81->80-81, *:82->80 762 // 763 // TODO improve: 764 // - combine non-consecutive ports mapped to a single port (80->80, 81->80, 84->80, 86->80, 87->80); to be printed as *:80-81,84,86-87->80 765 // - combine tcp and udp mappings if their port-mapping is exactly the same (*:80-81->80-81/tcp+udp instead of *:80-81->80-81/tcp, *:80-81->80-81/udp) 766 func (c *serviceContext) Ports() string { 767 if c.service.Endpoint.Ports == nil { 768 return "" 769 } 770 771 pr := portRange{} 772 ports := []string{} 773 774 servicePorts := c.service.Endpoint.Ports 775 sort.Slice(servicePorts, func(i, j int) bool { 776 if servicePorts[i].Protocol == servicePorts[j].Protocol { 777 return servicePorts[i].PublishedPort < servicePorts[j].PublishedPort 778 } 779 return servicePorts[i].Protocol < servicePorts[j].Protocol 780 }) 781 782 for _, p := range c.service.Endpoint.Ports { 783 if p.PublishMode == swarm.PortConfigPublishModeIngress { 784 prIsRange := pr.tEnd != pr.tStart 785 tOverlaps := p.TargetPort <= pr.tEnd 786 787 // Start a new port-range if: 788 // - the protocol is different from the current port-range 789 // - published or target port are not consecutive to the current port-range 790 // - the current port-range is a _range_, and the target port overlaps with the current range's target-ports 791 if p.Protocol != pr.protocol || p.PublishedPort-pr.pEnd > 1 || p.TargetPort-pr.tEnd > 1 || prIsRange && tOverlaps { 792 // start a new port-range, and print the previous port-range (if any) 793 if pr.pStart > 0 { 794 ports = append(ports, pr.String()) 795 } 796 pr = portRange{ 797 pStart: p.PublishedPort, 798 pEnd: p.PublishedPort, 799 tStart: p.TargetPort, 800 tEnd: p.TargetPort, 801 protocol: p.Protocol, 802 } 803 continue 804 } 805 pr.pEnd = p.PublishedPort 806 pr.tEnd = p.TargetPort 807 } 808 } 809 if pr.pStart > 0 { 810 ports = append(ports, pr.String()) 811 } 812 return strings.Join(ports, ", ") 813 }