Skip to content

Commit

Permalink
Merge pull request moby#14699 from estesp/docker-ps-format
Browse files Browse the repository at this point in the history
Carry moby#10255: Docker ps format
  • Loading branch information
calavera committed Jul 22, 2015
2 parents fe68ac8 + 542b58d commit 40b9224
Show file tree
Hide file tree
Showing 11 changed files with 536 additions and 88 deletions.
4 changes: 4 additions & 0 deletions api/client/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
return nil
}

func (cli *DockerCli) PsFormat() string {
return cli.configFile.PsFormat
}

// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
// The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config
// is set the client scheme will be set to https.
Expand Down
97 changes: 14 additions & 83 deletions api/client/ps.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,14 @@ package client

import (
"encoding/json"
"fmt"
"net/url"
"strconv"
"strings"
"text/tabwriter"
"time"

"github.com/docker/docker/api"
"github.com/docker/docker/api/client/ps"
"github.com/docker/docker/api/types"
"github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/parsers/filters"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/pkg/units"
)

// CmdPs outputs a list of Docker containers.
Expand All @@ -38,6 +31,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
since = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show created since Id or Name, include non-running")
before = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name")
last = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running")
format = cmd.String([]string{"-format"}, "", "Pretty-print containers using a Go template")
flFilter = opts.NewListOpts(nil)
)
cmd.Require(flag.Exact, 0)
Expand Down Expand Up @@ -98,87 +92,24 @@ func (cli *DockerCli) CmdPs(args ...string) error {
return err
}

w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
if !*quiet {
fmt.Fprint(w, "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES")

if *size {
fmt.Fprintln(w, "\tSIZE")
f := *format
if len(f) == 0 {
if len(cli.PsFormat()) > 0 {
f = cli.PsFormat()
} else {
fmt.Fprint(w, "\n")
f = "table"
}
}

stripNamePrefix := func(ss []string) []string {
for i, s := range ss {
ss[i] = s[1:]
}

return ss
psCtx := ps.Context{
Output: cli.out,
Format: f,
Quiet: *quiet,
Size: *size,
Trunc: !*noTrunc,
}

for _, container := range containers {
ID := container.ID

if !*noTrunc {
ID = stringid.TruncateID(ID)
}

if *quiet {
fmt.Fprintln(w, ID)

continue
}

var (
names = stripNamePrefix(container.Names)
command = strconv.Quote(container.Command)
displayPort string
)

if !*noTrunc {
command = stringutils.Truncate(command, 20)

// only display the default name for the container with notrunc is passed
for _, name := range names {
if len(strings.Split(name, "/")) == 1 {
names = []string{name}
break
}
}
}

image := container.Image
if image == "" {
image = "<no image>"
}

if container.HostConfig.NetworkMode == "host" {
displayPort = "*/tcp, */udp"
} else {
displayPort = api.DisplayablePorts(container.Ports)
}

fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", ID, image, command,
units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(container.Created), 0))),
container.Status, displayPort, strings.Join(names, ","))

if *size {
if container.SizeRootFs > 0 {
fmt.Fprintf(w, "%s (virtual %s)\n", units.HumanSize(float64(container.SizeRw)), units.HumanSize(float64(container.SizeRootFs)))
} else {
fmt.Fprintf(w, "%s\n", units.HumanSize(float64(container.SizeRw)))
}

continue
}

fmt.Fprint(w, "\n")
}

if !*quiet {
w.Flush()
}
ps.Format(psCtx, containers)

return nil
}
210 changes: 210 additions & 0 deletions api/client/ps/custom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package ps

import (
"bytes"
"fmt"
"strconv"
"strings"
"text/tabwriter"
"text/template"
"time"

"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/pkg/units"
)

const (
tableKey = "table"

idHeader = "CONTAINER ID"
imageHeader = "IMAGE"
namesHeader = "NAMES"
commandHeader = "COMMAND"
createdAtHeader = "CREATED AT"
runningForHeader = "CREATED"
statusHeader = "STATUS"
portsHeader = "PORTS"
sizeHeader = "SIZE"
labelsHeader = "LABELS"
)

type containerContext struct {
trunc bool
header []string
c types.Container
}

func (c *containerContext) ID() string {
c.addHeader(idHeader)
if c.trunc {
return stringid.TruncateID(c.c.ID)
}
return c.c.ID
}

func (c *containerContext) Names() string {
c.addHeader(namesHeader)
names := stripNamePrefix(c.c.Names)
if c.trunc {
for _, name := range names {
if len(strings.Split(name, "/")) == 1 {
names = []string{name}
break
}
}
}
return strings.Join(names, ",")
}

func (c *containerContext) Image() string {
c.addHeader(imageHeader)
if c.c.Image == "" {
return "<no image>"
}
return c.c.Image
}

func (c *containerContext) Command() string {
c.addHeader(commandHeader)
command := c.c.Command
if c.trunc {
command = stringutils.Truncate(command, 20)
}
return strconv.Quote(command)
}

func (c *containerContext) CreatedAt() string {
c.addHeader(createdAtHeader)
return time.Unix(int64(c.c.Created), 0).String()
}

func (c *containerContext) RunningFor() string {
c.addHeader(runningForHeader)
createdAt := time.Unix(int64(c.c.Created), 0)
return units.HumanDuration(time.Now().UTC().Sub(createdAt))
}

func (c *containerContext) Ports() string {
c.addHeader(portsHeader)
return api.DisplayablePorts(c.c.Ports)
}

func (c *containerContext) Status() string {
c.addHeader(statusHeader)
return c.c.Status
}

func (c *containerContext) Size() string {
c.addHeader(sizeHeader)
srw := units.HumanSize(float64(c.c.SizeRw))
sv := units.HumanSize(float64(c.c.SizeRootFs))

sf := srw
if c.c.SizeRootFs > 0 {
sf = fmt.Sprintf("%s (virtual %s)", srw, sv)
}
return sf
}

func (c *containerContext) Labels() string {
c.addHeader(labelsHeader)
if c.c.Labels == nil {
return ""
}

var joinLabels []string
for k, v := range c.c.Labels {
joinLabels = append(joinLabels, fmt.Sprintf("%s=%s", k, v))
}
return strings.Join(joinLabels, ",")
}

func (c *containerContext) Label(name string) string {
n := strings.Split(name, ".")
r := strings.NewReplacer("-", " ", "_", " ")
h := r.Replace(n[len(n)-1])

c.addHeader(h)

if c.c.Labels == nil {
return ""
}
return c.c.Labels[name]
}

func (c *containerContext) fullHeader() string {
if c.header == nil {
return ""
}
return strings.Join(c.header, "\t")
}

func (c *containerContext) addHeader(header string) {
if c.header == nil {
c.header = []string{}
}
c.header = append(c.header, strings.ToUpper(header))
}

func customFormat(ctx Context, containers []types.Container) {
var (
table bool
header string
format = ctx.Format
buffer = bytes.NewBufferString("")
)

if strings.HasPrefix(ctx.Format, tableKey) {
table = true
format = format[len(tableKey):]
}

format = strings.Trim(format, " ")
r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
format = r.Replace(format)

if table && ctx.Size {
format += "\t{{.Size}}"
}

tmpl, err := template.New("ps template").Parse(format)
if err != nil {
buffer.WriteString(fmt.Sprintf("Invalid `docker ps` format: %v\n", err))
}

for _, container := range containers {
containerCtx := &containerContext{
trunc: ctx.Trunc,
c: container,
}
if err := tmpl.Execute(buffer, containerCtx); err != nil {
buffer = bytes.NewBufferString(fmt.Sprintf("Invalid `docker ps` format: %v\n", err))
break
}
if table && len(header) == 0 {
header = containerCtx.fullHeader()
}
buffer.WriteString("\n")
}

if table {
t := tabwriter.NewWriter(ctx.Output, 20, 1, 3, ' ', 0)
t.Write([]byte(header))
t.Write([]byte("\n"))
buffer.WriteTo(t)
t.Flush()
} else {
buffer.WriteTo(ctx.Output)
}
}

func stripNamePrefix(ss []string) []string {
for i, s := range ss {
ss[i] = s[1:]
}

return ss
}
Loading

0 comments on commit 40b9224

Please sign in to comment.