Allow --format
to use different delim in table
format
This fix is an attempt to address https://github.com/docker/docker/pull/28213#issuecomment-273840405 Currently when specify table format with table `--format "table {{.ID}}..."`, the delimiter in the header section of the table is always `"\t"`. That is actually different from the content of the table as the delimiter could be anything (or even contatenated with `.`, for example): ``` $ docker service ps web --format 'table {{.Name}}.{{.ID}}' --no-trunc NAME ID web.1.inyhxhvjcijl0hdbu8lgrwwh7 \_ web.1.p9m4kx2srjqmfms4igam0uqlb ``` This fix is an attampt to address the skewness of the table when delimiter is not `"\t"`. The basic idea is that, when header consists of `table` key, the header section will be redendered the same way as content section. A map mapping each placeholder name to the HEADER entry name is used for the context of the header. Unit tests have been updated and added to cover the changes. This fix is related to #28313. Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
This commit is contained in:
parent
584e399d96
commit
ea61dac9e6
17 changed files with 281 additions and 247 deletions
|
@ -14,7 +14,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"
|
||||
|
@ -71,7 +71,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 {
|
||||
|
@ -80,12 +90,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)
|
||||
}
|
||||
|
@ -93,7 +122,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 {
|
||||
|
@ -107,7 +135,6 @@ func (c *containerContext) Names() string {
|
|||
}
|
||||
|
||||
func (c *containerContext) Image() string {
|
||||
c.AddHeader(imageHeader)
|
||||
if c.c.Image == "" {
|
||||
return "<no image>"
|
||||
}
|
||||
|
@ -120,7 +147,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)
|
||||
|
@ -129,28 +155,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)
|
||||
|
||||
|
@ -162,7 +183,6 @@ func (c *containerContext) Size() string {
|
|||
}
|
||||
|
||||
func (c *containerContext) Labels() string {
|
||||
c.AddHeader(labelsHeader)
|
||||
if c.c.Labels == nil {
|
||||
return ""
|
||||
}
|
||||
|
@ -175,12 +195,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 ""
|
||||
}
|
||||
|
@ -188,8 +202,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 {
|
||||
|
@ -207,8 +219,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" {
|
||||
|
@ -220,8 +230,6 @@ func (c *containerContext) LocalVolumes() string {
|
|||
}
|
||||
|
||||
func (c *containerContext) Networks() string {
|
||||
c.AddHeader(networksHeader)
|
||||
|
||||
if c.c.NetworkSettings == nil {
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
@ -333,8 +312,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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
56
cli/command/formatter/disk_usage_test.go
Normal file
56
cli/command/formatter/disk_usage_test.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package formatter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
//"encoding/json"
|
||||
//"strings"
|
||||
"testing"
|
||||
//"time"
|
||||
|
||||
//"github.com/docker/docker/api/types"
|
||||
//"github.com/docker/docker/pkg/stringid"
|
||||
"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 {
|
||||
//networks := []types.NetworkResource{
|
||||
// {ID: "networkID1", Name: "foobar_baz", Driver: "foo", Scope: "local", Created: timestamp1},
|
||||
// {ID: "networkID2", Name: "foobar_bar", Driver: "bar", Scope: "local", Created: timestamp2},
|
||||
//}
|
||||
out := bytes.NewBufferString("")
|
||||
testcase.context.Output = out
|
||||
testcase.context.Write()
|
||||
assert.Equal(t, out.String(), testcase.expected)
|
||||
}
|
||||
}
|
|
@ -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.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")
|
||||
|
|
|
@ -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,21 @@ 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)
|
||||
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 ctx.Write(newImageContext(), render)
|
||||
}
|
||||
|
||||
func imageFormat(ctx ImageContext, images []types.ImageSummary, format func(subContext subContext) error) error {
|
||||
|
@ -192,12 +206,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 +236,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 +268,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 +279,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"
|
||||
}
|
||||
|
|
|
@ -18,27 +18,26 @@ func TestImageContext(t *testing.T) {
|
|||
|
||||
var ctx imageContext
|
||||
cases := []struct {
|
||||
imageCtx imageContext
|
||||
expValue string
|
||||
expHeader string
|
||||
call func() string
|
||||
imageCtx imageContext
|
||||
expValue 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -372,7 +372,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 {
|
||||
|
@ -387,27 +395,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)
|
||||
|
|
|
@ -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("--")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue