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  }