moby/cli/command/task/print.go
Derek McGowan 3a1279393f
Use distribution reference
Remove forked reference package. Use normalized named values
everywhere and familiar functions to convert back to familiar
strings for UX and storage compatibility.

Enforce that the source repository in the distribution metadata
is always a normalized string, ignore invalid values which are not.
Update distribution tests to use normalized values.

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
2017-02-07 11:08:37 -08:00

159 lines
3.7 KiB
Go

package task
import (
"fmt"
"io"
"sort"
"strings"
"text/tabwriter"
"time"
"golang.org/x/net/context"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/cli/command/idresolver"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/go-units"
)
const (
psTaskItemFmt = "%s\t%s\t%s\t%s\t%s\t%s %s ago\t%s\t%s\n"
maxErrLength = 30
)
type portStatus swarm.PortStatus
func (ps portStatus) String() string {
if len(ps.Ports) == 0 {
return ""
}
str := fmt.Sprintf("*:%d->%d/%s", ps.Ports[0].PublishedPort, ps.Ports[0].TargetPort, ps.Ports[0].Protocol)
for _, pConfig := range ps.Ports[1:] {
str += fmt.Sprintf(",*:%d->%d/%s", pConfig.PublishedPort, pConfig.TargetPort, pConfig.Protocol)
}
return str
}
type tasksBySlot []swarm.Task
func (t tasksBySlot) Len() int {
return len(t)
}
func (t tasksBySlot) Swap(i, j int) {
t[i], t[j] = t[j], t[i]
}
func (t tasksBySlot) Less(i, j int) bool {
// Sort by slot.
if t[i].Slot != t[j].Slot {
return t[i].Slot < t[j].Slot
}
// If same slot, sort by most recent.
return t[j].Meta.CreatedAt.Before(t[i].CreatedAt)
}
// Print task information in a table format.
// Besides this, command `docker node ps <node>`
// and `docker stack ps` will call this, too.
func Print(dockerCli command.Cli, ctx context.Context, tasks []swarm.Task, resolver *idresolver.IDResolver, noTrunc bool) error {
sort.Stable(tasksBySlot(tasks))
writer := tabwriter.NewWriter(dockerCli.Out(), 0, 4, 2, ' ', 0)
// Ignore flushing errors
defer writer.Flush()
fmt.Fprintln(writer, strings.Join([]string{"ID", "NAME", "IMAGE", "NODE", "DESIRED STATE", "CURRENT STATE", "ERROR", "PORTS"}, "\t"))
return print(writer, ctx, tasks, resolver, noTrunc)
}
// PrintQuiet shows task list in a quiet way.
func PrintQuiet(dockerCli command.Cli, tasks []swarm.Task) error {
sort.Stable(tasksBySlot(tasks))
out := dockerCli.Out()
for _, task := range tasks {
fmt.Fprintln(out, task.ID)
}
return nil
}
func print(out io.Writer, ctx context.Context, tasks []swarm.Task, resolver *idresolver.IDResolver, noTrunc bool) error {
prevName := ""
for _, task := range tasks {
id := task.ID
if !noTrunc {
id = stringid.TruncateID(id)
}
serviceName, err := resolver.Resolve(ctx, swarm.Service{}, task.ServiceID)
if err != nil {
return err
}
nodeValue, err := resolver.Resolve(ctx, swarm.Node{}, task.NodeID)
if err != nil {
return err
}
name := ""
if task.Slot != 0 {
name = fmt.Sprintf("%v.%v", serviceName, task.Slot)
} else {
name = fmt.Sprintf("%v.%v", serviceName, task.NodeID)
}
// Indent the name if necessary
indentedName := name
if name == prevName {
indentedName = fmt.Sprintf(" \\_ %s", indentedName)
}
prevName = name
// Trim and quote the error message.
taskErr := task.Status.Err
if !noTrunc && len(taskErr) > maxErrLength {
taskErr = fmt.Sprintf("%s…", taskErr[:maxErrLength-1])
}
if len(taskErr) > 0 {
taskErr = fmt.Sprintf("\"%s\"", taskErr)
}
image := task.Spec.ContainerSpec.Image
if !noTrunc {
ref, err := reference.ParseNormalizedNamed(image)
if err == nil {
// update image string for display, (strips any digest)
if nt, ok := ref.(reference.NamedTagged); ok {
if namedTagged, err := reference.WithTag(reference.TrimNamed(nt), nt.Tag()); err == nil {
image = reference.FamiliarString(namedTagged)
}
}
}
}
fmt.Fprintf(
out,
psTaskItemFmt,
id,
indentedName,
image,
nodeValue,
command.PrettyPrint(task.DesiredState),
command.PrettyPrint(task.Status.State),
strings.ToLower(units.HumanDuration(time.Since(task.Status.Timestamp))),
taskErr,
portStatus(task.Status.PortStatus),
)
}
return nil
}