2016-09-08 13:11:39 -04:00
package service
import (
2018-05-03 21:02:44 -04:00
"context"
2016-09-08 13:11:39 -04:00
"fmt"
2017-07-19 03:34:03 -04:00
"sort"
"vbom.ml/util/sortorder"
2016-09-08 13:11:39 -04:00
2017-04-17 18:07:56 -04:00
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
2017-05-15 08:45:19 -04:00
"github.com/docker/cli/opts"
2016-09-08 13:11:39 -04:00
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/spf13/cobra"
)
type listOptions struct {
quiet bool
2017-01-26 16:08:07 -05:00
format string
2016-09-08 13:11:39 -04:00
filter opts . FilterOpt
}
2017-07-19 03:34:03 -04:00
func newListCommand ( dockerCli command . Cli ) * cobra . Command {
2017-05-15 08:45:19 -04:00
options := listOptions { filter : opts . NewFilterOpt ( ) }
2016-09-08 13:11:39 -04:00
cmd := & cobra . Command {
Use : "ls [OPTIONS]" ,
Aliases : [ ] string { "list" } ,
Short : "List services" ,
Args : cli . NoArgs ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2017-05-15 08:45:19 -04:00
return runList ( dockerCli , options )
2016-09-08 13:11:39 -04:00
} ,
}
flags := cmd . Flags ( )
2017-05-15 08:45:19 -04:00
flags . BoolVarP ( & options . quiet , "quiet" , "q" , false , "Only display IDs" )
flags . StringVar ( & options . format , "format" , "" , "Pretty-print services using a Go template" )
flags . VarP ( & options . filter , "filter" , "f" , "Filter output based on conditions provided" )
2016-09-08 13:11:39 -04:00
return cmd
}
2017-07-19 03:34:03 -04:00
func runList ( dockerCli command . Cli , options listOptions ) error {
2016-09-08 13:11:39 -04:00
ctx := context . Background ( )
client := dockerCli . Client ( )
2017-05-15 08:45:19 -04:00
serviceFilters := options . filter . Value ( )
2017-04-25 11:57:04 -04:00
services , err := client . ServiceList ( ctx , types . ServiceListOptions { Filters : serviceFilters } )
2016-09-08 13:11:39 -04:00
if err != nil {
return err
}
2018-07-06 15:49:10 -04:00
sort . Slice ( services , func ( i , j int ) bool {
return sortorder . NaturalLess ( services [ i ] . Spec . Name , services [ j ] . Spec . Name )
} )
2018-10-23 11:05:44 -04:00
info := map [ string ] ListInfo { }
2017-05-15 08:45:19 -04:00
if len ( services ) > 0 && ! options . quiet {
2016-10-08 22:29:58 -04:00
// only non-empty services and not quiet, should we call TaskList and NodeList api
2016-09-08 13:11:39 -04:00
taskFilter := filters . NewArgs ( )
for _ , service := range services {
taskFilter . Add ( "service" , service . ID )
}
2016-11-01 10:01:16 -04:00
tasks , err := client . TaskList ( ctx , types . TaskListOptions { Filters : taskFilter } )
2016-09-08 13:11:39 -04:00
if err != nil {
return err
}
nodes , err := client . NodeList ( ctx , types . NodeListOptions { } )
if err != nil {
return err
}
2017-01-26 16:08:07 -05:00
info = GetServicesStatus ( services , nodes , tasks )
}
2017-05-15 08:45:19 -04:00
format := options . format
2017-01-26 16:08:07 -05:00
if len ( format ) == 0 {
2017-05-15 08:45:19 -04:00
if len ( dockerCli . ConfigFile ( ) . ServicesFormat ) > 0 && ! options . quiet {
2017-01-26 16:08:07 -05:00
format = dockerCli . ConfigFile ( ) . ServicesFormat
} else {
format = formatter . TableFormatKey
}
2016-09-08 13:11:39 -04:00
}
2016-10-08 22:29:58 -04:00
2017-01-26 16:08:07 -05:00
servicesCtx := formatter . Context {
Output : dockerCli . Out ( ) ,
2018-10-23 11:05:44 -04:00
Format : NewListFormat ( format , options . quiet ) ,
2017-01-26 16:08:07 -05:00
}
2018-10-23 11:05:44 -04:00
return ListFormatWrite ( servicesCtx , services , info )
2016-09-08 13:11:39 -04:00
}
2017-01-26 16:08:07 -05:00
// GetServicesStatus returns a map of mode and replicas
2018-10-23 11:05:44 -04:00
func GetServicesStatus ( services [ ] swarm . Service , nodes [ ] swarm . Node , tasks [ ] swarm . Task ) map [ string ] ListInfo {
2017-01-26 16:08:07 -05:00
running := map [ string ] int { }
tasksNoShutdown := map [ string ] int { }
2016-09-08 13:11:39 -04:00
activeNodes := make ( map [ string ] struct { } )
for _ , n := range nodes {
if n . Status . State != swarm . NodeStateDown {
activeNodes [ n . ID ] = struct { } { }
}
}
for _ , task := range tasks {
2016-10-24 23:39:53 -04:00
if task . DesiredState != swarm . TaskStateShutdown {
tasksNoShutdown [ task . ServiceID ] ++
}
if _ , nodeActive := activeNodes [ task . NodeID ] ; nodeActive && task . Status . State == swarm . TaskStateRunning {
2016-09-08 13:11:39 -04:00
running [ task . ServiceID ] ++
}
}
2018-10-23 11:05:44 -04:00
info := map [ string ] ListInfo { }
2016-09-08 13:11:39 -04:00
for _ , service := range services {
2018-10-23 11:05:44 -04:00
info [ service . ID ] = ListInfo { }
2016-09-08 13:11:39 -04:00
if service . Spec . Mode . Replicated != nil && service . Spec . Mode . Replicated . Replicas != nil {
2019-01-09 12:10:35 -05:00
if service . Spec . TaskTemplate . Placement != nil && service . Spec . TaskTemplate . Placement . MaxReplicas > 0 {
info [ service . ID ] = ListInfo {
Mode : "replicated" ,
Replicas : fmt . Sprintf ( "%d/%d (max %d per node)" , running [ service . ID ] , * service . Spec . Mode . Replicated . Replicas , service . Spec . TaskTemplate . Placement . MaxReplicas ) ,
}
} else {
info [ service . ID ] = ListInfo {
Mode : "replicated" ,
Replicas : fmt . Sprintf ( "%d/%d" , running [ service . ID ] , * service . Spec . Mode . Replicated . Replicas ) ,
}
2017-01-26 16:08:07 -05:00
}
2016-09-08 13:11:39 -04:00
} else if service . Spec . Mode . Global != nil {
2018-10-23 11:05:44 -04:00
info [ service . ID ] = ListInfo {
2017-01-26 16:08:07 -05:00
Mode : "global" ,
Replicas : fmt . Sprintf ( "%d/%d" , running [ service . ID ] , tasksNoShutdown [ service . ID ] ) ,
2016-11-17 01:21:18 -05:00
}
}
2016-09-08 13:11:39 -04:00
}
2017-01-26 16:08:07 -05:00
return info
2016-09-08 13:11:39 -04:00
}