@@ -2,11 +2,13 @@ package service
22
33import (
44 "context"
5- "fmt"
65 "sort"
76
7+ "github.com/docker/docker/client"
88 "vbom.ml/util/sortorder"
99
10+ "github.com/docker/docker/api/types/versions"
11+
1012 "github.com/docker/cli/cli"
1113 "github.com/docker/cli/cli/command"
1214 "github.com/docker/cli/cli/command/formatter"
@@ -49,34 +51,36 @@ func runList(dockerCli command.Cli, options listOptions) error {
4951 client := dockerCli .Client ()
5052
5153 serviceFilters := options .filter .Value ()
52- services , err := client .ServiceList (ctx , types.ServiceListOptions {Filters : serviceFilters })
54+ services , err := client .ServiceList (ctx , types.ServiceListOptions {
55+ Filters : serviceFilters ,
56+ // When not running "quiet", also get service status (number of running
57+ // and desired tasks). Note that this is only supported on API v1.41 and
58+ // up; older API versions ignore this option, and we will have to collect
59+ // the information manually below.
60+ Status : ! options .quiet ,
61+ })
5362 if err != nil {
5463 return err
5564 }
5665
57- sort .Slice (services , func (i , j int ) bool {
58- return sortorder .NaturalLess (services [i ].Spec .Name , services [j ].Spec .Name )
59- })
60- info := map [string ]ListInfo {}
6166 if len (services ) > 0 && ! options .quiet {
62- // only non-empty services and not quiet, should we call TaskList and NodeList api
63- taskFilter := filters .NewArgs ()
64- for _ , service := range services {
65- taskFilter .Add ("service" , service .ID )
66- }
67-
68- tasks , err := client .TaskList (ctx , types.TaskListOptions {Filters : taskFilter })
69- if err != nil {
70- return err
71- }
72-
73- nodes , err := client .NodeList (ctx , types.NodeListOptions {})
74- if err != nil {
75- return err
67+ // Now that a request was made, we know what API version was used (either
68+ // through configuration, or after client and daemon negotiated a version).
69+ // If API version v1.41 or up was used; the daemon already did the legwork
70+ // for us, and we don't have to calculate the number of desired and running
71+ // tasks. On older API versions, we need to do some extra requests to get
72+ // that information
73+ if versions .LessThan (client .ClientVersion (), "1.41" ) {
74+ var err error
75+ services , err = SetServiceStatus (ctx , client , services )
76+ if err != nil {
77+ return err
78+ }
7679 }
77-
78- info = GetServicesStatus (services , nodes , tasks )
7980 }
81+ sort .Slice (services , func (i , j int ) bool {
82+ return sortorder .NaturalLess (services [i ].Spec .Name , services [j ].Spec .Name )
83+ })
8084
8185 format := options .format
8286 if len (format ) == 0 {
@@ -91,52 +95,69 @@ func runList(dockerCli command.Cli, options listOptions) error {
9195 Output : dockerCli .Out (),
9296 Format : NewListFormat (format , options .quiet ),
9397 }
94- return ListFormatWrite (servicesCtx , services , info )
98+ return ListFormatWrite (servicesCtx , services )
9599}
96100
97- // GetServicesStatus returns a map of mode and replicas
98- func GetServicesStatus (services []swarm.Service , nodes []swarm.Node , tasks []swarm.Task ) map [string ]ListInfo {
99- running := map [string ]int {}
100- tasksNoShutdown := map [string ]int {}
101+ // SetServiceStatus propagates the ServiceStatus field for "services".
102+ //
103+ // If API version v1.41 or up is used, this information is already set by the
104+ // daemon. On older API versions, we need to do some extra requests to get
105+ // that information
106+ func SetServiceStatus (ctx context.Context , c client.APIClient , services []swarm.Service ) ([]swarm.Service , error ) {
107+ // status := make(map[string]swarm.ServiceStatus)
108+ status := map [string ]* swarm.ServiceStatus {}
109+
110+ // only non-empty services and not quiet, should we call TaskList and NodeList api
111+ taskFilter := filters .NewArgs ()
112+ for _ , s := range services {
113+ status [s .ID ] = & swarm.ServiceStatus {}
114+ taskFilter .Add ("service" , s .ID )
115+ }
101116
102- activeNodes := make (map [string ]struct {})
103- for _ , n := range nodes {
104- if n .Status .State != swarm .NodeStateDown {
105- activeNodes [n .ID ] = struct {}{}
106- }
117+ tasks , err := c .TaskList (ctx , types.TaskListOptions {Filters : taskFilter })
118+ if err != nil {
119+ return nil , err
107120 }
108121
109- for _ , task := range tasks {
110- if task .DesiredState != swarm .TaskStateShutdown {
111- tasksNoShutdown [task .ServiceID ]++
122+ if len (tasks ) > 0 {
123+ activeNodes := make (map [string ]struct {})
124+ nodes , err := c .NodeList (ctx , types.NodeListOptions {})
125+ if err != nil {
126+ return nil , err
112127 }
113-
114- if _ , nodeActive := activeNodes [task .NodeID ]; nodeActive && task .Status .State == swarm .TaskStateRunning {
115- running [task .ServiceID ]++
128+ for _ , n := range nodes {
129+ if n .Status .State != swarm .NodeStateDown {
130+ activeNodes [n .ID ] = struct {}{}
131+ }
116132 }
117- }
118133
119- info := map [string ]ListInfo {}
120- for _ , service := range services {
121- info [service .ID ] = ListInfo {}
122- if service .Spec .Mode .Replicated != nil && service .Spec .Mode .Replicated .Replicas != nil {
123- if service .Spec .TaskTemplate .Placement != nil && service .Spec .TaskTemplate .Placement .MaxReplicas > 0 {
124- info [service .ID ] = ListInfo {
125- Mode : "replicated" ,
126- Replicas : fmt .Sprintf ("%d/%d (max %d per node)" , running [service .ID ], * service .Spec .Mode .Replicated .Replicas , service .Spec .TaskTemplate .Placement .MaxReplicas ),
127- }
128- } else {
129- info [service .ID ] = ListInfo {
130- Mode : "replicated" ,
131- Replicas : fmt .Sprintf ("%d/%d" , running [service .ID ], * service .Spec .Mode .Replicated .Replicas ),
132- }
134+ for _ , task := range tasks {
135+ if task .DesiredState != swarm .TaskStateShutdown {
136+ status [task .ServiceID ].DesiredTasks ++
133137 }
134- } else if service .Spec .Mode .Global != nil {
135- info [service .ID ] = ListInfo {
136- Mode : "global" ,
137- Replicas : fmt .Sprintf ("%d/%d" , running [service .ID ], tasksNoShutdown [service .ID ]),
138+
139+ if _ , nodeActive := activeNodes [task .NodeID ]; nodeActive && task .Status .State == swarm .TaskStateRunning {
140+ status [task .ServiceID ].RunningTasks ++
138141 }
139142 }
140143 }
141- return info
144+ out := make ([]swarm.Service , len (services ))
145+ for i , s := range services {
146+ services [i ].ServiceStatus = & swarm.ServiceStatus {
147+ DesiredTasks : status [s .ID ].DesiredTasks ,
148+ RunningTasks : status [s .ID ].RunningTasks ,
149+ }
150+ }
151+ return out , nil
152+ }
153+
154+ func serviceMode (service swarm.Service ) string {
155+ switch {
156+ case service .Spec .Mode .Global != nil :
157+ return "global"
158+ case service .Spec .Mode .Replicated != nil :
159+ return "replicated"
160+ default :
161+ return ""
162+ }
142163}
0 commit comments