Merge pull request #30733 from yongtang/02022017-formatter-header

Allow `--format` to use different delim in `table` format
This commit is contained in:
Kenfe-Mickaël Laventure 2017-03-03 11:25:19 -08:00 committed by GitHub
commit 9fb77af2e6
17 changed files with 266 additions and 247 deletions

View File

@ -15,7 +15,7 @@ import (
)
const (
defaultContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}} ago\t{{.Status}}\t{{.Ports}}\t{{.Names}}"
defaultContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}"
containerIDHeader = "CONTAINER ID"
namesHeader = "NAMES"
@ -72,7 +72,17 @@ func ContainerWrite(ctx Context, containers []types.Container) error {
}
return nil
}
return ctx.Write(&containerContext{}, render)
return ctx.Write(newContainerContext(), render)
}
type containerHeaderContext map[string]string
func (c containerHeaderContext) Label(name string) string {
n := strings.Split(name, ".")
r := strings.NewReplacer("-", " ", "_", " ")
h := r.Replace(n[len(n)-1])
return h
}
type containerContext struct {
@ -81,12 +91,31 @@ type containerContext struct {
c types.Container
}
func newContainerContext() *containerContext {
containerCtx := containerContext{}
containerCtx.header = containerHeaderContext{
"ID": containerIDHeader,
"Names": namesHeader,
"Image": imageHeader,
"Command": commandHeader,
"CreatedAt": createdAtHeader,
"RunningFor": runningForHeader,
"Ports": portsHeader,
"Status": statusHeader,
"Size": sizeHeader,
"Labels": labelsHeader,
"Mounts": mountsHeader,
"LocalVolumes": localVolumes,
"Networks": networksHeader,
}
return &containerCtx
}
func (c *containerContext) MarshalJSON() ([]byte, error) {
return marshalJSON(c)
}
func (c *containerContext) ID() string {
c.AddHeader(containerIDHeader)
if c.trunc {
return stringid.TruncateID(c.c.ID)
}
@ -94,7 +123,6 @@ func (c *containerContext) ID() string {
}
func (c *containerContext) Names() string {
c.AddHeader(namesHeader)
names := stripNamePrefix(c.c.Names)
if c.trunc {
for _, name := range names {
@ -108,7 +136,6 @@ func (c *containerContext) Names() string {
}
func (c *containerContext) Image() string {
c.AddHeader(imageHeader)
if c.c.Image == "" {
return "<no image>"
}
@ -136,7 +163,6 @@ func (c *containerContext) Image() string {
}
func (c *containerContext) Command() string {
c.AddHeader(commandHeader)
command := c.c.Command
if c.trunc {
command = stringutils.Ellipsis(command, 20)
@ -145,28 +171,23 @@ func (c *containerContext) Command() string {
}
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))
return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
}
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.HumanSizeWithPrecision(float64(c.c.SizeRw), 3)
sv := units.HumanSizeWithPrecision(float64(c.c.SizeRootFs), 3)
@ -178,7 +199,6 @@ func (c *containerContext) Size() string {
}
func (c *containerContext) Labels() string {
c.AddHeader(labelsHeader)
if c.c.Labels == nil {
return ""
}
@ -191,12 +211,6 @@ func (c *containerContext) Labels() string {
}
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 ""
}
@ -204,8 +218,6 @@ func (c *containerContext) Label(name string) string {
}
func (c *containerContext) Mounts() string {
c.AddHeader(mountsHeader)
var name string
var mounts []string
for _, m := range c.c.Mounts {
@ -223,8 +235,6 @@ func (c *containerContext) Mounts() string {
}
func (c *containerContext) LocalVolumes() string {
c.AddHeader(localVolumes)
count := 0
for _, m := range c.c.Mounts {
if m.Driver == "local" {
@ -236,8 +246,6 @@ func (c *containerContext) LocalVolumes() string {
}
func (c *containerContext) Networks() string {
c.AddHeader(networksHeader)
if c.c.NetworkSettings == nil {
return ""
}

View File

@ -22,22 +22,20 @@ func TestContainerPsContext(t *testing.T) {
container types.Container
trunc bool
expValue string
expHeader string
call func() string
}{
{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},
{types.Container{Image: "verylongimagename"}, false, "verylongimagename", imageHeader, ctx.Image},
{types.Container{ID: containerID}, true, stringid.TruncateID(containerID), ctx.ID},
{types.Container{ID: containerID}, false, containerID, ctx.ID},
{types.Container{Names: []string{"/foobar_baz"}}, true, "foobar_baz", ctx.Names},
{types.Container{Image: "ubuntu"}, true, "ubuntu", ctx.Image},
{types.Container{Image: "verylongimagename"}, true, "verylongimagename", ctx.Image},
{types.Container{Image: "verylongimagename"}, false, "verylongimagename", ctx.Image},
{types.Container{
Image: "a5a665ff33eced1e0803148700880edab4",
ImageID: "a5a665ff33eced1e0803148700880edab4269067ed77e27737a708d0d293fbf5",
},
true,
"a5a665ff33ec",
imageHeader,
ctx.Image,
},
{types.Container{
@ -46,19 +44,18 @@ func TestContainerPsContext(t *testing.T) {
},
false,
"a5a665ff33eced1e0803148700880edab4",
imageHeader,
ctx.Image,
},
{types.Container{Image: ""}, true, "<no image>", imageHeader, ctx.Image},
{types.Container{Command: "sh -c 'ls -la'"}, true, `"sh -c 'ls -la'"`, commandHeader, ctx.Command},
{types.Container{Created: unix}, true, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
{types.Container{Ports: []types.Port{{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}}}, true, "8080/tcp", portsHeader, ctx.Ports},
{types.Container{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status},
{types.Container{SizeRw: 10}, true, "10B", sizeHeader, ctx.Size},
{types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10B (virtual 20B)", sizeHeader, ctx.Size},
{types.Container{}, true, "", labelsHeader, ctx.Labels},
{types.Container{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", labelsHeader, ctx.Labels},
{types.Container{Created: unix}, true, "About a minute", runningForHeader, ctx.RunningFor},
{types.Container{Image: ""}, true, "<no image>", ctx.Image},
{types.Container{Command: "sh -c 'ls -la'"}, true, `"sh -c 'ls -la'"`, ctx.Command},
{types.Container{Created: unix}, true, time.Unix(unix, 0).String(), ctx.CreatedAt},
{types.Container{Ports: []types.Port{{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}}}, true, "8080/tcp", ctx.Ports},
{types.Container{Status: "RUNNING"}, true, "RUNNING", ctx.Status},
{types.Container{SizeRw: 10}, true, "10B", ctx.Size},
{types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10B (virtual 20B)", ctx.Size},
{types.Container{}, true, "", ctx.Labels},
{types.Container{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", ctx.Labels},
{types.Container{Created: unix}, true, "About a minute ago", ctx.RunningFor},
{types.Container{
Mounts: []types.MountPoint{
{
@ -67,7 +64,7 @@ func TestContainerPsContext(t *testing.T) {
Source: "/a/path",
},
},
}, true, "this-is-a-lo...", mountsHeader, ctx.Mounts},
}, true, "this-is-a-lo...", ctx.Mounts},
{types.Container{
Mounts: []types.MountPoint{
{
@ -75,7 +72,7 @@ func TestContainerPsContext(t *testing.T) {
Source: "/a/path",
},
},
}, false, "/a/path", mountsHeader, ctx.Mounts},
}, false, "/a/path", ctx.Mounts},
{types.Container{
Mounts: []types.MountPoint{
{
@ -84,7 +81,7 @@ func TestContainerPsContext(t *testing.T) {
Source: "/a/path",
},
},
}, false, "733908409c91817de8e92b0096373245f329f19a88e2c849f02460e9b3d1c203", mountsHeader, ctx.Mounts},
}, false, "733908409c91817de8e92b0096373245f329f19a88e2c849f02460e9b3d1c203", ctx.Mounts},
}
for _, c := range cases {
@ -95,11 +92,6 @@ func TestContainerPsContext(t *testing.T) {
} 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)
}
}
c1 := types.Container{Labels: map[string]string{"com.docker.swarm.swarm-id": "33", "com.docker.swarm.node_name": "ubuntu"}}
@ -115,12 +107,6 @@ func TestContainerPsContext(t *testing.T) {
t.Fatalf("Expected ubuntu, was %s\n", node)
}
h := ctx.FullHeader()
if h != "SWARM ID\tNODE NAME" {
t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
}
c2 := types.Container{}
ctx = containerContext{c: c2, trunc: true}
@ -128,13 +114,6 @@ func TestContainerPsContext(t *testing.T) {
if label != "" {
t.Fatalf("Expected an empty string, was %s", label)
}
ctx = containerContext{c: c2, trunc: true}
FullHeader := ctx.FullHeader()
if FullHeader != "" {
t.Fatalf("Expected FullHeader to be empty, was %s", FullHeader)
}
}
func TestContainerContextWrite(t *testing.T) {
@ -247,6 +226,14 @@ size: 0B
Context{Format: NewContainerFormat("{{.Image}}", false, true)},
"ubuntu\nubuntu\n",
},
// Special headers for customerized table format
{
Context{Format: NewContainerFormat(`table {{truncate .ID 5}}\t{{json .Image}} {{.RunningFor}}/{{title .Status}}/{{pad .Ports 2 2}}.{{upper .Names}} {{lower .Status}}`, false, true)},
`CONTAINER ID IMAGE CREATED/STATUS/ PORTS .NAMES STATUS
conta "ubuntu" 24 hours ago//.FOOBAR_BAZ
conta "ubuntu" 24 hours ago//.FOOBAR_BAR
`,
},
}
for _, testcase := range cases {
@ -333,8 +320,8 @@ func TestContainerContextWriteJSON(t *testing.T) {
}
expectedCreated := time.Unix(unix, 0).String()
expectedJSONs := []map[string]interface{}{
{"Command": "\"\"", "CreatedAt": expectedCreated, "ID": "containerID1", "Image": "ubuntu", "Labels": "", "LocalVolumes": "0", "Mounts": "", "Names": "foobar_baz", "Networks": "", "Ports": "", "RunningFor": "About a minute", "Size": "0B", "Status": ""},
{"Command": "\"\"", "CreatedAt": expectedCreated, "ID": "containerID2", "Image": "ubuntu", "Labels": "", "LocalVolumes": "0", "Mounts": "", "Names": "foobar_bar", "Networks": "", "Ports": "", "RunningFor": "About a minute", "Size": "0B", "Status": ""},
{"Command": "\"\"", "CreatedAt": expectedCreated, "ID": "containerID1", "Image": "ubuntu", "Labels": "", "LocalVolumes": "0", "Mounts": "", "Names": "foobar_baz", "Networks": "", "Ports": "", "RunningFor": "About a minute ago", "Size": "0B", "Status": ""},
{"Command": "\"\"", "CreatedAt": expectedCreated, "ID": "containerID2", "Image": "ubuntu", "Labels": "", "LocalVolumes": "0", "Mounts": "", "Names": "foobar_bar", "Networks": "", "Ports": "", "RunningFor": "About a minute ago", "Size": "0B", "Status": ""},
}
out := bytes.NewBufferString("")
err := ContainerWrite(Context{Format: "{{json .}}", Output: out}, containers)

View File

@ -1,9 +1,5 @@
package formatter
import (
"strings"
)
const (
imageHeader = "IMAGE"
createdSinceHeader = "CREATED"
@ -16,29 +12,17 @@ const (
)
type subContext interface {
FullHeader() string
AddHeader(header string)
FullHeader() interface{}
}
// HeaderContext provides the subContext interface for managing headers
type HeaderContext struct {
header []string
header interface{}
}
// FullHeader returns the header as a string
func (c *HeaderContext) FullHeader() string {
if c.header == nil {
return ""
}
return strings.Join(c.header, "\t")
}
// AddHeader adds another column to the header
func (c *HeaderContext) AddHeader(header string) {
if c.header == nil {
c.header = []string{}
}
c.header = append(c.header, strings.ToUpper(header))
// FullHeader returns the header as an interface
func (c *HeaderContext) FullHeader() interface{} {
return c.header
}
func stripNamePrefix(ss []string) []string {

View File

@ -77,7 +77,15 @@ func (ctx *DiskUsageContext) Write() {
return
}
ctx.postFormat(tmpl, &diskUsageContainersContext{containers: []*types.Container{}})
diskUsageContainersCtx := diskUsageContainersContext{containers: []*types.Container{}}
diskUsageContainersCtx.header = map[string]string{
"Type": typeHeader,
"TotalCount": totalHeader,
"Active": activeHeader,
"Size": sizeHeader,
"Reclaimable": reclaimableHeader,
}
ctx.postFormat(tmpl, &diskUsageContainersCtx)
return
}
@ -114,7 +122,7 @@ func (ctx *DiskUsageContext) Write() {
return
}
}
ctx.postFormat(tmpl, &imageContext{})
ctx.postFormat(tmpl, newImageContext())
// Now containers
ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
@ -133,7 +141,7 @@ func (ctx *DiskUsageContext) Write() {
return
}
}
ctx.postFormat(tmpl, &containerContext{})
ctx.postFormat(tmpl, newContainerContext())
// And volumes
ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n"))
@ -149,7 +157,7 @@ func (ctx *DiskUsageContext) Write() {
return
}
}
ctx.postFormat(tmpl, &volumeContext{v: types.Volume{}})
ctx.postFormat(tmpl, newVolumeContext())
}
type diskUsageImagesContext struct {
@ -163,17 +171,14 @@ func (c *diskUsageImagesContext) MarshalJSON() ([]byte, error) {
}
func (c *diskUsageImagesContext) Type() string {
c.AddHeader(typeHeader)
return "Images"
}
func (c *diskUsageImagesContext) TotalCount() string {
c.AddHeader(totalHeader)
return fmt.Sprintf("%d", len(c.images))
}
func (c *diskUsageImagesContext) Active() string {
c.AddHeader(activeHeader)
used := 0
for _, i := range c.images {
if i.Containers > 0 {
@ -185,7 +190,6 @@ func (c *diskUsageImagesContext) Active() string {
}
func (c *diskUsageImagesContext) Size() string {
c.AddHeader(sizeHeader)
return units.HumanSize(float64(c.totalSize))
}
@ -193,7 +197,6 @@ func (c *diskUsageImagesContext) Size() string {
func (c *diskUsageImagesContext) Reclaimable() string {
var used int64
c.AddHeader(reclaimableHeader)
for _, i := range c.images {
if i.Containers != 0 {
if i.VirtualSize == -1 || i.SharedSize == -1 {
@ -221,12 +224,10 @@ func (c *diskUsageContainersContext) MarshalJSON() ([]byte, error) {
}
func (c *diskUsageContainersContext) Type() string {
c.AddHeader(typeHeader)
return "Containers"
}
func (c *diskUsageContainersContext) TotalCount() string {
c.AddHeader(totalHeader)
return fmt.Sprintf("%d", len(c.containers))
}
@ -237,7 +238,6 @@ func (c *diskUsageContainersContext) isActive(container types.Container) bool {
}
func (c *diskUsageContainersContext) Active() string {
c.AddHeader(activeHeader)
used := 0
for _, container := range c.containers {
if c.isActive(*container) {
@ -251,7 +251,6 @@ func (c *diskUsageContainersContext) Active() string {
func (c *diskUsageContainersContext) Size() string {
var size int64
c.AddHeader(sizeHeader)
for _, container := range c.containers {
size += container.SizeRw
}
@ -263,7 +262,6 @@ func (c *diskUsageContainersContext) Reclaimable() string {
var reclaimable int64
var totalSize int64
c.AddHeader(reclaimableHeader)
for _, container := range c.containers {
if !c.isActive(*container) {
reclaimable += container.SizeRw
@ -289,17 +287,14 @@ func (c *diskUsageVolumesContext) MarshalJSON() ([]byte, error) {
}
func (c *diskUsageVolumesContext) Type() string {
c.AddHeader(typeHeader)
return "Local Volumes"
}
func (c *diskUsageVolumesContext) TotalCount() string {
c.AddHeader(totalHeader)
return fmt.Sprintf("%d", len(c.volumes))
}
func (c *diskUsageVolumesContext) Active() string {
c.AddHeader(activeHeader)
used := 0
for _, v := range c.volumes {
@ -314,7 +309,6 @@ func (c *diskUsageVolumesContext) Active() string {
func (c *diskUsageVolumesContext) Size() string {
var size int64
c.AddHeader(sizeHeader)
for _, v := range c.volumes {
if v.UsageData.Size != -1 {
size += v.UsageData.Size
@ -328,7 +322,6 @@ func (c *diskUsageVolumesContext) Reclaimable() string {
var reclaimable int64
var totalSize int64
c.AddHeader(reclaimableHeader)
for _, v := range c.volumes {
if v.UsageData.Size != -1 {
if v.UsageData.RefCount == 0 {

View File

@ -0,0 +1,47 @@
package formatter
import (
"bytes"
"testing"
"github.com/docker/docker/pkg/testutil/assert"
)
func TestDiskUsageContextFormatWrite(t *testing.T) {
// Check default output format (verbose and non-verbose mode) for table headers
cases := []struct {
context DiskUsageContext
expected string
}{
{
DiskUsageContext{Verbose: false},
`TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 0 0 0B 0B
Containers 0 0 0B 0B
Local Volumes 0 0 0B 0B
`,
},
{
DiskUsageContext{Verbose: true},
`Images space usage:
REPOSITORY TAG IMAGE ID CREATED ago SIZE SHARED SIZE UNIQUE SiZE CONTAINERS
Containers space usage:
CONTAINER ID IMAGE COMMAND LOCAL VOLUMES SIZE CREATED ago STATUS NAMES
Local Volumes space usage:
VOLUME NAME LINKS SIZE
`,
},
}
for _, testcase := range cases {
out := bytes.NewBufferString("")
testcase.context.Output = out
testcase.context.Write()
assert.Equal(t, out.String(), testcase.expected)
}
}

View File

@ -44,7 +44,7 @@ type Context struct {
// internal element
finalFormat string
header string
header interface{}
buffer *bytes.Buffer
}
@ -71,14 +71,10 @@ func (c *Context) parseFormat() (*template.Template, error) {
func (c *Context) postFormat(tmpl *template.Template, subContext subContext) {
if c.Format.IsTable() {
if len(c.header) == 0 {
// if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template
tmpl.Execute(bytes.NewBufferString(""), subContext)
c.header = subContext.FullHeader()
}
t := tabwriter.NewWriter(c.Output, 20, 1, 3, ' ', 0)
t.Write([]byte(c.header))
buffer := bytes.NewBufferString("")
tmpl.Funcs(templates.HeaderFunctions).Execute(buffer, subContext.FullHeader())
buffer.WriteTo(t)
t.Write([]byte("\n"))
c.buffer.WriteTo(t)
t.Flush()
@ -91,7 +87,7 @@ func (c *Context) contextFormat(tmpl *template.Template, subContext subContext)
if err := tmpl.Execute(c.buffer, subContext); err != nil {
return fmt.Errorf("Template parsing error: %v\n", err)
}
if c.Format.IsTable() && len(c.header) == 0 {
if c.Format.IsTable() && c.header != nil {
c.header = subContext.FullHeader()
}
c.buffer.WriteString("\n")

View File

@ -11,8 +11,8 @@ import (
)
const (
defaultImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}"
defaultImageTableFormatWithDigest = "table {{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.Size}}"
defaultImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}}\t{{.Size}}"
defaultImageTableFormatWithDigest = "table {{.Repository}}\t{{.Tag}}\t{{.Digest}}\t{{.ID}}\t{{.CreatedSince}}\t{{.Size}}"
imageIDHeader = "IMAGE ID"
repositoryHeader = "REPOSITORY"
@ -76,7 +76,7 @@ func ImageWrite(ctx ImageContext, images []types.ImageSummary) error {
render := func(format func(subContext subContext) error) error {
return imageFormat(ctx, images, format)
}
return ctx.Write(&imageContext{}, render)
return ctx.Write(newImageContext(), render)
}
func imageFormat(ctx ImageContext, images []types.ImageSummary, format func(subContext subContext) error) error {
@ -192,12 +192,29 @@ type imageContext struct {
digest string
}
func newImageContext() *imageContext {
imageCtx := imageContext{}
imageCtx.header = map[string]string{
"ID": imageIDHeader,
"Repository": repositoryHeader,
"Tag": tagHeader,
"Digest": digestHeader,
"CreatedSince": createdSinceHeader,
"CreatedAt": createdAtHeader,
"Size": sizeHeader,
"Containers": containersHeader,
"VirtualSize": sizeHeader,
"SharedSize": sharedSizeHeader,
"UniqueSize": uniqueSizeHeader,
}
return &imageCtx
}
func (c *imageContext) MarshalJSON() ([]byte, error) {
return marshalJSON(c)
}
func (c *imageContext) ID() string {
c.AddHeader(imageIDHeader)
if c.trunc {
return stringid.TruncateID(c.i.ID)
}
@ -205,38 +222,31 @@ func (c *imageContext) ID() string {
}
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))
return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
}
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.HumanSizeWithPrecision(float64(c.i.Size), 3)
}
func (c *imageContext) Containers() string {
c.AddHeader(containersHeader)
if c.i.Containers == -1 {
return "N/A"
}
@ -244,12 +254,10 @@ func (c *imageContext) Containers() string {
}
func (c *imageContext) VirtualSize() string {
c.AddHeader(sizeHeader)
return units.HumanSize(float64(c.i.VirtualSize))
}
func (c *imageContext) SharedSize() string {
c.AddHeader(sharedSizeHeader)
if c.i.SharedSize == -1 {
return "N/A"
}
@ -257,7 +265,6 @@ func (c *imageContext) SharedSize() string {
}
func (c *imageContext) UniqueSize() string {
c.AddHeader(uniqueSizeHeader)
if c.i.VirtualSize == -1 || c.i.SharedSize == -1 {
return "N/A"
}

View File

@ -20,25 +20,24 @@ func TestImageContext(t *testing.T) {
cases := []struct {
imageCtx imageContext
expValue string
expHeader string
call func() string
}{
{imageContext{
i: types.ImageSummary{ID: imageID},
trunc: true,
}, stringid.TruncateID(imageID), imageIDHeader, ctx.ID},
}, stringid.TruncateID(imageID), ctx.ID},
{imageContext{
i: types.ImageSummary{ID: imageID},
trunc: false,
}, imageID, imageIDHeader, ctx.ID},
}, imageID, ctx.ID},
{imageContext{
i: types.ImageSummary{Size: 10, VirtualSize: 10},
trunc: true,
}, "10B", sizeHeader, ctx.Size},
}, "10B", ctx.Size},
{imageContext{
i: types.ImageSummary{Created: unix},
trunc: true,
}, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
}, time.Unix(unix, 0).String(), ctx.CreatedAt},
// FIXME
// {imageContext{
// i: types.ImageSummary{Created: unix},
@ -47,15 +46,15 @@ func TestImageContext(t *testing.T) {
{imageContext{
i: types.ImageSummary{},
repo: "busybox",
}, "busybox", repositoryHeader, ctx.Repository},
}, "busybox", ctx.Repository},
{imageContext{
i: types.ImageSummary{},
tag: "latest",
}, "latest", tagHeader, ctx.Tag},
}, "latest", ctx.Tag},
{imageContext{
i: types.ImageSummary{},
digest: "sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a",
}, "sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a", digestHeader, ctx.Digest},
}, "sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a", ctx.Digest},
}
for _, c := range cases {
@ -66,11 +65,6 @@ func TestImageContext(t *testing.T) {
} 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)
}
}
}

View File

@ -44,7 +44,28 @@ func NetworkWrite(ctx Context, networks []types.NetworkResource) error {
}
return nil
}
return ctx.Write(&networkContext{}, render)
networkCtx := networkContext{}
networkCtx.header = networkHeaderContext{
"ID": networkIDHeader,
"Name": nameHeader,
"Driver": driverHeader,
"Scope": scopeHeader,
"IPv6": ipv6Header,
"Internal": internalHeader,
"Labels": labelsHeader,
"CreatedAt": createdAtHeader,
}
return ctx.Write(&networkCtx, render)
}
type networkHeaderContext map[string]string
func (c networkHeaderContext) Label(name string) string {
n := strings.Split(name, ".")
r := strings.NewReplacer("-", " ", "_", " ")
h := r.Replace(n[len(n)-1])
return h
}
type networkContext struct {
@ -58,7 +79,6 @@ func (c *networkContext) MarshalJSON() ([]byte, error) {
}
func (c *networkContext) ID() string {
c.AddHeader(networkIDHeader)
if c.trunc {
return stringid.TruncateID(c.n.ID)
}
@ -66,32 +86,26 @@ func (c *networkContext) ID() string {
}
func (c *networkContext) Name() string {
c.AddHeader(nameHeader)
return c.n.Name
}
func (c *networkContext) Driver() string {
c.AddHeader(driverHeader)
return c.n.Driver
}
func (c *networkContext) Scope() string {
c.AddHeader(scopeHeader)
return c.n.Scope
}
func (c *networkContext) IPv6() string {
c.AddHeader(ipv6Header)
return fmt.Sprintf("%v", c.n.EnableIPv6)
}
func (c *networkContext) Internal() string {
c.AddHeader(internalHeader)
return fmt.Sprintf("%v", c.n.Internal)
}
func (c *networkContext) Labels() string {
c.AddHeader(labelsHeader)
if c.n.Labels == nil {
return ""
}
@ -104,12 +118,6 @@ func (c *networkContext) Labels() string {
}
func (c *networkContext) Label(name string) string {
n := strings.Split(name, ".")
r := strings.NewReplacer("-", " ", "_", " ")
h := r.Replace(n[len(n)-1])
c.AddHeader(h)
if c.n.Labels == nil {
return ""
}
@ -117,6 +125,5 @@ func (c *networkContext) Label(name string) string {
}
func (c *networkContext) CreatedAt() string {
c.AddHeader(createdAtHeader)
return c.n.Created.String()
}

View File

@ -19,41 +19,40 @@ func TestNetworkContext(t *testing.T) {
cases := []struct {
networkCtx networkContext
expValue string
expHeader string
call func() string
}{
{networkContext{
n: types.NetworkResource{ID: networkID},
trunc: false,
}, networkID, networkIDHeader, ctx.ID},
}, networkID, ctx.ID},
{networkContext{
n: types.NetworkResource{ID: networkID},
trunc: true,
}, stringid.TruncateID(networkID), networkIDHeader, ctx.ID},
}, stringid.TruncateID(networkID), ctx.ID},
{networkContext{
n: types.NetworkResource{Name: "network_name"},
}, "network_name", nameHeader, ctx.Name},
}, "network_name", ctx.Name},
{networkContext{
n: types.NetworkResource{Driver: "driver_name"},
}, "driver_name", driverHeader, ctx.Driver},
}, "driver_name", ctx.Driver},
{networkContext{
n: types.NetworkResource{EnableIPv6: true},
}, "true", ipv6Header, ctx.IPv6},
}, "true", ctx.IPv6},
{networkContext{
n: types.NetworkResource{EnableIPv6: false},
}, "false", ipv6Header, ctx.IPv6},
}, "false", ctx.IPv6},
{networkContext{
n: types.NetworkResource{Internal: true},
}, "true", internalHeader, ctx.Internal},
}, "true", ctx.Internal},
{networkContext{
n: types.NetworkResource{Internal: false},
}, "false", internalHeader, ctx.Internal},
}, "false", ctx.Internal},
{networkContext{
n: types.NetworkResource{},
}, "", labelsHeader, ctx.Labels},
}, "", ctx.Labels},
{networkContext{
n: types.NetworkResource{Labels: map[string]string{"label1": "value1", "label2": "value2"}},
}, "label1=value1,label2=value2", labelsHeader, ctx.Labels},
}, "label1=value1,label2=value2", ctx.Labels},
}
for _, c := range cases {
@ -64,11 +63,6 @@ func TestNetworkContext(t *testing.T) {
} 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)
}
}
}

View File

@ -44,7 +44,15 @@ func PluginWrite(ctx Context, plugins []*types.Plugin) error {
}
return nil
}
return ctx.Write(&pluginContext{}, render)
pluginCtx := pluginContext{}
pluginCtx.header = map[string]string{
"ID": pluginIDHeader,
"Name": nameHeader,
"Description": descriptionHeader,
"Enabled": enabledHeader,
"PluginReference": imageHeader,
}
return ctx.Write(&pluginCtx, render)
}
type pluginContext struct {
@ -58,7 +66,6 @@ func (c *pluginContext) MarshalJSON() ([]byte, error) {
}
func (c *pluginContext) ID() string {
c.AddHeader(pluginIDHeader)
if c.trunc {
return stringid.TruncateID(c.p.ID)
}
@ -66,12 +73,10 @@ func (c *pluginContext) ID() string {
}
func (c *pluginContext) Name() string {
c.AddHeader(nameHeader)
return c.p.Name
}
func (c *pluginContext) Description() string {
c.AddHeader(descriptionHeader)
desc := strings.Replace(c.p.Config.Description, "\n", "", -1)
desc = strings.Replace(desc, "\r", "", -1)
if c.trunc {
@ -82,11 +87,9 @@ func (c *pluginContext) Description() string {
}
func (c *pluginContext) Enabled() bool {
c.AddHeader(enabledHeader)
return c.p.Enabled
}
func (c *pluginContext) PluginReference() string {
c.AddHeader(imageHeader)
return c.p.PluginReference
}

View File

@ -18,23 +18,22 @@ func TestPluginContext(t *testing.T) {
cases := []struct {
pluginCtx pluginContext
expValue string
expHeader string
call func() string
}{
{pluginContext{
p: types.Plugin{ID: pluginID},
trunc: false,
}, pluginID, pluginIDHeader, ctx.ID},
}, pluginID, ctx.ID},
{pluginContext{
p: types.Plugin{ID: pluginID},
trunc: true,
}, stringid.TruncateID(pluginID), pluginIDHeader, ctx.ID},
}, stringid.TruncateID(pluginID), ctx.ID},
{pluginContext{
p: types.Plugin{Name: "plugin_name"},
}, "plugin_name", nameHeader, ctx.Name},
}, "plugin_name", ctx.Name},
{pluginContext{
p: types.Plugin{Config: types.PluginConfig{Description: "plugin_description"}},
}, "plugin_description", descriptionHeader, ctx.Description},
}, "plugin_description", ctx.Description},
}
for _, c := range cases {
@ -45,11 +44,6 @@ func TestPluginContext(t *testing.T) {
} 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)
}
}
}

View File

@ -388,7 +388,15 @@ func ServiceListWrite(ctx Context, services []swarm.Service, info map[string]Ser
}
return nil
}
return ctx.Write(&serviceContext{}, render)
serviceCtx := serviceContext{}
serviceCtx.header = map[string]string{
"ID": serviceIDHeader,
"Name": nameHeader,
"Mode": modeHeader,
"Replicas": replicasHeader,
"Image": imageHeader,
}
return ctx.Write(&serviceCtx, render)
}
type serviceContext struct {
@ -403,27 +411,22 @@ func (c *serviceContext) MarshalJSON() ([]byte, error) {
}
func (c *serviceContext) ID() string {
c.AddHeader(serviceIDHeader)
return stringid.TruncateID(c.service.ID)
}
func (c *serviceContext) Name() string {
c.AddHeader(nameHeader)
return c.service.Spec.Name
}
func (c *serviceContext) Mode() string {
c.AddHeader(modeHeader)
return c.mode
}
func (c *serviceContext) Replicas() string {
c.AddHeader(replicasHeader)
return c.replicas
}
func (c *serviceContext) Image() string {
c.AddHeader(imageHeader)
image := c.service.Spec.TaskTemplate.ContainerSpec.Image
if ref, err := reference.ParseNormalizedNamed(image); err == nil {
// update image string for display, (strips any digest)

View File

@ -129,7 +129,24 @@ func ContainerStatsWrite(ctx Context, containerStats []StatsEntry, osType string
}
return nil
}
return ctx.Write(&containerStatsContext{os: osType}, render)
memUsage := memUseHeader
if osType == winOSType {
memUsage = winMemUseHeader
}
containerStatsCtx := containerStatsContext{}
containerStatsCtx.header = map[string]string{
"Container": containerHeader,
"Name": nameHeader,
"ID": containerIDHeader,
"CPUPerc": cpuPercHeader,
"MemUsage": memUsage,
"MemPerc": memPercHeader,
"NetIO": netIOHeader,
"BlockIO": blockIOHeader,
"PIDs": pidsHeader,
}
containerStatsCtx.os = osType
return ctx.Write(&containerStatsCtx, render)
}
type containerStatsContext struct {
@ -143,12 +160,10 @@ func (c *containerStatsContext) MarshalJSON() ([]byte, error) {
}
func (c *containerStatsContext) Container() string {
c.AddHeader(containerHeader)
return c.s.Container
}
func (c *containerStatsContext) Name() string {
c.AddHeader(nameHeader)
if len(c.s.Name) > 1 {
return c.s.Name[1:]
}
@ -156,12 +171,10 @@ func (c *containerStatsContext) Name() string {
}
func (c *containerStatsContext) ID() string {
c.AddHeader(containerIDHeader)
return c.s.ID
}
func (c *containerStatsContext) CPUPerc() string {
c.AddHeader(cpuPercHeader)
if c.s.IsInvalid {
return fmt.Sprintf("--")
}
@ -169,11 +182,6 @@ func (c *containerStatsContext) CPUPerc() string {
}
func (c *containerStatsContext) MemUsage() string {
header := memUseHeader
if c.os == winOSType {
header = winMemUseHeader
}
c.AddHeader(header)
if c.s.IsInvalid {
return fmt.Sprintf("-- / --")
}
@ -184,8 +192,6 @@ func (c *containerStatsContext) MemUsage() string {
}
func (c *containerStatsContext) MemPerc() string {
header := memPercHeader
c.AddHeader(header)
if c.s.IsInvalid || c.os == winOSType {
return fmt.Sprintf("--")
}
@ -193,7 +199,6 @@ func (c *containerStatsContext) MemPerc() string {
}
func (c *containerStatsContext) NetIO() string {
c.AddHeader(netIOHeader)
if c.s.IsInvalid {
return fmt.Sprintf("--")
}
@ -201,7 +206,6 @@ func (c *containerStatsContext) NetIO() string {
}
func (c *containerStatsContext) BlockIO() string {
c.AddHeader(blockIOHeader)
if c.s.IsInvalid {
return fmt.Sprintf("--")
}
@ -209,7 +213,6 @@ func (c *containerStatsContext) BlockIO() string {
}
func (c *containerStatsContext) PIDs() string {
c.AddHeader(pidsHeader)
if c.s.IsInvalid || c.os == winOSType {
return fmt.Sprintf("--")
}

View File

@ -42,11 +42,6 @@ func TestContainerStatsContext(t *testing.T) {
if v := te.call(); v != te.expValue {
t.Fatalf("Expected %q, got %q", te.expValue, v)
}
h := ctx.FullHeader()
if h != te.expHeader {
t.Fatalf("Expected %q, got %q", te.expHeader, h)
}
}
}

View File

@ -45,7 +45,17 @@ func VolumeWrite(ctx Context, volumes []*types.Volume) error {
}
return nil
}
return ctx.Write(&volumeContext{}, render)
return ctx.Write(newVolumeContext(), render)
}
type volumeHeaderContext map[string]string
func (c volumeHeaderContext) Label(name string) string {
n := strings.Split(name, ".")
r := strings.NewReplacer("-", " ", "_", " ")
h := r.Replace(n[len(n)-1])
return h
}
type volumeContext struct {
@ -53,32 +63,41 @@ type volumeContext struct {
v types.Volume
}
func newVolumeContext() *volumeContext {
volumeCtx := volumeContext{}
volumeCtx.header = volumeHeaderContext{
"Name": volumeNameHeader,
"Driver": driverHeader,
"Scope": scopeHeader,
"Mountpoint": mountpointHeader,
"Labels": labelsHeader,
"Links": linksHeader,
"Size": sizeHeader,
}
return &volumeCtx
}
func (c *volumeContext) MarshalJSON() ([]byte, error) {
return marshalJSON(c)
}
func (c *volumeContext) Name() string {
c.AddHeader(volumeNameHeader)
return c.v.Name
}
func (c *volumeContext) Driver() string {
c.AddHeader(driverHeader)
return c.v.Driver
}
func (c *volumeContext) Scope() string {
c.AddHeader(scopeHeader)
return c.v.Scope
}
func (c *volumeContext) Mountpoint() string {
c.AddHeader(mountpointHeader)
return c.v.Mountpoint
}
func (c *volumeContext) Labels() string {
c.AddHeader(labelsHeader)
if c.v.Labels == nil {
return ""
}
@ -91,13 +110,6 @@ func (c *volumeContext) Labels() string {
}
func (c *volumeContext) Label(name string) string {
n := strings.Split(name, ".")
r := strings.NewReplacer("-", " ", "_", " ")
h := r.Replace(n[len(n)-1])
c.AddHeader(h)
if c.v.Labels == nil {
return ""
}
@ -105,7 +117,6 @@ func (c *volumeContext) Label(name string) string {
}
func (c *volumeContext) Links() string {
c.AddHeader(linksHeader)
if c.v.UsageData == nil {
return "N/A"
}
@ -113,7 +124,6 @@ func (c *volumeContext) Links() string {
}
func (c *volumeContext) Size() string {
c.AddHeader(sizeHeader)
if c.v.UsageData == nil {
return "N/A"
}

View File

@ -18,27 +18,26 @@ func TestVolumeContext(t *testing.T) {
cases := []struct {
volumeCtx volumeContext
expValue string
expHeader string
call func() string
}{
{volumeContext{
v: types.Volume{Name: volumeName},
}, volumeName, volumeNameHeader, ctx.Name},
}, volumeName, ctx.Name},
{volumeContext{
v: types.Volume{Driver: "driver_name"},
}, "driver_name", driverHeader, ctx.Driver},
}, "driver_name", ctx.Driver},
{volumeContext{
v: types.Volume{Scope: "local"},
}, "local", scopeHeader, ctx.Scope},
}, "local", ctx.Scope},
{volumeContext{
v: types.Volume{Mountpoint: "mountpoint"},
}, "mountpoint", mountpointHeader, ctx.Mountpoint},
}, "mountpoint", ctx.Mountpoint},
{volumeContext{
v: types.Volume{},
}, "", labelsHeader, ctx.Labels},
}, "", ctx.Labels},
{volumeContext{
v: types.Volume{Labels: map[string]string{"label1": "value1", "label2": "value2"}},
}, "label1=value1,label2=value2", labelsHeader, ctx.Labels},
}, "label1=value1,label2=value2", ctx.Labels},
}
for _, c := range cases {
@ -49,11 +48,6 @@ func TestVolumeContext(t *testing.T) {
} 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)
}
}
}