mirror of https://github.com/docker/cli.git
Refactor formatter.
Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
parent
d9cb421d69
commit
db0952ad22
|
@ -106,27 +106,19 @@ func runPs(dockerCli *command.DockerCli, opts *psOptions) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
f := opts.format
|
format := opts.format
|
||||||
if len(f) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().PsFormat) > 0 && !opts.quiet {
|
if len(dockerCli.ConfigFile().PsFormat) > 0 && !opts.quiet {
|
||||||
f = dockerCli.ConfigFile().PsFormat
|
format = dockerCli.ConfigFile().PsFormat
|
||||||
} else {
|
} else {
|
||||||
f = "table"
|
format = "table"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
psCtx := formatter.ContainerContext{
|
containerCtx := formatter.Context{
|
||||||
Context: formatter.Context{
|
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: f,
|
Format: formatter.NewContainerFormat(format, opts.quiet, opts.size),
|
||||||
Quiet: opts.quiet,
|
|
||||||
Trunc: !opts.noTrunc,
|
Trunc: !opts.noTrunc,
|
||||||
},
|
|
||||||
Size: listOptions.Size,
|
|
||||||
Containers: containers,
|
|
||||||
}
|
}
|
||||||
|
return formatter.ContainerWrite(containerCtx, containers)
|
||||||
psCtx.Write()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -11,7 +10,7 @@ import (
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
"github.com/docker/docker/pkg/stringutils"
|
"github.com/docker/docker/pkg/stringutils"
|
||||||
"github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -26,67 +25,53 @@ const (
|
||||||
mountsHeader = "MOUNTS"
|
mountsHeader = "MOUNTS"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContainerContext contains container specific information required by the formater, encapsulate a Context struct.
|
// NewContainerFormat returns a Format for rendering using a Context
|
||||||
type ContainerContext struct {
|
func NewContainerFormat(source string, quiet bool, size bool) Format {
|
||||||
Context
|
switch source {
|
||||||
// Size when set to true will display the size of the output.
|
case TableFormatKey:
|
||||||
Size bool
|
if quiet {
|
||||||
// Containers
|
return defaultQuietFormat
|
||||||
Containers []types.Container
|
}
|
||||||
|
format := defaultContainerTableFormat
|
||||||
|
if size {
|
||||||
|
format += `\t{{.Size}}`
|
||||||
|
}
|
||||||
|
return Format(format)
|
||||||
|
case RawFormatKey:
|
||||||
|
if quiet {
|
||||||
|
return `container_id: {{.ID}}`
|
||||||
|
}
|
||||||
|
format := `container_id: {{.ID}}\nimage: {{.Image}}\ncommand: {{.Command}}\ncreated_at: {{.CreatedAt}}\nstatus: {{.Status}}\nnames: {{.Names}}\nlabels: {{.Labels}}\nports: {{.Ports}}\n`
|
||||||
|
if size {
|
||||||
|
format += `size: {{.Size}}\n`
|
||||||
|
}
|
||||||
|
return Format(format)
|
||||||
|
}
|
||||||
|
return Format(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx ContainerContext) Write() {
|
// ContainerWrite renders the context for a list of containers
|
||||||
switch ctx.Format {
|
func ContainerWrite(ctx Context, containers []types.Container) error {
|
||||||
case tableFormatKey:
|
render := func(format func(subContext subContext) error) error {
|
||||||
if ctx.Quiet {
|
for _, container := range containers {
|
||||||
ctx.Format = defaultQuietFormat
|
err := format(&containerContext{trunc: ctx.Trunc, c: container})
|
||||||
} else {
|
|
||||||
ctx.Format = defaultContainerTableFormat
|
|
||||||
if ctx.Size {
|
|
||||||
ctx.Format += `\t{{.Size}}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case rawFormatKey:
|
|
||||||
if ctx.Quiet {
|
|
||||||
ctx.Format = `container_id: {{.ID}}`
|
|
||||||
} else {
|
|
||||||
ctx.Format = `container_id: {{.ID}}\nimage: {{.Image}}\ncommand: {{.Command}}\ncreated_at: {{.CreatedAt}}\nstatus: {{.Status}}\nnames: {{.Names}}\nlabels: {{.Labels}}\nports: {{.Ports}}\n`
|
|
||||||
if ctx.Size {
|
|
||||||
ctx.Format += `size: {{.Size}}\n`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.buffer = bytes.NewBufferString("")
|
|
||||||
ctx.preformat()
|
|
||||||
|
|
||||||
tmpl, err := ctx.parseFormat()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
for _, container := range ctx.Containers {
|
|
||||||
containerCtx := &containerContext{
|
|
||||||
trunc: ctx.Trunc,
|
|
||||||
c: container,
|
|
||||||
}
|
|
||||||
err = ctx.contextFormat(tmpl, containerCtx)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
ctx.postformat(tmpl, &containerContext{})
|
}
|
||||||
|
return ctx.Write(&containerContext{}, render)
|
||||||
}
|
}
|
||||||
|
|
||||||
type containerContext struct {
|
type containerContext struct {
|
||||||
baseSubContext
|
HeaderContext
|
||||||
trunc bool
|
trunc bool
|
||||||
c types.Container
|
c types.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *containerContext) ID() string {
|
func (c *containerContext) ID() string {
|
||||||
c.addHeader(containerIDHeader)
|
c.AddHeader(containerIDHeader)
|
||||||
if c.trunc {
|
if c.trunc {
|
||||||
return stringid.TruncateID(c.c.ID)
|
return stringid.TruncateID(c.c.ID)
|
||||||
}
|
}
|
||||||
|
@ -94,7 +79,7 @@ func (c *containerContext) ID() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *containerContext) Names() string {
|
func (c *containerContext) Names() string {
|
||||||
c.addHeader(namesHeader)
|
c.AddHeader(namesHeader)
|
||||||
names := stripNamePrefix(c.c.Names)
|
names := stripNamePrefix(c.c.Names)
|
||||||
if c.trunc {
|
if c.trunc {
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
|
@ -108,7 +93,7 @@ func (c *containerContext) Names() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *containerContext) Image() string {
|
func (c *containerContext) Image() string {
|
||||||
c.addHeader(imageHeader)
|
c.AddHeader(imageHeader)
|
||||||
if c.c.Image == "" {
|
if c.c.Image == "" {
|
||||||
return "<no image>"
|
return "<no image>"
|
||||||
}
|
}
|
||||||
|
@ -121,7 +106,7 @@ func (c *containerContext) Image() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *containerContext) Command() string {
|
func (c *containerContext) Command() string {
|
||||||
c.addHeader(commandHeader)
|
c.AddHeader(commandHeader)
|
||||||
command := c.c.Command
|
command := c.c.Command
|
||||||
if c.trunc {
|
if c.trunc {
|
||||||
command = stringutils.Ellipsis(command, 20)
|
command = stringutils.Ellipsis(command, 20)
|
||||||
|
@ -130,28 +115,28 @@ func (c *containerContext) Command() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *containerContext) CreatedAt() string {
|
func (c *containerContext) CreatedAt() string {
|
||||||
c.addHeader(createdAtHeader)
|
c.AddHeader(createdAtHeader)
|
||||||
return time.Unix(int64(c.c.Created), 0).String()
|
return time.Unix(int64(c.c.Created), 0).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *containerContext) RunningFor() string {
|
func (c *containerContext) RunningFor() string {
|
||||||
c.addHeader(runningForHeader)
|
c.AddHeader(runningForHeader)
|
||||||
createdAt := time.Unix(int64(c.c.Created), 0)
|
createdAt := time.Unix(int64(c.c.Created), 0)
|
||||||
return units.HumanDuration(time.Now().UTC().Sub(createdAt))
|
return units.HumanDuration(time.Now().UTC().Sub(createdAt))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *containerContext) Ports() string {
|
func (c *containerContext) Ports() string {
|
||||||
c.addHeader(portsHeader)
|
c.AddHeader(portsHeader)
|
||||||
return api.DisplayablePorts(c.c.Ports)
|
return api.DisplayablePorts(c.c.Ports)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *containerContext) Status() string {
|
func (c *containerContext) Status() string {
|
||||||
c.addHeader(statusHeader)
|
c.AddHeader(statusHeader)
|
||||||
return c.c.Status
|
return c.c.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *containerContext) Size() string {
|
func (c *containerContext) Size() string {
|
||||||
c.addHeader(sizeHeader)
|
c.AddHeader(sizeHeader)
|
||||||
srw := units.HumanSizeWithPrecision(float64(c.c.SizeRw), 3)
|
srw := units.HumanSizeWithPrecision(float64(c.c.SizeRw), 3)
|
||||||
sv := units.HumanSizeWithPrecision(float64(c.c.SizeRootFs), 3)
|
sv := units.HumanSizeWithPrecision(float64(c.c.SizeRootFs), 3)
|
||||||
|
|
||||||
|
@ -163,7 +148,7 @@ func (c *containerContext) Size() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *containerContext) Labels() string {
|
func (c *containerContext) Labels() string {
|
||||||
c.addHeader(labelsHeader)
|
c.AddHeader(labelsHeader)
|
||||||
if c.c.Labels == nil {
|
if c.c.Labels == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -180,7 +165,7 @@ func (c *containerContext) Label(name string) string {
|
||||||
r := strings.NewReplacer("-", " ", "_", " ")
|
r := strings.NewReplacer("-", " ", "_", " ")
|
||||||
h := r.Replace(n[len(n)-1])
|
h := r.Replace(n[len(n)-1])
|
||||||
|
|
||||||
c.addHeader(h)
|
c.AddHeader(h)
|
||||||
|
|
||||||
if c.c.Labels == nil {
|
if c.c.Labels == nil {
|
||||||
return ""
|
return ""
|
||||||
|
@ -189,7 +174,7 @@ func (c *containerContext) Label(name string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *containerContext) Mounts() string {
|
func (c *containerContext) Mounts() string {
|
||||||
c.addHeader(mountsHeader)
|
c.AddHeader(mountsHeader)
|
||||||
|
|
||||||
var name string
|
var name string
|
||||||
var mounts []string
|
var mounts []string
|
||||||
|
|
|
@ -95,7 +95,7 @@ func TestContainerPsContext(t *testing.T) {
|
||||||
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
|
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
h := ctx.fullHeader()
|
h := ctx.FullHeader()
|
||||||
if h != c.expHeader {
|
if h != c.expHeader {
|
||||||
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
|
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ func TestContainerPsContext(t *testing.T) {
|
||||||
t.Fatalf("Expected ubuntu, was %s\n", node)
|
t.Fatalf("Expected ubuntu, was %s\n", node)
|
||||||
}
|
}
|
||||||
|
|
||||||
h := ctx.fullHeader()
|
h := ctx.FullHeader()
|
||||||
if h != "SWARM ID\tNODE NAME" {
|
if h != "SWARM ID\tNODE NAME" {
|
||||||
t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
|
t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
|
||||||
|
|
||||||
|
@ -129,9 +129,9 @@ func TestContainerPsContext(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = containerContext{c: c2, trunc: true}
|
ctx = containerContext{c: c2, trunc: true}
|
||||||
fullHeader := ctx.fullHeader()
|
FullHeader := ctx.FullHeader()
|
||||||
if fullHeader != "" {
|
if FullHeader != "" {
|
||||||
t.Fatalf("Expected fullHeader to be empty, was %s", fullHeader)
|
t.Fatalf("Expected FullHeader to be empty, was %s", FullHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -140,95 +140,55 @@ func TestContainerContextWrite(t *testing.T) {
|
||||||
unixTime := time.Now().AddDate(0, 0, -1).Unix()
|
unixTime := time.Now().AddDate(0, 0, -1).Unix()
|
||||||
expectedTime := time.Unix(unixTime, 0).String()
|
expectedTime := time.Unix(unixTime, 0).String()
|
||||||
|
|
||||||
contexts := []struct {
|
cases := []struct {
|
||||||
context ContainerContext
|
context Context
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
// Errors
|
// Errors
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{Format: "{{InvalidFunction}}"},
|
||||||
Context: Context{
|
|
||||||
Format: "{{InvalidFunction}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`Template parsing error: template: :1: function "InvalidFunction" not defined
|
`Template parsing error: template: :1: function "InvalidFunction" not defined
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{Format: "{{nil}}"},
|
||||||
Context: Context{
|
|
||||||
Format: "{{nil}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
// Table Format
|
// Table Format
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{Format: NewContainerFormat("table", false, true)},
|
||||||
Context: Context{
|
|
||||||
Format: "table",
|
|
||||||
},
|
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
`CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
|
`CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
|
||||||
containerID1 ubuntu "" 24 hours ago foobar_baz 0 B
|
containerID1 ubuntu "" 24 hours ago foobar_baz 0 B
|
||||||
containerID2 ubuntu "" 24 hours ago foobar_bar 0 B
|
containerID2 ubuntu "" 24 hours ago foobar_bar 0 B
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{Format: NewContainerFormat("table", false, false)},
|
||||||
Context: Context{
|
|
||||||
Format: "table",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
`CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||||
containerID1 ubuntu "" 24 hours ago foobar_baz
|
containerID1 ubuntu "" 24 hours ago foobar_baz
|
||||||
containerID2 ubuntu "" 24 hours ago foobar_bar
|
containerID2 ubuntu "" 24 hours ago foobar_bar
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{Format: NewContainerFormat("table {{.Image}}", false, false)},
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Image}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"IMAGE\nubuntu\nubuntu\n",
|
"IMAGE\nubuntu\nubuntu\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{Format: NewContainerFormat("table {{.Image}}", false, true)},
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Image}}",
|
|
||||||
},
|
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
"IMAGE\nubuntu\nubuntu\n",
|
"IMAGE\nubuntu\nubuntu\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{Format: NewContainerFormat("table {{.Image}}", true, false)},
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Image}}",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"IMAGE\nubuntu\nubuntu\n",
|
"IMAGE\nubuntu\nubuntu\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{Format: NewContainerFormat("table", true, false)},
|
||||||
Context: Context{
|
|
||||||
Format: "table",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"containerID1\ncontainerID2\n",
|
"containerID1\ncontainerID2\n",
|
||||||
},
|
},
|
||||||
// Raw Format
|
// Raw Format
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{Format: NewContainerFormat("raw", false, false)},
|
||||||
Context: Context{
|
|
||||||
Format: "raw",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fmt.Sprintf(`container_id: containerID1
|
fmt.Sprintf(`container_id: containerID1
|
||||||
image: ubuntu
|
image: ubuntu
|
||||||
command: ""
|
command: ""
|
||||||
|
@ -250,12 +210,7 @@ ports:
|
||||||
`, expectedTime, expectedTime),
|
`, expectedTime, expectedTime),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{Format: NewContainerFormat("raw", false, true)},
|
||||||
Context: Context{
|
|
||||||
Format: "raw",
|
|
||||||
},
|
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
fmt.Sprintf(`container_id: containerID1
|
fmt.Sprintf(`container_id: containerID1
|
||||||
image: ubuntu
|
image: ubuntu
|
||||||
command: ""
|
command: ""
|
||||||
|
@ -279,47 +234,33 @@ size: 0 B
|
||||||
`, expectedTime, expectedTime),
|
`, expectedTime, expectedTime),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{Format: NewContainerFormat("raw", true, false)},
|
||||||
Context: Context{
|
|
||||||
Format: "raw",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"container_id: containerID1\ncontainer_id: containerID2\n",
|
"container_id: containerID1\ncontainer_id: containerID2\n",
|
||||||
},
|
},
|
||||||
// Custom Format
|
// Custom Format
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{Format: "{{.Image}}"},
|
||||||
Context: Context{
|
|
||||||
Format: "{{.Image}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"ubuntu\nubuntu\n",
|
"ubuntu\nubuntu\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{Format: NewContainerFormat("{{.Image}}", false, true)},
|
||||||
Context: Context{
|
|
||||||
Format: "{{.Image}}",
|
|
||||||
},
|
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
"ubuntu\nubuntu\n",
|
"ubuntu\nubuntu\n",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, context := range contexts {
|
for _, testcase := range cases {
|
||||||
containers := []types.Container{
|
containers := []types.Container{
|
||||||
{ID: "containerID1", Names: []string{"/foobar_baz"}, Image: "ubuntu", Created: unixTime},
|
{ID: "containerID1", Names: []string{"/foobar_baz"}, Image: "ubuntu", Created: unixTime},
|
||||||
{ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu", Created: unixTime},
|
{ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu", Created: unixTime},
|
||||||
}
|
}
|
||||||
out := bytes.NewBufferString("")
|
out := bytes.NewBufferString("")
|
||||||
context.context.Output = out
|
testcase.context.Output = out
|
||||||
context.context.Containers = containers
|
err := ContainerWrite(testcase.context, containers)
|
||||||
context.context.Write()
|
if err != nil {
|
||||||
actual := out.String()
|
assert.Error(t, err, testcase.expected)
|
||||||
assert.Equal(t, actual, context.expected)
|
} else {
|
||||||
// Clean buffer
|
assert.Equal(t, out.String(), testcase.expected)
|
||||||
out.Reset()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,75 +269,56 @@ func TestContainerContextWriteWithNoContainers(t *testing.T) {
|
||||||
containers := []types.Container{}
|
containers := []types.Container{}
|
||||||
|
|
||||||
contexts := []struct {
|
contexts := []struct {
|
||||||
context ContainerContext
|
context Context
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{
|
||||||
Context: Context{
|
|
||||||
Format: "{{.Image}}",
|
Format: "{{.Image}}",
|
||||||
Output: out,
|
Output: out,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
"",
|
"",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Image}}",
|
Format: "table {{.Image}}",
|
||||||
Output: out,
|
Output: out,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
"IMAGE\n",
|
"IMAGE\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{
|
||||||
Context: Context{
|
Format: NewContainerFormat("{{.Image}}", false, true),
|
||||||
Format: "{{.Image}}",
|
|
||||||
Output: out,
|
Output: out,
|
||||||
},
|
},
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
"",
|
"",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{
|
||||||
Context: Context{
|
Format: NewContainerFormat("table {{.Image}}", false, true),
|
||||||
Format: "table {{.Image}}",
|
|
||||||
Output: out,
|
Output: out,
|
||||||
},
|
},
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
"IMAGE\n",
|
"IMAGE\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Image}}\t{{.Size}}",
|
Format: "table {{.Image}}\t{{.Size}}",
|
||||||
Output: out,
|
Output: out,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
"IMAGE SIZE\n",
|
"IMAGE SIZE\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ContainerContext{
|
Context{
|
||||||
Context: Context{
|
Format: NewContainerFormat("table {{.Image}}\t{{.Size}}", false, true),
|
||||||
Format: "table {{.Image}}\t{{.Size}}",
|
|
||||||
Output: out,
|
Output: out,
|
||||||
},
|
},
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
"IMAGE SIZE\n",
|
"IMAGE SIZE\n",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, context := range contexts {
|
for _, context := range contexts {
|
||||||
context.context.Containers = containers
|
ContainerWrite(context.context, containers)
|
||||||
context.context.Write()
|
assert.Equal(t, context.expected, out.String())
|
||||||
actual := out.String()
|
|
||||||
if actual != context.expected {
|
|
||||||
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
|
||||||
}
|
|
||||||
// Clean buffer
|
// Clean buffer
|
||||||
out.Reset()
|
out.Reset()
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tableKey = "table"
|
|
||||||
|
|
||||||
imageHeader = "IMAGE"
|
imageHeader = "IMAGE"
|
||||||
createdSinceHeader = "CREATED"
|
createdSinceHeader = "CREATED"
|
||||||
createdAtHeader = "CREATED AT"
|
createdAtHeader = "CREATED AT"
|
||||||
|
@ -18,22 +16,25 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type subContext interface {
|
type subContext interface {
|
||||||
fullHeader() string
|
FullHeader() string
|
||||||
addHeader(header string)
|
AddHeader(header string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type baseSubContext struct {
|
// HeaderContext provides the subContext interface for managing headers
|
||||||
|
type HeaderContext struct {
|
||||||
header []string
|
header []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *baseSubContext) fullHeader() string {
|
// FullHeader returns the header as a string
|
||||||
|
func (c *HeaderContext) FullHeader() string {
|
||||||
if c.header == nil {
|
if c.header == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return strings.Join(c.header, "\t")
|
return strings.Join(c.header, "\t")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *baseSubContext) addHeader(header string) {
|
// AddHeader adds another column to the header
|
||||||
|
func (c *HeaderContext) AddHeader(header string) {
|
||||||
if c.header == nil {
|
if c.header == nil {
|
||||||
c.header = []string{}
|
c.header = []string{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,36 +12,48 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
tableFormatKey = "table"
|
// TableFormatKey is the key used to format as a table
|
||||||
rawFormatKey = "raw"
|
TableFormatKey = "table"
|
||||||
|
// RawFormatKey is the key used to format as raw JSON
|
||||||
|
RawFormatKey = "raw"
|
||||||
|
|
||||||
defaultQuietFormat = "{{.ID}}"
|
defaultQuietFormat = "{{.ID}}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Format is the format string rendered using the Context
|
||||||
|
type Format string
|
||||||
|
|
||||||
|
// IsTable returns true if the format is a table-type format
|
||||||
|
func (f Format) IsTable() bool {
|
||||||
|
return strings.HasPrefix(string(f), TableFormatKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains returns true if the format contains the substring
|
||||||
|
func (f Format) Contains(sub string) bool {
|
||||||
|
return strings.Contains(string(f), sub)
|
||||||
|
}
|
||||||
|
|
||||||
// Context contains information required by the formatter to print the output as desired.
|
// Context contains information required by the formatter to print the output as desired.
|
||||||
type Context struct {
|
type Context struct {
|
||||||
// Output is the output stream to which the formatted string is written.
|
// Output is the output stream to which the formatted string is written.
|
||||||
Output io.Writer
|
Output io.Writer
|
||||||
// Format is used to choose raw, table or custom format for the output.
|
// Format is used to choose raw, table or custom format for the output.
|
||||||
Format string
|
Format Format
|
||||||
// Quiet when set to true will simply print minimal information.
|
|
||||||
Quiet bool
|
|
||||||
// Trunc when set to true will truncate the output of certain fields such as Container ID.
|
// Trunc when set to true will truncate the output of certain fields such as Container ID.
|
||||||
Trunc bool
|
Trunc bool
|
||||||
|
|
||||||
// internal element
|
// internal element
|
||||||
table bool
|
|
||||||
finalFormat string
|
finalFormat string
|
||||||
header string
|
header string
|
||||||
buffer *bytes.Buffer
|
buffer *bytes.Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) preformat() {
|
func (c *Context) preFormat() {
|
||||||
c.finalFormat = c.Format
|
c.finalFormat = string(c.Format)
|
||||||
|
|
||||||
if strings.HasPrefix(c.Format, tableKey) {
|
// TODO: handle this in the Format type
|
||||||
c.table = true
|
if c.Format.IsTable() {
|
||||||
c.finalFormat = c.finalFormat[len(tableKey):]
|
c.finalFormat = c.finalFormat[len(TableFormatKey):]
|
||||||
}
|
}
|
||||||
|
|
||||||
c.finalFormat = strings.Trim(c.finalFormat, " ")
|
c.finalFormat = strings.Trim(c.finalFormat, " ")
|
||||||
|
@ -52,18 +64,17 @@ func (c *Context) preformat() {
|
||||||
func (c *Context) parseFormat() (*template.Template, error) {
|
func (c *Context) parseFormat() (*template.Template, error) {
|
||||||
tmpl, err := templates.Parse(c.finalFormat)
|
tmpl, err := templates.Parse(c.finalFormat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
|
return tmpl, fmt.Errorf("Template parsing error: %v\n", err)
|
||||||
c.buffer.WriteTo(c.Output)
|
|
||||||
}
|
}
|
||||||
return tmpl, err
|
return tmpl, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) postformat(tmpl *template.Template, subContext subContext) {
|
func (c *Context) postFormat(tmpl *template.Template, subContext subContext) {
|
||||||
if c.table {
|
if c.Format.IsTable() {
|
||||||
if len(c.header) == 0 {
|
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
|
// 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)
|
tmpl.Execute(bytes.NewBufferString(""), subContext)
|
||||||
c.header = subContext.fullHeader()
|
c.header = subContext.FullHeader()
|
||||||
}
|
}
|
||||||
|
|
||||||
t := tabwriter.NewWriter(c.Output, 20, 1, 3, ' ', 0)
|
t := tabwriter.NewWriter(c.Output, 20, 1, 3, ' ', 0)
|
||||||
|
@ -78,13 +89,35 @@ func (c *Context) postformat(tmpl *template.Template, subContext subContext) {
|
||||||
|
|
||||||
func (c *Context) contextFormat(tmpl *template.Template, subContext subContext) error {
|
func (c *Context) contextFormat(tmpl *template.Template, subContext subContext) error {
|
||||||
if err := tmpl.Execute(c.buffer, subContext); err != nil {
|
if err := tmpl.Execute(c.buffer, subContext); err != nil {
|
||||||
c.buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err))
|
return fmt.Errorf("Template parsing error: %v\n", err)
|
||||||
c.buffer.WriteTo(c.Output)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
if c.table && len(c.header) == 0 {
|
if c.Format.IsTable() && len(c.header) == 0 {
|
||||||
c.header = subContext.fullHeader()
|
c.header = subContext.FullHeader()
|
||||||
}
|
}
|
||||||
c.buffer.WriteString("\n")
|
c.buffer.WriteString("\n")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubFormat is a function type accepted by Write()
|
||||||
|
type SubFormat func(func(subContext) error) error
|
||||||
|
|
||||||
|
// Write the template to the buffer using this Context
|
||||||
|
func (c *Context) Write(sub subContext, f SubFormat) error {
|
||||||
|
c.buffer = bytes.NewBufferString("")
|
||||||
|
c.preFormat()
|
||||||
|
|
||||||
|
tmpl, err := c.parseFormat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
subFormat := func(subContext subContext) error {
|
||||||
|
return c.contextFormat(tmpl, subContext)
|
||||||
|
}
|
||||||
|
if err := f(subFormat); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.postFormat(tmpl, sub)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
"github.com/docker/docker/reference"
|
"github.com/docker/docker/reference"
|
||||||
"github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -25,38 +23,38 @@ const (
|
||||||
type ImageContext struct {
|
type ImageContext struct {
|
||||||
Context
|
Context
|
||||||
Digest bool
|
Digest bool
|
||||||
// Images
|
|
||||||
Images []types.Image
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isDangling(image types.Image) bool {
|
func isDangling(image types.Image) bool {
|
||||||
return len(image.RepoTags) == 1 && image.RepoTags[0] == "<none>:<none>" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "<none>@<none>"
|
return len(image.RepoTags) == 1 && image.RepoTags[0] == "<none>:<none>" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "<none>@<none>"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx ImageContext) Write() {
|
// NewImageFormat returns a format for rendering an ImageContext
|
||||||
switch ctx.Format {
|
func NewImageFormat(source string, quiet bool, digest bool) Format {
|
||||||
case tableFormatKey:
|
switch source {
|
||||||
ctx.Format = defaultImageTableFormat
|
case TableFormatKey:
|
||||||
if ctx.Digest {
|
switch {
|
||||||
ctx.Format = defaultImageTableFormatWithDigest
|
case quiet:
|
||||||
|
return defaultQuietFormat
|
||||||
|
case digest:
|
||||||
|
return defaultImageTableFormatWithDigest
|
||||||
|
default:
|
||||||
|
return defaultImageTableFormat
|
||||||
}
|
}
|
||||||
if ctx.Quiet {
|
case RawFormatKey:
|
||||||
ctx.Format = defaultQuietFormat
|
switch {
|
||||||
}
|
case quiet:
|
||||||
case rawFormatKey:
|
return `image_id: {{.ID}}`
|
||||||
if ctx.Quiet {
|
case digest:
|
||||||
ctx.Format = `image_id: {{.ID}}`
|
return `repository: {{ .Repository }}
|
||||||
} else {
|
|
||||||
if ctx.Digest {
|
|
||||||
ctx.Format = `repository: {{ .Repository }}
|
|
||||||
tag: {{.Tag}}
|
tag: {{.Tag}}
|
||||||
digest: {{.Digest}}
|
digest: {{.Digest}}
|
||||||
image_id: {{.ID}}
|
image_id: {{.ID}}
|
||||||
created_at: {{.CreatedAt}}
|
created_at: {{.CreatedAt}}
|
||||||
virtual_size: {{.Size}}
|
virtual_size: {{.Size}}
|
||||||
`
|
`
|
||||||
} else {
|
default:
|
||||||
ctx.Format = `repository: {{ .Repository }}
|
return `repository: {{ .Repository }}
|
||||||
tag: {{.Tag}}
|
tag: {{.Tag}}
|
||||||
image_id: {{.ID}}
|
image_id: {{.ID}}
|
||||||
created_at: {{.CreatedAt}}
|
created_at: {{.CreatedAt}}
|
||||||
|
@ -64,20 +62,24 @@ virtual_size: {{.Size}}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ctx.buffer = bytes.NewBufferString("")
|
format := Format(source)
|
||||||
ctx.preformat()
|
if format.IsTable() && digest && !format.Contains("{{.Digest}}") {
|
||||||
if ctx.table && ctx.Digest && !strings.Contains(ctx.Format, "{{.Digest}}") {
|
format += "\t{{.Digest}}"
|
||||||
ctx.finalFormat += "\t{{.Digest}}"
|
|
||||||
}
|
}
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
tmpl, err := ctx.parseFormat()
|
// ImageWrite writes the formatter images using the ImageContext
|
||||||
if err != nil {
|
func ImageWrite(ctx ImageContext, images []types.Image) error {
|
||||||
return
|
render := func(format func(subContext subContext) error) error {
|
||||||
|
return imageFormat(ctx, images, format)
|
||||||
}
|
}
|
||||||
|
return ctx.Write(&imageContext{}, render)
|
||||||
|
}
|
||||||
|
|
||||||
for _, image := range ctx.Images {
|
func imageFormat(ctx ImageContext, images []types.Image, format func(subContext subContext) error) error {
|
||||||
|
for _, image := range images {
|
||||||
images := []*imageContext{}
|
images := []*imageContext{}
|
||||||
if isDangling(image) {
|
if isDangling(image) {
|
||||||
images = append(images, &imageContext{
|
images = append(images, &imageContext{
|
||||||
|
@ -170,18 +172,16 @@ virtual_size: {{.Size}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, imageCtx := range images {
|
for _, imageCtx := range images {
|
||||||
err = ctx.contextFormat(tmpl, imageCtx)
|
if err := format(imageCtx); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
ctx.postformat(tmpl, &imageContext{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type imageContext struct {
|
type imageContext struct {
|
||||||
baseSubContext
|
HeaderContext
|
||||||
trunc bool
|
trunc bool
|
||||||
i types.Image
|
i types.Image
|
||||||
repo string
|
repo string
|
||||||
|
@ -190,7 +190,7 @@ type imageContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *imageContext) ID() string {
|
func (c *imageContext) ID() string {
|
||||||
c.addHeader(imageIDHeader)
|
c.AddHeader(imageIDHeader)
|
||||||
if c.trunc {
|
if c.trunc {
|
||||||
return stringid.TruncateID(c.i.ID)
|
return stringid.TruncateID(c.i.ID)
|
||||||
}
|
}
|
||||||
|
@ -198,32 +198,32 @@ func (c *imageContext) ID() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *imageContext) Repository() string {
|
func (c *imageContext) Repository() string {
|
||||||
c.addHeader(repositoryHeader)
|
c.AddHeader(repositoryHeader)
|
||||||
return c.repo
|
return c.repo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *imageContext) Tag() string {
|
func (c *imageContext) Tag() string {
|
||||||
c.addHeader(tagHeader)
|
c.AddHeader(tagHeader)
|
||||||
return c.tag
|
return c.tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *imageContext) Digest() string {
|
func (c *imageContext) Digest() string {
|
||||||
c.addHeader(digestHeader)
|
c.AddHeader(digestHeader)
|
||||||
return c.digest
|
return c.digest
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *imageContext) CreatedSince() string {
|
func (c *imageContext) CreatedSince() string {
|
||||||
c.addHeader(createdSinceHeader)
|
c.AddHeader(createdSinceHeader)
|
||||||
createdAt := time.Unix(int64(c.i.Created), 0)
|
createdAt := time.Unix(int64(c.i.Created), 0)
|
||||||
return units.HumanDuration(time.Now().UTC().Sub(createdAt))
|
return units.HumanDuration(time.Now().UTC().Sub(createdAt))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *imageContext) CreatedAt() string {
|
func (c *imageContext) CreatedAt() string {
|
||||||
c.addHeader(createdAtHeader)
|
c.AddHeader(createdAtHeader)
|
||||||
return time.Unix(int64(c.i.Created), 0).String()
|
return time.Unix(int64(c.i.Created), 0).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *imageContext) Size() string {
|
func (c *imageContext) Size() string {
|
||||||
c.addHeader(sizeHeader)
|
c.AddHeader(sizeHeader)
|
||||||
return units.HumanSizeWithPrecision(float64(c.i.Size), 3)
|
return units.HumanSizeWithPrecision(float64(c.i.Size), 3)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
"github.com/docker/docker/pkg/testutil/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestImageContext(t *testing.T) {
|
func TestImageContext(t *testing.T) {
|
||||||
|
@ -66,7 +67,7 @@ func TestImageContext(t *testing.T) {
|
||||||
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
|
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
h := ctx.fullHeader()
|
h := ctx.FullHeader()
|
||||||
if h != c.expHeader {
|
if h != c.expHeader {
|
||||||
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
|
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
|
||||||
}
|
}
|
||||||
|
@ -77,7 +78,7 @@ func TestImageContextWrite(t *testing.T) {
|
||||||
unixTime := time.Now().AddDate(0, 0, -1).Unix()
|
unixTime := time.Now().AddDate(0, 0, -1).Unix()
|
||||||
expectedTime := time.Unix(unixTime, 0).String()
|
expectedTime := time.Unix(unixTime, 0).String()
|
||||||
|
|
||||||
contexts := []struct {
|
cases := []struct {
|
||||||
context ImageContext
|
context ImageContext
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
|
@ -104,7 +105,7 @@ func TestImageContextWrite(t *testing.T) {
|
||||||
{
|
{
|
||||||
ImageContext{
|
ImageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: "table",
|
Format: NewImageFormat("table", false, false),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
`REPOSITORY TAG IMAGE ID CREATED SIZE
|
`REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||||
|
@ -116,7 +117,7 @@ image tag2 imageID2 24 hours ago
|
||||||
{
|
{
|
||||||
ImageContext{
|
ImageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: "table {{.Repository}}",
|
Format: NewImageFormat("table {{.Repository}}", false, false),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"REPOSITORY\nimage\nimage\n<none>\n",
|
"REPOSITORY\nimage\nimage\n<none>\n",
|
||||||
|
@ -124,7 +125,7 @@ image tag2 imageID2 24 hours ago
|
||||||
{
|
{
|
||||||
ImageContext{
|
ImageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: "table {{.Repository}}",
|
Format: NewImageFormat("table {{.Repository}}", false, true),
|
||||||
},
|
},
|
||||||
Digest: true,
|
Digest: true,
|
||||||
},
|
},
|
||||||
|
@ -137,8 +138,7 @@ image <none>
|
||||||
{
|
{
|
||||||
ImageContext{
|
ImageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: "table {{.Repository}}",
|
Format: NewImageFormat("table {{.Repository}}", true, false),
|
||||||
Quiet: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"REPOSITORY\nimage\nimage\n<none>\n",
|
"REPOSITORY\nimage\nimage\n<none>\n",
|
||||||
|
@ -146,8 +146,7 @@ image <none>
|
||||||
{
|
{
|
||||||
ImageContext{
|
ImageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: "table",
|
Format: NewImageFormat("table", true, false),
|
||||||
Quiet: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"imageID1\nimageID2\nimageID3\n",
|
"imageID1\nimageID2\nimageID3\n",
|
||||||
|
@ -155,8 +154,7 @@ image <none>
|
||||||
{
|
{
|
||||||
ImageContext{
|
ImageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: "table",
|
Format: NewImageFormat("table", false, true),
|
||||||
Quiet: false,
|
|
||||||
},
|
},
|
||||||
Digest: true,
|
Digest: true,
|
||||||
},
|
},
|
||||||
|
@ -169,8 +167,7 @@ image tag2 <none>
|
||||||
{
|
{
|
||||||
ImageContext{
|
ImageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: "table",
|
Format: NewImageFormat("table", true, true),
|
||||||
Quiet: true,
|
|
||||||
},
|
},
|
||||||
Digest: true,
|
Digest: true,
|
||||||
},
|
},
|
||||||
|
@ -180,7 +177,7 @@ image tag2 <none>
|
||||||
{
|
{
|
||||||
ImageContext{
|
ImageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: "raw",
|
Format: NewImageFormat("raw", false, false),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fmt.Sprintf(`repository: image
|
fmt.Sprintf(`repository: image
|
||||||
|
@ -206,7 +203,7 @@ virtual_size: 0 B
|
||||||
{
|
{
|
||||||
ImageContext{
|
ImageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: "raw",
|
Format: NewImageFormat("raw", false, true),
|
||||||
},
|
},
|
||||||
Digest: true,
|
Digest: true,
|
||||||
},
|
},
|
||||||
|
@ -236,8 +233,7 @@ virtual_size: 0 B
|
||||||
{
|
{
|
||||||
ImageContext{
|
ImageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: "raw",
|
Format: NewImageFormat("raw", true, false),
|
||||||
Quiet: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
`image_id: imageID1
|
`image_id: imageID1
|
||||||
|
@ -249,7 +245,7 @@ image_id: imageID3
|
||||||
{
|
{
|
||||||
ImageContext{
|
ImageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: "{{.Repository}}",
|
Format: NewImageFormat("{{.Repository}}", false, false),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"image\nimage\n<none>\n",
|
"image\nimage\n<none>\n",
|
||||||
|
@ -257,7 +253,7 @@ image_id: imageID3
|
||||||
{
|
{
|
||||||
ImageContext{
|
ImageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: "{{.Repository}}",
|
Format: NewImageFormat("{{.Repository}}", false, true),
|
||||||
},
|
},
|
||||||
Digest: true,
|
Digest: true,
|
||||||
},
|
},
|
||||||
|
@ -265,22 +261,20 @@ image_id: imageID3
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, context := range contexts {
|
for _, testcase := range cases {
|
||||||
images := []types.Image{
|
images := []types.Image{
|
||||||
{ID: "imageID1", RepoTags: []string{"image:tag1"}, RepoDigests: []string{"image@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf"}, Created: unixTime},
|
{ID: "imageID1", RepoTags: []string{"image:tag1"}, RepoDigests: []string{"image@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf"}, Created: unixTime},
|
||||||
{ID: "imageID2", RepoTags: []string{"image:tag2"}, Created: unixTime},
|
{ID: "imageID2", RepoTags: []string{"image:tag2"}, Created: unixTime},
|
||||||
{ID: "imageID3", RepoTags: []string{"<none>:<none>"}, RepoDigests: []string{"<none>@<none>"}, Created: unixTime},
|
{ID: "imageID3", RepoTags: []string{"<none>:<none>"}, RepoDigests: []string{"<none>@<none>"}, Created: unixTime},
|
||||||
}
|
}
|
||||||
out := bytes.NewBufferString("")
|
out := bytes.NewBufferString("")
|
||||||
context.context.Output = out
|
testcase.context.Output = out
|
||||||
context.context.Images = images
|
err := ImageWrite(testcase.context, images)
|
||||||
context.context.Write()
|
if err != nil {
|
||||||
actual := out.String()
|
assert.Error(t, err, testcase.expected)
|
||||||
if actual != context.expected {
|
} else {
|
||||||
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
assert.Equal(t, out.String(), testcase.expected)
|
||||||
}
|
}
|
||||||
// Clean buffer
|
|
||||||
out.Reset()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,7 +289,7 @@ func TestImageContextWriteWithNoImage(t *testing.T) {
|
||||||
{
|
{
|
||||||
ImageContext{
|
ImageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: "{{.Repository}}",
|
Format: NewImageFormat("{{.Repository}}", false, false),
|
||||||
Output: out,
|
Output: out,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -304,7 +298,7 @@ func TestImageContextWriteWithNoImage(t *testing.T) {
|
||||||
{
|
{
|
||||||
ImageContext{
|
ImageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: "table {{.Repository}}",
|
Format: NewImageFormat("table {{.Repository}}", false, false),
|
||||||
Output: out,
|
Output: out,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -313,32 +307,26 @@ func TestImageContextWriteWithNoImage(t *testing.T) {
|
||||||
{
|
{
|
||||||
ImageContext{
|
ImageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: "{{.Repository}}",
|
Format: NewImageFormat("{{.Repository}}", false, true),
|
||||||
Output: out,
|
Output: out,
|
||||||
},
|
},
|
||||||
Digest: true,
|
|
||||||
},
|
},
|
||||||
"",
|
"",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ImageContext{
|
ImageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: "table {{.Repository}}",
|
Format: NewImageFormat("table {{.Repository}}", false, true),
|
||||||
Output: out,
|
Output: out,
|
||||||
},
|
},
|
||||||
Digest: true,
|
|
||||||
},
|
},
|
||||||
"REPOSITORY DIGEST\n",
|
"REPOSITORY DIGEST\n",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, context := range contexts {
|
for _, context := range contexts {
|
||||||
context.context.Images = images
|
ImageWrite(context.context, images)
|
||||||
context.context.Write()
|
assert.Equal(t, out.String(), context.expected)
|
||||||
actual := out.String()
|
|
||||||
if actual != context.expected {
|
|
||||||
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
|
||||||
}
|
|
||||||
// Clean buffer
|
// Clean buffer
|
||||||
out.Reset()
|
out.Reset()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -17,60 +16,45 @@ const (
|
||||||
internalHeader = "INTERNAL"
|
internalHeader = "INTERNAL"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NetworkContext contains network specific information required by the formatter,
|
// NewNetworkFormat returns a Format for rendering using a network Context
|
||||||
// encapsulate a Context struct.
|
func NewNetworkFormat(source string, quiet bool) Format {
|
||||||
type NetworkContext struct {
|
switch source {
|
||||||
Context
|
case TableFormatKey:
|
||||||
// Networks
|
if quiet {
|
||||||
Networks []types.NetworkResource
|
return defaultQuietFormat
|
||||||
|
}
|
||||||
|
return defaultNetworkTableFormat
|
||||||
|
case RawFormatKey:
|
||||||
|
if quiet {
|
||||||
|
return `network_id: {{.ID}}`
|
||||||
|
}
|
||||||
|
return `network_id: {{.ID}}\nname: {{.Name}}\ndriver: {{.Driver}}\nscope: {{.Scope}}\n`
|
||||||
|
}
|
||||||
|
return Format(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx NetworkContext) Write() {
|
// NetworkWrite writes the context
|
||||||
switch ctx.Format {
|
func NetworkWrite(ctx Context, networks []types.NetworkResource) error {
|
||||||
case tableFormatKey:
|
render := func(format func(subContext subContext) error) error {
|
||||||
if ctx.Quiet {
|
for _, network := range networks {
|
||||||
ctx.Format = defaultQuietFormat
|
networkCtx := &networkContext{trunc: ctx.Trunc, n: network}
|
||||||
} else {
|
if err := format(networkCtx); err != nil {
|
||||||
ctx.Format = defaultNetworkTableFormat
|
return err
|
||||||
}
|
|
||||||
case rawFormatKey:
|
|
||||||
if ctx.Quiet {
|
|
||||||
ctx.Format = `network_id: {{.ID}}`
|
|
||||||
} else {
|
|
||||||
ctx.Format = `network_id: {{.ID}}\nname: {{.Name}}\ndriver: {{.Driver}}\nscope: {{.Scope}}\n`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
ctx.buffer = bytes.NewBufferString("")
|
|
||||||
ctx.preformat()
|
|
||||||
|
|
||||||
tmpl, err := ctx.parseFormat()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
return ctx.Write(&networkContext{}, render)
|
||||||
for _, network := range ctx.Networks {
|
|
||||||
networkCtx := &networkContext{
|
|
||||||
trunc: ctx.Trunc,
|
|
||||||
n: network,
|
|
||||||
}
|
|
||||||
err = ctx.contextFormat(tmpl, networkCtx)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.postformat(tmpl, &networkContext{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type networkContext struct {
|
type networkContext struct {
|
||||||
baseSubContext
|
HeaderContext
|
||||||
trunc bool
|
trunc bool
|
||||||
n types.NetworkResource
|
n types.NetworkResource
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *networkContext) ID() string {
|
func (c *networkContext) ID() string {
|
||||||
c.addHeader(networkIDHeader)
|
c.AddHeader(networkIDHeader)
|
||||||
if c.trunc {
|
if c.trunc {
|
||||||
return stringid.TruncateID(c.n.ID)
|
return stringid.TruncateID(c.n.ID)
|
||||||
}
|
}
|
||||||
|
@ -78,32 +62,32 @@ func (c *networkContext) ID() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *networkContext) Name() string {
|
func (c *networkContext) Name() string {
|
||||||
c.addHeader(nameHeader)
|
c.AddHeader(nameHeader)
|
||||||
return c.n.Name
|
return c.n.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *networkContext) Driver() string {
|
func (c *networkContext) Driver() string {
|
||||||
c.addHeader(driverHeader)
|
c.AddHeader(driverHeader)
|
||||||
return c.n.Driver
|
return c.n.Driver
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *networkContext) Scope() string {
|
func (c *networkContext) Scope() string {
|
||||||
c.addHeader(scopeHeader)
|
c.AddHeader(scopeHeader)
|
||||||
return c.n.Scope
|
return c.n.Scope
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *networkContext) IPv6() string {
|
func (c *networkContext) IPv6() string {
|
||||||
c.addHeader(ipv6Header)
|
c.AddHeader(ipv6Header)
|
||||||
return fmt.Sprintf("%v", c.n.EnableIPv6)
|
return fmt.Sprintf("%v", c.n.EnableIPv6)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *networkContext) Internal() string {
|
func (c *networkContext) Internal() string {
|
||||||
c.addHeader(internalHeader)
|
c.AddHeader(internalHeader)
|
||||||
return fmt.Sprintf("%v", c.n.Internal)
|
return fmt.Sprintf("%v", c.n.Internal)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *networkContext) Labels() string {
|
func (c *networkContext) Labels() string {
|
||||||
c.addHeader(labelsHeader)
|
c.AddHeader(labelsHeader)
|
||||||
if c.n.Labels == nil {
|
if c.n.Labels == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -120,7 +104,7 @@ func (c *networkContext) Label(name string) string {
|
||||||
r := strings.NewReplacer("-", " ", "_", " ")
|
r := strings.NewReplacer("-", " ", "_", " ")
|
||||||
h := r.Replace(n[len(n)-1])
|
h := r.Replace(n[len(n)-1])
|
||||||
|
|
||||||
c.addHeader(h)
|
c.AddHeader(h)
|
||||||
|
|
||||||
if c.n.Labels == nil {
|
if c.n.Labels == nil {
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
"github.com/docker/docker/pkg/testutil/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNetworkContext(t *testing.T) {
|
func TestNetworkContext(t *testing.T) {
|
||||||
|
@ -62,7 +63,7 @@ func TestNetworkContext(t *testing.T) {
|
||||||
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
|
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
h := ctx.fullHeader()
|
h := ctx.FullHeader()
|
||||||
if h != c.expHeader {
|
if h != c.expHeader {
|
||||||
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
|
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
|
||||||
}
|
}
|
||||||
|
@ -70,71 +71,45 @@ func TestNetworkContext(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNetworkContextWrite(t *testing.T) {
|
func TestNetworkContextWrite(t *testing.T) {
|
||||||
contexts := []struct {
|
cases := []struct {
|
||||||
context NetworkContext
|
context Context
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
{
|
{
|
||||||
NetworkContext{
|
Context{Format: "{{InvalidFunction}}"},
|
||||||
Context: Context{
|
|
||||||
Format: "{{InvalidFunction}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`Template parsing error: template: :1: function "InvalidFunction" not defined
|
`Template parsing error: template: :1: function "InvalidFunction" not defined
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
NetworkContext{
|
Context{Format: "{{nil}}"},
|
||||||
Context: Context{
|
|
||||||
Format: "{{nil}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
// Table format
|
// Table format
|
||||||
{
|
{
|
||||||
NetworkContext{
|
Context{Format: NewNetworkFormat("table", false)},
|
||||||
Context: Context{
|
|
||||||
Format: "table",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`NETWORK ID NAME DRIVER SCOPE
|
`NETWORK ID NAME DRIVER SCOPE
|
||||||
networkID1 foobar_baz foo local
|
networkID1 foobar_baz foo local
|
||||||
networkID2 foobar_bar bar local
|
networkID2 foobar_bar bar local
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
NetworkContext{
|
Context{Format: NewNetworkFormat("table", true)},
|
||||||
Context: Context{
|
|
||||||
Format: "table",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`networkID1
|
`networkID1
|
||||||
networkID2
|
networkID2
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
NetworkContext{
|
Context{Format: NewNetworkFormat("table {{.Name}}", false)},
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Name}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`NAME
|
`NAME
|
||||||
foobar_baz
|
foobar_baz
|
||||||
foobar_bar
|
foobar_bar
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
NetworkContext{
|
Context{Format: NewNetworkFormat("table {{.Name}}", true)},
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Name}}",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`NAME
|
`NAME
|
||||||
foobar_baz
|
foobar_baz
|
||||||
foobar_bar
|
foobar_bar
|
||||||
|
@ -142,11 +117,8 @@ foobar_bar
|
||||||
},
|
},
|
||||||
// Raw Format
|
// Raw Format
|
||||||
{
|
{
|
||||||
NetworkContext{
|
Context{Format: NewNetworkFormat("raw", false)},
|
||||||
Context: Context{
|
`network_id: networkID1
|
||||||
Format: "raw",
|
|
||||||
},
|
|
||||||
}, `network_id: networkID1
|
|
||||||
name: foobar_baz
|
name: foobar_baz
|
||||||
driver: foo
|
driver: foo
|
||||||
scope: local
|
scope: local
|
||||||
|
@ -159,43 +131,32 @@ scope: local
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
NetworkContext{
|
Context{Format: NewNetworkFormat("raw", true)},
|
||||||
Context: Context{
|
|
||||||
Format: "raw",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`network_id: networkID1
|
`network_id: networkID1
|
||||||
network_id: networkID2
|
network_id: networkID2
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
// Custom Format
|
// Custom Format
|
||||||
{
|
{
|
||||||
NetworkContext{
|
Context{Format: NewNetworkFormat("{{.Name}}", false)},
|
||||||
Context: Context{
|
|
||||||
Format: "{{.Name}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`foobar_baz
|
`foobar_baz
|
||||||
foobar_bar
|
foobar_bar
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, context := range contexts {
|
for _, testcase := range cases {
|
||||||
networks := []types.NetworkResource{
|
networks := []types.NetworkResource{
|
||||||
{ID: "networkID1", Name: "foobar_baz", Driver: "foo", Scope: "local"},
|
{ID: "networkID1", Name: "foobar_baz", Driver: "foo", Scope: "local"},
|
||||||
{ID: "networkID2", Name: "foobar_bar", Driver: "bar", Scope: "local"},
|
{ID: "networkID2", Name: "foobar_bar", Driver: "bar", Scope: "local"},
|
||||||
}
|
}
|
||||||
out := bytes.NewBufferString("")
|
out := bytes.NewBufferString("")
|
||||||
context.context.Output = out
|
testcase.context.Output = out
|
||||||
context.context.Networks = networks
|
err := NetworkWrite(testcase.context, networks)
|
||||||
context.context.Write()
|
if err != nil {
|
||||||
actual := out.String()
|
assert.Error(t, err, testcase.expected)
|
||||||
if actual != context.expected {
|
} else {
|
||||||
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
assert.Equal(t, out.String(), testcase.expected)
|
||||||
}
|
}
|
||||||
// Clean buffer
|
|
||||||
out.Reset()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -16,78 +15,63 @@ const (
|
||||||
// Status header ?
|
// Status header ?
|
||||||
)
|
)
|
||||||
|
|
||||||
// VolumeContext contains volume specific information required by the formatter,
|
// NewVolumeFormat returns a format for use with a volume Context
|
||||||
// encapsulate a Context struct.
|
func NewVolumeFormat(source string, quiet bool) Format {
|
||||||
type VolumeContext struct {
|
switch source {
|
||||||
Context
|
case TableFormatKey:
|
||||||
// Volumes
|
if quiet {
|
||||||
Volumes []*types.Volume
|
return defaultVolumeQuietFormat
|
||||||
|
}
|
||||||
|
return defaultVolumeTableFormat
|
||||||
|
case RawFormatKey:
|
||||||
|
if quiet {
|
||||||
|
return `name: {{.Name}}`
|
||||||
|
}
|
||||||
|
return `name: {{.Name}}\ndriver: {{.Driver}}\n`
|
||||||
|
}
|
||||||
|
return Format(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx VolumeContext) Write() {
|
// VolumeWrite writes formatted volumes using the Context
|
||||||
switch ctx.Format {
|
func VolumeWrite(ctx Context, volumes []*types.Volume) error {
|
||||||
case tableFormatKey:
|
render := func(format func(subContext subContext) error) error {
|
||||||
if ctx.Quiet {
|
for _, volume := range volumes {
|
||||||
ctx.Format = defaultVolumeQuietFormat
|
if err := format(&volumeContext{v: volume}); err != nil {
|
||||||
} else {
|
return err
|
||||||
ctx.Format = defaultVolumeTableFormat
|
|
||||||
}
|
|
||||||
case rawFormatKey:
|
|
||||||
if ctx.Quiet {
|
|
||||||
ctx.Format = `name: {{.Name}}`
|
|
||||||
} else {
|
|
||||||
ctx.Format = `name: {{.Name}}\ndriver: {{.Driver}}\n`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
ctx.buffer = bytes.NewBufferString("")
|
|
||||||
ctx.preformat()
|
|
||||||
|
|
||||||
tmpl, err := ctx.parseFormat()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
return ctx.Write(&volumeContext{}, render)
|
||||||
for _, volume := range ctx.Volumes {
|
|
||||||
volumeCtx := &volumeContext{
|
|
||||||
v: volume,
|
|
||||||
}
|
|
||||||
err = ctx.contextFormat(tmpl, volumeCtx)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.postformat(tmpl, &networkContext{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type volumeContext struct {
|
type volumeContext struct {
|
||||||
baseSubContext
|
HeaderContext
|
||||||
v *types.Volume
|
v *types.Volume
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *volumeContext) Name() string {
|
func (c *volumeContext) Name() string {
|
||||||
c.addHeader(nameHeader)
|
c.AddHeader(nameHeader)
|
||||||
return c.v.Name
|
return c.v.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *volumeContext) Driver() string {
|
func (c *volumeContext) Driver() string {
|
||||||
c.addHeader(driverHeader)
|
c.AddHeader(driverHeader)
|
||||||
return c.v.Driver
|
return c.v.Driver
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *volumeContext) Scope() string {
|
func (c *volumeContext) Scope() string {
|
||||||
c.addHeader(scopeHeader)
|
c.AddHeader(scopeHeader)
|
||||||
return c.v.Scope
|
return c.v.Scope
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *volumeContext) Mountpoint() string {
|
func (c *volumeContext) Mountpoint() string {
|
||||||
c.addHeader(mountpointHeader)
|
c.AddHeader(mountpointHeader)
|
||||||
return c.v.Mountpoint
|
return c.v.Mountpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *volumeContext) Labels() string {
|
func (c *volumeContext) Labels() string {
|
||||||
c.addHeader(labelsHeader)
|
c.AddHeader(labelsHeader)
|
||||||
if c.v.Labels == nil {
|
if c.v.Labels == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -105,7 +89,7 @@ func (c *volumeContext) Label(name string) string {
|
||||||
r := strings.NewReplacer("-", " ", "_", " ")
|
r := strings.NewReplacer("-", " ", "_", " ")
|
||||||
h := r.Replace(n[len(n)-1])
|
h := r.Replace(n[len(n)-1])
|
||||||
|
|
||||||
c.addHeader(h)
|
c.AddHeader(h)
|
||||||
|
|
||||||
if c.v.Labels == nil {
|
if c.v.Labels == nil {
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/pkg/stringid"
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
"github.com/docker/docker/pkg/testutil/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestVolumeContext(t *testing.T) {
|
func TestVolumeContext(t *testing.T) {
|
||||||
|
@ -48,7 +49,7 @@ func TestVolumeContext(t *testing.T) {
|
||||||
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
|
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
h := ctx.fullHeader()
|
h := ctx.FullHeader()
|
||||||
if h != c.expHeader {
|
if h != c.expHeader {
|
||||||
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
|
t.Fatalf("Expected %s, was %s\n", c.expHeader, h)
|
||||||
}
|
}
|
||||||
|
@ -56,71 +57,45 @@ func TestVolumeContext(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVolumeContextWrite(t *testing.T) {
|
func TestVolumeContextWrite(t *testing.T) {
|
||||||
contexts := []struct {
|
cases := []struct {
|
||||||
context VolumeContext
|
context Context
|
||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
{
|
{
|
||||||
VolumeContext{
|
Context{Format: "{{InvalidFunction}}"},
|
||||||
Context: Context{
|
|
||||||
Format: "{{InvalidFunction}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`Template parsing error: template: :1: function "InvalidFunction" not defined
|
`Template parsing error: template: :1: function "InvalidFunction" not defined
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
VolumeContext{
|
Context{Format: "{{nil}}"},
|
||||||
Context: Context{
|
|
||||||
Format: "{{nil}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
// Table format
|
// Table format
|
||||||
{
|
{
|
||||||
VolumeContext{
|
Context{Format: NewVolumeFormat("table", false)},
|
||||||
Context: Context{
|
|
||||||
Format: "table",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`DRIVER NAME
|
`DRIVER NAME
|
||||||
foo foobar_baz
|
foo foobar_baz
|
||||||
bar foobar_bar
|
bar foobar_bar
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
VolumeContext{
|
Context{Format: NewVolumeFormat("table", true)},
|
||||||
Context: Context{
|
|
||||||
Format: "table",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`foobar_baz
|
`foobar_baz
|
||||||
foobar_bar
|
foobar_bar
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
VolumeContext{
|
Context{Format: NewVolumeFormat("table {{.Name}}", false)},
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Name}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`NAME
|
`NAME
|
||||||
foobar_baz
|
foobar_baz
|
||||||
foobar_bar
|
foobar_bar
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
VolumeContext{
|
Context{Format: NewVolumeFormat("table {{.Name}}", true)},
|
||||||
Context: Context{
|
|
||||||
Format: "table {{.Name}}",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`NAME
|
`NAME
|
||||||
foobar_baz
|
foobar_baz
|
||||||
foobar_bar
|
foobar_bar
|
||||||
|
@ -128,11 +103,8 @@ foobar_bar
|
||||||
},
|
},
|
||||||
// Raw Format
|
// Raw Format
|
||||||
{
|
{
|
||||||
VolumeContext{
|
Context{Format: NewVolumeFormat("raw", false)},
|
||||||
Context: Context{
|
`name: foobar_baz
|
||||||
Format: "raw",
|
|
||||||
},
|
|
||||||
}, `name: foobar_baz
|
|
||||||
driver: foo
|
driver: foo
|
||||||
|
|
||||||
name: foobar_bar
|
name: foobar_bar
|
||||||
|
@ -141,43 +113,32 @@ driver: bar
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
VolumeContext{
|
Context{Format: NewVolumeFormat("raw", true)},
|
||||||
Context: Context{
|
|
||||||
Format: "raw",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`name: foobar_baz
|
`name: foobar_baz
|
||||||
name: foobar_bar
|
name: foobar_bar
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
// Custom Format
|
// Custom Format
|
||||||
{
|
{
|
||||||
VolumeContext{
|
Context{Format: NewVolumeFormat("{{.Name}}", false)},
|
||||||
Context: Context{
|
|
||||||
Format: "{{.Name}}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
`foobar_baz
|
`foobar_baz
|
||||||
foobar_bar
|
foobar_bar
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, context := range contexts {
|
for _, testcase := range cases {
|
||||||
volumes := []*types.Volume{
|
volumes := []*types.Volume{
|
||||||
{Name: "foobar_baz", Driver: "foo"},
|
{Name: "foobar_baz", Driver: "foo"},
|
||||||
{Name: "foobar_bar", Driver: "bar"},
|
{Name: "foobar_bar", Driver: "bar"},
|
||||||
}
|
}
|
||||||
out := bytes.NewBufferString("")
|
out := bytes.NewBufferString("")
|
||||||
context.context.Output = out
|
testcase.context.Output = out
|
||||||
context.context.Volumes = volumes
|
err := VolumeWrite(testcase.context, volumes)
|
||||||
context.context.Write()
|
if err != nil {
|
||||||
actual := out.String()
|
assert.Error(t, err, testcase.expected)
|
||||||
if actual != context.expected {
|
} else {
|
||||||
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
assert.Equal(t, out.String(), testcase.expected)
|
||||||
}
|
}
|
||||||
// Clean buffer
|
|
||||||
out.Reset()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,27 +64,22 @@ func runImages(dockerCli *command.DockerCli, opts imagesOptions) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
f := opts.format
|
format := opts.format
|
||||||
if len(f) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().ImagesFormat) > 0 && !opts.quiet {
|
if len(dockerCli.ConfigFile().ImagesFormat) > 0 && !opts.quiet {
|
||||||
f = dockerCli.ConfigFile().ImagesFormat
|
format = dockerCli.ConfigFile().ImagesFormat
|
||||||
} else {
|
} else {
|
||||||
f = "table"
|
format = "table"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
imagesCtx := formatter.ImageContext{
|
imageCtx := formatter.ImageContext{
|
||||||
Context: formatter.Context{
|
Context: formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: f,
|
Format: formatter.NewImageFormat(format, opts.quiet, opts.showDigests),
|
||||||
Quiet: opts.quiet,
|
|
||||||
Trunc: !opts.noTrunc,
|
Trunc: !opts.noTrunc,
|
||||||
},
|
},
|
||||||
Digest: opts.showDigests,
|
Digest: opts.showDigests,
|
||||||
Images: images,
|
|
||||||
}
|
}
|
||||||
|
return formatter.ImageWrite(imageCtx, images)
|
||||||
imagesCtx.Write()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,35 +50,27 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
|
|
||||||
func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
|
|
||||||
options := types.NetworkListOptions{Filters: opts.filter.Value()}
|
options := types.NetworkListOptions{Filters: opts.filter.Value()}
|
||||||
networkResources, err := client.NetworkList(context.Background(), options)
|
networkResources, err := client.NetworkList(context.Background(), options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
f := opts.format
|
format := opts.format
|
||||||
if len(f) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().NetworksFormat) > 0 && !opts.quiet {
|
if len(dockerCli.ConfigFile().NetworksFormat) > 0 && !opts.quiet {
|
||||||
f = dockerCli.ConfigFile().NetworksFormat
|
format = dockerCli.ConfigFile().NetworksFormat
|
||||||
} else {
|
} else {
|
||||||
f = "table"
|
format = "table"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(byNetworkName(networkResources))
|
sort.Sort(byNetworkName(networkResources))
|
||||||
|
|
||||||
networksCtx := formatter.NetworkContext{
|
networksCtx := formatter.Context{
|
||||||
Context: formatter.Context{
|
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: f,
|
Format: formatter.NewNetworkFormat(format, opts.quiet),
|
||||||
Quiet: opts.quiet,
|
|
||||||
Trunc: !opts.noTrunc,
|
Trunc: !opts.noTrunc,
|
||||||
},
|
|
||||||
Networks: networkResources,
|
|
||||||
}
|
}
|
||||||
|
return formatter.NetworkWrite(networksCtx, networkResources)
|
||||||
networksCtx.Write()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,29 +56,22 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
f := opts.format
|
format := opts.format
|
||||||
if len(f) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().VolumesFormat) > 0 && !opts.quiet {
|
if len(dockerCli.ConfigFile().VolumesFormat) > 0 && !opts.quiet {
|
||||||
f = dockerCli.ConfigFile().VolumesFormat
|
format = dockerCli.ConfigFile().VolumesFormat
|
||||||
} else {
|
} else {
|
||||||
f = "table"
|
format = "table"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(byVolumeName(volumes.Volumes))
|
sort.Sort(byVolumeName(volumes.Volumes))
|
||||||
|
|
||||||
volumeCtx := formatter.VolumeContext{
|
volumeCtx := formatter.Context{
|
||||||
Context: formatter.Context{
|
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: f,
|
Format: formatter.NewVolumeFormat(format, opts.quiet),
|
||||||
Quiet: opts.quiet,
|
|
||||||
},
|
|
||||||
Volumes: volumes.Volumes,
|
|
||||||
}
|
}
|
||||||
|
return formatter.VolumeWrite(volumeCtx, volumes.Volumes)
|
||||||
volumeCtx.Write()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var listDescription = `
|
var listDescription = `
|
||||||
|
|
Loading…
Reference in New Issue