Skip to content

Commit

Permalink
Add --format support to images command
Browse files Browse the repository at this point in the history
- rename `api/client/ps` to `api/client/formatter`
- add a a image formatter

Signed-off-by: Vincent Demeester <[email protected]>
  • Loading branch information
vdemeester committed Dec 21, 2015
1 parent 67620bc commit 34a3c3c
Show file tree
Hide file tree
Showing 17 changed files with 1,126 additions and 479 deletions.
8 changes: 7 additions & 1 deletion api/client/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,17 @@ func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
}

// PsFormat returns the format string specified in the configuration.
// String contains columns and format specification, for example {{ID}\t{{Name}}.
// String contains columns and format specification, for example {{ID}}\t{{Name}}.
func (cli *DockerCli) PsFormat() string {
return cli.configFile.PsFormat
}

// ImagesFormat returns the format string specified in the configuration.
// String contains columns and format specification, for example {{ID}}\t{{Name}}.
func (cli *DockerCli) ImagesFormat() string {
return cli.configFile.ImagesFormat
}

// 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
96 changes: 79 additions & 17 deletions api/client/ps/custom.go → api/client/formatter/custom.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ps
package formatter

import (
"fmt"
Expand All @@ -16,26 +16,31 @@ import (
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"
containerIDHeader = "CONTAINER ID"
imageHeader = "IMAGE"
namesHeader = "NAMES"
commandHeader = "COMMAND"
createdSinceHeader = "CREATED"
createdAtHeader = "CREATED AT"
runningForHeader = "CREATED"
statusHeader = "STATUS"
portsHeader = "PORTS"
sizeHeader = "SIZE"
labelsHeader = "LABELS"
imageIDHeader = "IMAGE ID"
repositoryHeader = "REPOSITORY"
tagHeader = "TAG"
digestHeader = "DIGEST"
)

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

func (c *containerContext) ID() string {
c.addHeader(idHeader)
c.addHeader(containerIDHeader)
if c.trunc {
return stringid.TruncateID(c.c.ID)
}
Expand Down Expand Up @@ -137,14 +142,71 @@ func (c *containerContext) Label(name string) string {
return c.c.Labels[name]
}

func (c *containerContext) fullHeader() string {
type imageContext struct {
baseSubContext
trunc bool
i types.Image
repo string
tag string
digest string
}

func (c *imageContext) ID() string {
c.addHeader(imageIDHeader)
if c.trunc {
return stringid.TruncateID(c.i.ID)
}
return c.i.ID
}

func (c *imageContext) Repository() string {
c.addHeader(repositoryHeader)
return c.repo
}

func (c *imageContext) Tag() string {
c.addHeader(tagHeader)
return c.tag
}

func (c *imageContext) Digest() string {
c.addHeader(digestHeader)
return c.digest
}

func (c *imageContext) CreatedSince() string {
c.addHeader(createdSinceHeader)
createdAt := time.Unix(int64(c.i.Created), 0)
return units.HumanDuration(time.Now().UTC().Sub(createdAt))
}

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

func (c *imageContext) Size() string {
c.addHeader(sizeHeader)
return units.HumanSize(float64(c.i.Size))
}

type subContext interface {
fullHeader() string
addHeader(header string)
}

type baseSubContext struct {
header []string
}

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

func (c *containerContext) addHeader(header string) {
func (c *baseSubContext) addHeader(header string) {
if c.header == nil {
c.header = []string{}
}
Expand Down
108 changes: 87 additions & 21 deletions api/client/ps/custom_test.go → api/client/formatter/custom_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ps
package formatter

import (
"reflect"
Expand All @@ -22,8 +22,8 @@ func TestContainerPsContext(t *testing.T) {
expHeader string
call func() string
}{
{types.Container{ID: containerID}, true, stringid.TruncateID(containerID), idHeader, ctx.ID},
{types.Container{ID: containerID}, false, containerID, idHeader, ctx.ID},
{types.Container{ID: containerID}, true, stringid.TruncateID(containerID), containerIDHeader, ctx.ID},
{types.Container{ID: containerID}, false, containerID, containerIDHeader, ctx.ID},
{types.Container{Names: []string{"/foobar_baz"}}, true, "foobar_baz", namesHeader, ctx.Names},
{types.Container{Image: "ubuntu"}, true, "ubuntu", imageHeader, ctx.Image},
{types.Container{Image: "verylongimagename"}, true, "verylongimagename", imageHeader, ctx.Image},
Expand Down Expand Up @@ -62,24 +62,7 @@ func TestContainerPsContext(t *testing.T) {
ctx = containerContext{c: c.container, trunc: c.trunc}
v := c.call()
if strings.Contains(v, ",") {
// comma-separated values means probably a map input, which won't
// be guaranteed to have the same order as our expected value
// We'll create maps and use reflect.DeepEquals to check instead:
entriesMap := make(map[string]string)
expMap := make(map[string]string)
entries := strings.Split(v, ",")
expectedEntries := strings.Split(c.expValue, ",")
for _, entry := range entries {
keyval := strings.Split(entry, "=")
entriesMap[keyval[0]] = keyval[1]
}
for _, expected := range expectedEntries {
keyval := strings.Split(expected, "=")
expMap[keyval[0]] = keyval[1]
}
if !reflect.DeepEqual(expMap, entriesMap) {
t.Fatalf("Expected entries: %v, got: %v", c.expValue, v)
}
compareMultipleValues(t, v, c.expValue)
} else if v != c.expValue {
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
}
Expand Down Expand Up @@ -124,3 +107,86 @@ func TestContainerPsContext(t *testing.T) {
}

}

func TestImagesContext(t *testing.T) {
imageID := stringid.GenerateRandomID()
unix := time.Now().Unix()

var ctx imageContext
cases := []struct {
imageCtx imageContext
expValue string
expHeader string
call func() string
}{
{imageContext{
i: types.Image{ID: imageID},
trunc: true,
}, stringid.TruncateID(imageID), imageIDHeader, ctx.ID},
{imageContext{
i: types.Image{ID: imageID},
trunc: false,
}, imageID, imageIDHeader, ctx.ID},
{imageContext{
i: types.Image{Size: 10},
trunc: true,
}, "10 B", sizeHeader, ctx.Size},
{imageContext{
i: types.Image{Created: unix},
trunc: true,
}, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
// FIXME
// {imageContext{
// i: types.Image{Created: unix},
// trunc: true,
// }, units.HumanDuration(time.Unix(unix, 0)), createdSinceHeader, ctx.CreatedSince},
{imageContext{
i: types.Image{},
repo: "busybox",
}, "busybox", repositoryHeader, ctx.Repository},
{imageContext{
i: types.Image{},
tag: "latest",
}, "latest", tagHeader, ctx.Tag},
{imageContext{
i: types.Image{},
digest: "sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a",
}, "sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a", digestHeader, ctx.Digest},
}

for _, c := range cases {
ctx = c.imageCtx
v := c.call()
if strings.Contains(v, ",") {
compareMultipleValues(t, v, c.expValue)
} else if v != c.expValue {
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
}

h := ctx.fullHeader()
if h != c.expHeader {
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
}
}
}

func compareMultipleValues(t *testing.T, value, expected string) {
// comma-separated values means probably a map input, which won't
// be guaranteed to have the same order as our expected value
// We'll create maps and use reflect.DeepEquals to check instead:
entriesMap := make(map[string]string)
expMap := make(map[string]string)
entries := strings.Split(value, ",")
expectedEntries := strings.Split(expected, ",")
for _, entry := range entries {
keyval := strings.Split(entry, "=")
entriesMap[keyval[0]] = keyval[1]
}
for _, expected := range expectedEntries {
keyval := strings.Split(expected, "=")
expMap[keyval[0]] = keyval[1]
}
if !reflect.DeepEqual(expMap, entriesMap) {
t.Fatalf("Expected entries: %v, got: %v", expected, value)
}
}
Loading

0 comments on commit 34a3c3c

Please sign in to comment.