github.com/xeptore/docker-cli@v20.10.14+incompatible/cli/command/task/print.go (about) 1 package task 2 3 import ( 4 "context" 5 "fmt" 6 "sort" 7 8 "github.com/docker/cli/cli/command" 9 "github.com/docker/cli/cli/command/formatter" 10 "github.com/docker/cli/cli/command/idresolver" 11 "github.com/docker/cli/cli/config/configfile" 12 "github.com/docker/docker/api/types/swarm" 13 "github.com/fvbommel/sortorder" 14 ) 15 16 type tasksSortable []swarm.Task 17 18 func (t tasksSortable) Len() int { 19 return len(t) 20 } 21 22 func (t tasksSortable) Swap(i, j int) { 23 t[i], t[j] = t[j], t[i] 24 } 25 26 func (t tasksSortable) Less(i, j int) bool { 27 if t[i].Name != t[j].Name { 28 return sortorder.NaturalLess(t[i].Name, t[j].Name) 29 } 30 // Sort tasks for the same service and slot by most recent. 31 return t[j].Meta.CreatedAt.Before(t[i].CreatedAt) 32 } 33 34 // Print task information in a format. 35 // Besides this, command `docker node ps <node>` 36 // and `docker stack ps` will call this, too. 37 func Print(ctx context.Context, dockerCli command.Cli, tasks []swarm.Task, resolver *idresolver.IDResolver, trunc, quiet bool, format string) error { 38 tasks, err := generateTaskNames(ctx, tasks, resolver) 39 if err != nil { 40 return err 41 } 42 43 // First sort tasks, so that all tasks (including previous ones) of the same 44 // service and slot are together. This must be done first, to print "previous" 45 // tasks indented 46 sort.Stable(tasksSortable(tasks)) 47 48 names := map[string]string{} 49 nodes := map[string]string{} 50 51 tasksCtx := formatter.Context{ 52 Output: dockerCli.Out(), 53 Format: NewTaskFormat(format, quiet), 54 Trunc: trunc, 55 } 56 57 var indent string 58 if tasksCtx.Format.IsTable() { 59 indent = ` \_ ` 60 } 61 prevName := "" 62 for _, task := range tasks { 63 if task.Name == prevName { 64 // Indent previous tasks of the same slot 65 names[task.ID] = indent + task.Name 66 } else { 67 names[task.ID] = task.Name 68 } 69 prevName = task.Name 70 71 nodeValue, err := resolver.Resolve(ctx, swarm.Node{}, task.NodeID) 72 if err != nil { 73 return err 74 } 75 nodes[task.ID] = nodeValue 76 } 77 78 return FormatWrite(tasksCtx, tasks, names, nodes) 79 } 80 81 // generateTaskNames generates names for the given tasks, and returns a copy of 82 // the slice with the 'Name' field set. 83 // 84 // Depending if the "--no-resolve" option is set, names have the following pattern: 85 // 86 // - ServiceName.Slot or ServiceID.Slot for tasks that are part of a replicated service 87 // - ServiceName.NodeName or ServiceID.NodeID for tasks that are part of a global service 88 // 89 // Task-names are not unique in cases where "tasks" contains previous/rotated tasks. 90 func generateTaskNames(ctx context.Context, tasks []swarm.Task, resolver *idresolver.IDResolver) ([]swarm.Task, error) { 91 // Use a copy of the tasks list, to not modify the original slice 92 t := append(tasks[:0:0], tasks...) 93 94 for i, task := range t { 95 serviceName, err := resolver.Resolve(ctx, swarm.Service{}, task.ServiceID) 96 if err != nil { 97 return nil, err 98 } 99 if task.Slot != 0 { 100 t[i].Name = fmt.Sprintf("%v.%v", serviceName, task.Slot) 101 } else { 102 t[i].Name = fmt.Sprintf("%v.%v", serviceName, task.NodeID) 103 } 104 } 105 return t, nil 106 } 107 108 // DefaultFormat returns the default format from the config file, or table 109 // format if nothing is set in the config. 110 func DefaultFormat(configFile *configfile.ConfigFile, quiet bool) string { 111 if len(configFile.TasksFormat) > 0 && !quiet { 112 return configFile.TasksFormat 113 } 114 return formatter.TableFormatKey 115 }