diff --git a/cli/command/formatter/checkpoint.go b/cli/command/checkpoint/formatter.go similarity index 52% rename from cli/command/formatter/checkpoint.go rename to cli/command/checkpoint/formatter.go index 041fcafb7d..1ad3aafddd 100644 --- a/cli/command/formatter/checkpoint.go +++ b/cli/command/checkpoint/formatter.go @@ -1,6 +1,9 @@ -package formatter +package checkpoint -import "github.com/docker/docker/api/types" +import ( + "github.com/docker/cli/cli/command/formatter" + "github.com/docker/docker/api/types" +) const ( defaultCheckpointFormat = "table {{.Name}}" @@ -8,18 +11,18 @@ const ( checkpointNameHeader = "CHECKPOINT NAME" ) -// NewCheckpointFormat returns a format for use with a checkpoint Context -func NewCheckpointFormat(source string) Format { +// NewFormat returns a format for use with a checkpoint Context +func NewFormat(source string) formatter.Format { switch source { - case TableFormatKey: + case formatter.TableFormatKey: return defaultCheckpointFormat } - return Format(source) + return formatter.Format(source) } -// CheckpointWrite writes formatted checkpoints using the Context -func CheckpointWrite(ctx Context, checkpoints []types.Checkpoint) error { - render := func(format func(subContext subContext) error) error { +// FormatWrite writes formatted checkpoints using the Context +func FormatWrite(ctx formatter.Context, checkpoints []types.Checkpoint) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, checkpoint := range checkpoints { if err := format(&checkpointContext{c: checkpoint}); err != nil { return err @@ -31,20 +34,20 @@ func CheckpointWrite(ctx Context, checkpoints []types.Checkpoint) error { } type checkpointContext struct { - HeaderContext + formatter.HeaderContext c types.Checkpoint } func newCheckpointContext() *checkpointContext { cpCtx := checkpointContext{} - cpCtx.header = volumeHeaderContext{ + cpCtx.Header = formatter.SubHeaderContext{ "Name": checkpointNameHeader, } return &cpCtx } func (c *checkpointContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return formatter.MarshalJSON(c) } func (c *checkpointContext) Name() string { diff --git a/cli/command/formatter/checkpoint_test.go b/cli/command/checkpoint/formatter_test.go similarity index 68% rename from cli/command/formatter/checkpoint_test.go rename to cli/command/checkpoint/formatter_test.go index 9b8ac5e505..7256b2fbda 100644 --- a/cli/command/formatter/checkpoint_test.go +++ b/cli/command/checkpoint/formatter_test.go @@ -1,20 +1,21 @@ -package formatter +package checkpoint import ( "bytes" "testing" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/docker/api/types" "gotest.tools/assert" ) func TestCheckpointContextFormatWrite(t *testing.T) { cases := []struct { - context Context + context formatter.Context expected string }{ { - Context{Format: NewCheckpointFormat(defaultCheckpointFormat)}, + formatter.Context{Format: NewFormat(defaultCheckpointFormat)}, `CHECKPOINT NAME checkpoint-1 checkpoint-2 @@ -22,14 +23,14 @@ checkpoint-3 `, }, { - Context{Format: NewCheckpointFormat("{{.Name}}")}, + formatter.Context{Format: NewFormat("{{.Name}}")}, `checkpoint-1 checkpoint-2 checkpoint-3 `, }, { - Context{Format: NewCheckpointFormat("{{.Name}}:")}, + formatter.Context{Format: NewFormat("{{.Name}}:")}, `checkpoint-1: checkpoint-2: checkpoint-3: @@ -45,7 +46,7 @@ checkpoint-3: for _, testcase := range cases { out := bytes.NewBufferString("") testcase.context.Output = out - err := CheckpointWrite(testcase.context, checkpoints) + err := FormatWrite(testcase.context, checkpoints) assert.NilError(t, err) assert.Equal(t, out.String(), testcase.expected) } diff --git a/cli/command/checkpoint/list.go b/cli/command/checkpoint/list.go index 7b041cfe89..fda23e2980 100644 --- a/cli/command/checkpoint/list.go +++ b/cli/command/checkpoint/list.go @@ -48,7 +48,7 @@ func runList(dockerCli command.Cli, container string, opts listOptions) error { cpCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewCheckpointFormat(formatter.TableFormatKey), + Format: NewFormat(formatter.TableFormatKey), } - return formatter.CheckpointWrite(cpCtx, checkpoints) + return FormatWrite(cpCtx, checkpoints) } diff --git a/cli/command/formatter/config.go b/cli/command/config/formatter.go similarity index 70% rename from cli/command/formatter/config.go rename to cli/command/config/formatter.go index 72203f35fb..4aebdb61ac 100644 --- a/cli/command/formatter/config.go +++ b/cli/command/config/formatter.go @@ -1,4 +1,4 @@ -package formatter +package config import ( "fmt" @@ -6,17 +6,18 @@ import ( "time" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/inspect" "github.com/docker/docker/api/types/swarm" units "github.com/docker/go-units" ) const ( - defaultConfigTableFormat = "table {{.ID}}\t{{.Name}}\t{{.CreatedAt}}\t{{.UpdatedAt}}" - configIDHeader = "ID" - configCreatedHeader = "CREATED" - configUpdatedHeader = "UPDATED" - configInspectPrettyTemplate Format = `ID: {{.ID}} + defaultConfigTableFormat = "table {{.ID}}\t{{.Name}}\t{{.CreatedAt}}\t{{.UpdatedAt}}" + configIDHeader = "ID" + configCreatedHeader = "CREATED" + configUpdatedHeader = "UPDATED" + configInspectPrettyTemplate formatter.Format = `ID: {{.ID}} Name: {{.Name}} {{- if .Labels }} Labels: @@ -29,23 +30,23 @@ Data: {{.Data}}` ) -// NewConfigFormat returns a Format for rendering using a config Context -func NewConfigFormat(source string, quiet bool) Format { +// NewFormat returns a Format for rendering using a config Context +func NewFormat(source string, quiet bool) formatter.Format { switch source { - case PrettyFormatKey: + case formatter.PrettyFormatKey: return configInspectPrettyTemplate - case TableFormatKey: + case formatter.TableFormatKey: if quiet { - return defaultQuietFormat + return formatter.DefaultQuietFormat } return defaultConfigTableFormat } - return Format(source) + return formatter.Format(source) } -// ConfigWrite writes the context -func ConfigWrite(ctx Context, configs []swarm.Config) error { - render := func(format func(subContext subContext) error) error { +// FormatWrite writes the context +func FormatWrite(ctx formatter.Context, configs []swarm.Config) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, config := range configs { configCtx := &configContext{c: config} if err := format(configCtx); err != nil { @@ -60,23 +61,23 @@ func ConfigWrite(ctx Context, configs []swarm.Config) error { func newConfigContext() *configContext { cCtx := &configContext{} - cCtx.header = map[string]string{ + cCtx.Header = formatter.SubHeaderContext{ "ID": configIDHeader, - "Name": nameHeader, + "Name": formatter.NameHeader, "CreatedAt": configCreatedHeader, "UpdatedAt": configUpdatedHeader, - "Labels": labelsHeader, + "Labels": formatter.LabelsHeader, } return cCtx } type configContext struct { - HeaderContext + formatter.HeaderContext c swarm.Config } func (c *configContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return formatter.MarshalJSON(c) } func (c *configContext) ID() string { @@ -114,12 +115,12 @@ func (c *configContext) Label(name string) string { return c.c.Spec.Annotations.Labels[name] } -// ConfigInspectWrite renders the context for a list of configs -func ConfigInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) error { +// InspectFormatWrite renders the context for a list of configs +func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.GetRefFunc) error { if ctx.Format != configInspectPrettyTemplate { return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef) } - render := func(format func(subContext subContext) error) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, ref := range refs { configI, _, err := getRef(ref) if err != nil { @@ -140,7 +141,7 @@ func ConfigInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) e type configInspectContext struct { swarm.Config - subContext + formatter.SubContext } func (ctx *configInspectContext) ID() string { diff --git a/cli/command/formatter/config_test.go b/cli/command/config/formatter_test.go similarity index 76% rename from cli/command/formatter/config_test.go rename to cli/command/config/formatter_test.go index 7cd310c9ec..aa8de73cbf 100644 --- a/cli/command/formatter/config_test.go +++ b/cli/command/config/formatter_test.go @@ -1,10 +1,11 @@ -package formatter +package config import ( "bytes" "testing" "time" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/docker/api/types/swarm" "gotest.tools/assert" is "gotest.tools/assert/cmp" @@ -13,32 +14,32 @@ import ( func TestConfigContextFormatWrite(t *testing.T) { // Check default output format (verbose and non-verbose mode) for table headers cases := []struct { - context Context + context formatter.Context expected string }{ // Errors { - Context{Format: "{{InvalidFunction}}"}, + formatter.Context{Format: "{{InvalidFunction}}"}, `Template parsing error: template: :1: function "InvalidFunction" not defined `, }, { - Context{Format: "{{nil}}"}, + formatter.Context{Format: "{{nil}}"}, `Template parsing error: template: :1:2: executing "" at : nil is not a command `, }, // Table format - {Context{Format: NewConfigFormat("table", false)}, + {formatter.Context{Format: NewFormat("table", false)}, `ID NAME CREATED UPDATED 1 passwords Less than a second ago Less than a second ago 2 id_rsa Less than a second ago Less than a second ago `}, - {Context{Format: NewConfigFormat("table {{.Name}}", true)}, + {formatter.Context{Format: NewFormat("table {{.Name}}", true)}, `NAME passwords id_rsa `}, - {Context{Format: NewConfigFormat("{{.ID}}-{{.Name}}", false)}, + {formatter.Context{Format: NewFormat("{{.ID}}-{{.Name}}", false)}, `1-passwords 2-id_rsa `}, @@ -55,7 +56,7 @@ id_rsa for _, testcase := range cases { out := bytes.NewBufferString("") testcase.context.Output = out - if err := ConfigWrite(testcase.context, configs); err != nil { + if err := FormatWrite(testcase.context, configs); err != nil { assert.ErrorContains(t, err, testcase.expected) } else { assert.Check(t, is.Equal(out.String(), testcase.expected)) diff --git a/cli/command/config/inspect.go b/cli/command/config/inspect.go index d1515ec962..ef11db713f 100644 --- a/cli/command/config/inspect.go +++ b/cli/command/config/inspect.go @@ -55,10 +55,10 @@ func runConfigInspect(dockerCli command.Cli, opts inspectOptions) error { configCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewConfigFormat(f, false), + Format: NewFormat(f, false), } - if err := formatter.ConfigInspectWrite(configCtx, opts.names, getRef); err != nil { + if err := InspectFormatWrite(configCtx, opts.names, getRef); err != nil { return cli.StatusError{StatusCode: 1, Status: err.Error()} } return nil diff --git a/cli/command/config/ls.go b/cli/command/config/ls.go index d87a3c9de4..849eb2bbc7 100644 --- a/cli/command/config/ls.go +++ b/cli/command/config/ls.go @@ -64,7 +64,7 @@ func runConfigList(dockerCli command.Cli, options listOptions) error { configCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewConfigFormat(format, options.quiet), + Format: NewFormat(format, options.quiet), } - return formatter.ConfigWrite(configCtx, configs) + return FormatWrite(configCtx, configs) } diff --git a/cli/command/container/diff.go b/cli/command/container/diff.go index 39b71c8062..1cfb63c654 100644 --- a/cli/command/container/diff.go +++ b/cli/command/container/diff.go @@ -41,7 +41,7 @@ func runDiff(dockerCli command.Cli, opts *diffOptions) error { } diffCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewDiffFormat("{{.Type}} {{.Path}}"), + Format: NewDiffFormat("{{.Type}} {{.Path}}"), } - return formatter.DiffWrite(diffCtx, changes) + return DiffFormatWrite(diffCtx, changes) } diff --git a/cli/command/formatter/diff.go b/cli/command/container/formatter_diff.go similarity index 67% rename from cli/command/formatter/diff.go rename to cli/command/container/formatter_diff.go index 9b4681934c..cf6a0b0b4a 100644 --- a/cli/command/formatter/diff.go +++ b/cli/command/container/formatter_diff.go @@ -1,6 +1,7 @@ -package formatter +package container import ( + "github.com/docker/cli/cli/command/formatter" "github.com/docker/docker/api/types/container" "github.com/docker/docker/pkg/archive" ) @@ -13,18 +14,18 @@ const ( ) // NewDiffFormat returns a format for use with a diff Context -func NewDiffFormat(source string) Format { +func NewDiffFormat(source string) formatter.Format { switch source { - case TableFormatKey: + case formatter.TableFormatKey: return defaultDiffTableFormat } - return Format(source) + return formatter.Format(source) } -// DiffWrite writes formatted diff using the Context -func DiffWrite(ctx Context, changes []container.ContainerChangeResponseItem) error { +// DiffFormatWrite writes formatted diff using the Context +func DiffFormatWrite(ctx formatter.Context, changes []container.ContainerChangeResponseItem) error { - render := func(format func(subContext subContext) error) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, change := range changes { if err := format(&diffContext{c: change}); err != nil { return err @@ -36,13 +37,13 @@ func DiffWrite(ctx Context, changes []container.ContainerChangeResponseItem) err } type diffContext struct { - HeaderContext + formatter.HeaderContext c container.ContainerChangeResponseItem } func newDiffContext() *diffContext { diffCtx := diffContext{} - diffCtx.header = map[string]string{ + diffCtx.Header = formatter.SubHeaderContext{ "Type": changeTypeHeader, "Path": pathHeader, } @@ -50,7 +51,7 @@ func newDiffContext() *diffContext { } func (d *diffContext) MarshalJSON() ([]byte, error) { - return marshalJSON(d) + return formatter.MarshalJSON(d) } func (d *diffContext) Type() string { diff --git a/cli/command/formatter/diff_test.go b/cli/command/container/formatter_diff_test.go similarity index 77% rename from cli/command/formatter/diff_test.go rename to cli/command/container/formatter_diff_test.go index aad5945057..e29e8eade5 100644 --- a/cli/command/formatter/diff_test.go +++ b/cli/command/container/formatter_diff_test.go @@ -1,9 +1,10 @@ -package formatter +package container import ( "bytes" "testing" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/docker/api/types/container" "github.com/docker/docker/pkg/archive" "gotest.tools/assert" @@ -13,11 +14,11 @@ import ( func TestDiffContextFormatWrite(t *testing.T) { // Check default output format (verbose and non-verbose mode) for table headers cases := []struct { - context Context + context formatter.Context expected string }{ { - Context{Format: NewDiffFormat("table")}, + formatter.Context{Format: NewDiffFormat("table")}, `CHANGE TYPE PATH C /var/log/app.log A /usr/app/app.js @@ -25,7 +26,7 @@ D /usr/app/old_app.js `, }, { - Context{Format: NewDiffFormat("table {{.Path}}")}, + formatter.Context{Format: NewDiffFormat("table {{.Path}}")}, `PATH /var/log/app.log /usr/app/app.js @@ -33,7 +34,7 @@ D /usr/app/old_app.js `, }, { - Context{Format: NewDiffFormat("{{.Type}}: {{.Path}}")}, + formatter.Context{Format: NewDiffFormat("{{.Type}}: {{.Path}}")}, `C: /var/log/app.log A: /usr/app/app.js D: /usr/app/old_app.js @@ -50,7 +51,7 @@ D: /usr/app/old_app.js for _, testcase := range cases { out := bytes.NewBufferString("") testcase.context.Output = out - err := DiffWrite(testcase.context, diffs) + err := DiffFormatWrite(testcase.context, diffs) if err != nil { assert.Error(t, err, testcase.expected) } else { diff --git a/cli/command/formatter/stats.go b/cli/command/container/formatter_stats.go similarity index 66% rename from cli/command/formatter/stats.go rename to cli/command/container/formatter_stats.go index 0c210c6f1f..565de4470b 100644 --- a/cli/command/formatter/stats.go +++ b/cli/command/container/formatter_stats.go @@ -1,9 +1,10 @@ -package formatter +package container import ( "fmt" "sync" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/docker/pkg/stringid" units "github.com/docker/go-units" ) @@ -40,8 +41,8 @@ type StatsEntry struct { IsInvalid bool } -// ContainerStats represents an entity to store containers statistics synchronously -type ContainerStats struct { +// Stats represents an entity to store containers statistics synchronously +type Stats struct { mutex sync.Mutex StatsEntry err error @@ -49,7 +50,7 @@ type ContainerStats struct { // GetError returns the container statistics error. // This is used to determine whether the statistics are valid or not -func (cs *ContainerStats) GetError() error { +func (cs *Stats) GetError() error { cs.mutex.Lock() defer cs.mutex.Unlock() return cs.err @@ -57,7 +58,7 @@ func (cs *ContainerStats) GetError() error { // SetErrorAndReset zeroes all the container statistics and store the error. // It is used when receiving time out error during statistics collecting to reduce lock overhead -func (cs *ContainerStats) SetErrorAndReset(err error) { +func (cs *Stats) SetErrorAndReset(err error) { cs.mutex.Lock() defer cs.mutex.Unlock() cs.CPUPercentage = 0 @@ -74,7 +75,7 @@ func (cs *ContainerStats) SetErrorAndReset(err error) { } // SetError sets container statistics error -func (cs *ContainerStats) SetError(err error) { +func (cs *Stats) SetError(err error) { cs.mutex.Lock() defer cs.mutex.Unlock() cs.err = err @@ -84,7 +85,7 @@ func (cs *ContainerStats) SetError(err error) { } // SetStatistics set the container statistics -func (cs *ContainerStats) SetStatistics(s StatsEntry) { +func (cs *Stats) SetStatistics(s StatsEntry) { cs.mutex.Lock() defer cs.mutex.Unlock() s.Container = cs.Container @@ -92,38 +93,38 @@ func (cs *ContainerStats) SetStatistics(s StatsEntry) { } // GetStatistics returns container statistics with other meta data such as the container name -func (cs *ContainerStats) GetStatistics() StatsEntry { +func (cs *Stats) GetStatistics() StatsEntry { cs.mutex.Lock() defer cs.mutex.Unlock() return cs.StatsEntry } // NewStatsFormat returns a format for rendering an CStatsContext -func NewStatsFormat(source, osType string) Format { - if source == TableFormatKey { +func NewStatsFormat(source, osType string) formatter.Format { + if source == formatter.TableFormatKey { if osType == winOSType { - return Format(winDefaultStatsTableFormat) + return formatter.Format(winDefaultStatsTableFormat) } - return Format(defaultStatsTableFormat) + return formatter.Format(defaultStatsTableFormat) } - return Format(source) + return formatter.Format(source) } -// NewContainerStats returns a new ContainerStats entity and sets in it the given name -func NewContainerStats(container string) *ContainerStats { - return &ContainerStats{StatsEntry: StatsEntry{Container: container}} +// NewStats returns a new Stats entity and sets in it the given name +func NewStats(container string) *Stats { + return &Stats{StatsEntry: StatsEntry{Container: container}} } -// ContainerStatsWrite renders the context for a list of containers statistics -func ContainerStatsWrite(ctx Context, containerStats []StatsEntry, osType string, trunc bool) error { - render := func(format func(subContext subContext) error) error { - for _, cstats := range containerStats { - containerStatsCtx := &containerStatsContext{ +// statsFormatWrite renders the context for a list of containers statistics +func statsFormatWrite(ctx formatter.Context, Stats []StatsEntry, osType string, trunc bool) error { + render := func(format func(subContext formatter.SubContext) error) error { + for _, cstats := range Stats { + statsCtx := &statsContext{ s: cstats, os: osType, trunc: trunc, } - if err := format(containerStatsCtx); err != nil { + if err := format(statsCtx); err != nil { return err } } @@ -133,11 +134,11 @@ func ContainerStatsWrite(ctx Context, containerStats []StatsEntry, osType string if osType == winOSType { memUsage = winMemUseHeader } - containerStatsCtx := containerStatsContext{} - containerStatsCtx.header = map[string]string{ + statsCtx := statsContext{} + statsCtx.Header = formatter.SubHeaderContext{ "Container": containerHeader, - "Name": nameHeader, - "ID": containerIDHeader, + "Name": formatter.NameHeader, + "ID": formatter.ContainerIDHeader, "CPUPerc": cpuPercHeader, "MemUsage": memUsage, "MemPerc": memPercHeader, @@ -145,47 +146,47 @@ func ContainerStatsWrite(ctx Context, containerStats []StatsEntry, osType string "BlockIO": blockIOHeader, "PIDs": pidsHeader, } - containerStatsCtx.os = osType - return ctx.Write(&containerStatsCtx, render) + statsCtx.os = osType + return ctx.Write(&statsCtx, render) } -type containerStatsContext struct { - HeaderContext +type statsContext struct { + formatter.HeaderContext s StatsEntry os string trunc bool } -func (c *containerStatsContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) +func (c *statsContext) MarshalJSON() ([]byte, error) { + return formatter.MarshalJSON(c) } -func (c *containerStatsContext) Container() string { +func (c *statsContext) Container() string { return c.s.Container } -func (c *containerStatsContext) Name() string { +func (c *statsContext) Name() string { if len(c.s.Name) > 1 { return c.s.Name[1:] } return "--" } -func (c *containerStatsContext) ID() string { +func (c *statsContext) ID() string { if c.trunc { return stringid.TruncateID(c.s.ID) } return c.s.ID } -func (c *containerStatsContext) CPUPerc() string { +func (c *statsContext) CPUPerc() string { if c.s.IsInvalid { return fmt.Sprintf("--") } return fmt.Sprintf("%.2f%%", c.s.CPUPercentage) } -func (c *containerStatsContext) MemUsage() string { +func (c *statsContext) MemUsage() string { if c.s.IsInvalid { return fmt.Sprintf("-- / --") } @@ -195,28 +196,28 @@ func (c *containerStatsContext) MemUsage() string { return fmt.Sprintf("%s / %s", units.BytesSize(c.s.Memory), units.BytesSize(c.s.MemoryLimit)) } -func (c *containerStatsContext) MemPerc() string { +func (c *statsContext) MemPerc() string { if c.s.IsInvalid || c.os == winOSType { return fmt.Sprintf("--") } return fmt.Sprintf("%.2f%%", c.s.MemoryPercentage) } -func (c *containerStatsContext) NetIO() string { +func (c *statsContext) NetIO() string { if c.s.IsInvalid { return fmt.Sprintf("--") } return fmt.Sprintf("%s / %s", units.HumanSizeWithPrecision(c.s.NetworkRx, 3), units.HumanSizeWithPrecision(c.s.NetworkTx, 3)) } -func (c *containerStatsContext) BlockIO() string { +func (c *statsContext) BlockIO() string { if c.s.IsInvalid { return fmt.Sprintf("--") } return fmt.Sprintf("%s / %s", units.HumanSizeWithPrecision(c.s.BlockRead, 3), units.HumanSizeWithPrecision(c.s.BlockWrite, 3)) } -func (c *containerStatsContext) PIDs() string { +func (c *statsContext) PIDs() string { if c.s.IsInvalid || c.os == winOSType { return fmt.Sprintf("--") } diff --git a/cli/command/formatter/stats_test.go b/cli/command/container/formatter_stats_test.go similarity index 81% rename from cli/command/formatter/stats_test.go rename to cli/command/container/formatter_stats_test.go index 3325dd9fba..68e76625ab 100644 --- a/cli/command/formatter/stats_test.go +++ b/cli/command/container/formatter_stats_test.go @@ -1,9 +1,10 @@ -package formatter +package container import ( "bytes" "testing" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/docker/pkg/stringid" "gotest.tools/assert" is "gotest.tools/assert/cmp" @@ -12,7 +13,7 @@ import ( func TestContainerStatsContext(t *testing.T) { containerID := stringid.GenerateRandomID() - var ctx containerStatsContext + var ctx statsContext tt := []struct { stats StatsEntry osType string @@ -39,7 +40,7 @@ func TestContainerStatsContext(t *testing.T) { } for _, te := range tt { - ctx = containerStatsContext{s: te.stats, os: te.osType} + ctx = statsContext{s: te.stats, os: te.osType} if v := te.call(); v != te.expValue { t.Fatalf("Expected %q, got %q", te.expValue, v) } @@ -48,34 +49,34 @@ func TestContainerStatsContext(t *testing.T) { func TestContainerStatsContextWrite(t *testing.T) { tt := []struct { - context Context + context formatter.Context expected string }{ { - Context{Format: "{{InvalidFunction}}"}, + formatter.Context{Format: "{{InvalidFunction}}"}, `Template parsing error: template: :1: function "InvalidFunction" not defined `, }, { - Context{Format: "{{nil}}"}, + formatter.Context{Format: "{{nil}}"}, `Template parsing error: template: :1:2: executing "" at : nil is not a command `, }, { - Context{Format: "table {{.MemUsage}}"}, + formatter.Context{Format: "table {{.MemUsage}}"}, `MEM USAGE / LIMIT 20B / 20B -- / -- `, }, { - Context{Format: "{{.Container}} {{.ID}} {{.Name}}"}, + formatter.Context{Format: "{{.Container}} {{.ID}} {{.Name}}"}, `container1 abcdef foo container2 -- `, }, { - Context{Format: "{{.Container}} {{.CPUPerc}}"}, + formatter.Context{Format: "{{.Container}} {{.CPUPerc}}"}, `container1 20.00% container2 -- `, @@ -115,7 +116,7 @@ container2 -- } var out bytes.Buffer te.context.Output = &out - err := ContainerStatsWrite(te.context, stats, "linux", false) + err := statsFormatWrite(te.context, stats, "linux", false) if err != nil { assert.Error(t, err, te.expected) } else { @@ -126,24 +127,24 @@ container2 -- func TestContainerStatsContextWriteWindows(t *testing.T) { tt := []struct { - context Context + context formatter.Context expected string }{ { - Context{Format: "table {{.MemUsage}}"}, + formatter.Context{Format: "table {{.MemUsage}}"}, `PRIV WORKING SET 20B -- / -- `, }, { - Context{Format: "{{.Container}} {{.CPUPerc}}"}, + formatter.Context{Format: "{{.Container}} {{.CPUPerc}}"}, `container1 20.00% container2 -- `, }, { - Context{Format: "{{.Container}} {{.MemPerc}} {{.PIDs}}"}, + formatter.Context{Format: "{{.Container}} {{.MemPerc}} {{.PIDs}}"}, `container1 -- -- container2 -- -- `, @@ -181,7 +182,7 @@ container2 -- -- } var out bytes.Buffer te.context.Output = &out - err := ContainerStatsWrite(te.context, stats, "windows", false) + err := statsFormatWrite(te.context, stats, "windows", false) if err != nil { assert.Error(t, err, te.expected) } else { @@ -194,25 +195,25 @@ func TestContainerStatsContextWriteWithNoStats(t *testing.T) { var out bytes.Buffer contexts := []struct { - context Context + context formatter.Context expected string }{ { - Context{ + formatter.Context{ Format: "{{.Container}}", Output: &out, }, "", }, { - Context{ + formatter.Context{ Format: "table {{.Container}}", Output: &out, }, "CONTAINER\n", }, { - Context{ + formatter.Context{ Format: "table {{.Container}}\t{{.CPUPerc}}", Output: &out, }, @@ -221,7 +222,7 @@ func TestContainerStatsContextWriteWithNoStats(t *testing.T) { } for _, context := range contexts { - ContainerStatsWrite(context.context, []StatsEntry{}, "linux", false) + statsFormatWrite(context.context, []StatsEntry{}, "linux", false) assert.Check(t, is.Equal(context.expected, out.String())) // Clean buffer out.Reset() @@ -232,25 +233,25 @@ func TestContainerStatsContextWriteWithNoStatsWindows(t *testing.T) { var out bytes.Buffer contexts := []struct { - context Context + context formatter.Context expected string }{ { - Context{ + formatter.Context{ Format: "{{.Container}}", Output: &out, }, "", }, { - Context{ + formatter.Context{ Format: "table {{.Container}}\t{{.MemUsage}}", Output: &out, }, "CONTAINER PRIV WORKING SET\n", }, { - Context{ + formatter.Context{ Format: "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}", Output: &out, }, @@ -259,7 +260,7 @@ func TestContainerStatsContextWriteWithNoStatsWindows(t *testing.T) { } for _, context := range contexts { - ContainerStatsWrite(context.context, []StatsEntry{}, "windows", false) + statsFormatWrite(context.context, []StatsEntry{}, "windows", false) assert.Check(t, is.Equal(context.expected, out.String())) // Clean buffer out.Reset() @@ -270,12 +271,12 @@ func TestContainerStatsContextWriteTrunc(t *testing.T) { var out bytes.Buffer contexts := []struct { - context Context + context formatter.Context trunc bool expected string }{ { - Context{ + formatter.Context{ Format: "{{.ID}}", Output: &out, }, @@ -283,7 +284,7 @@ func TestContainerStatsContextWriteTrunc(t *testing.T) { "b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc\n", }, { - Context{ + formatter.Context{ Format: "{{.ID}}", Output: &out, }, @@ -293,7 +294,7 @@ func TestContainerStatsContextWriteTrunc(t *testing.T) { } for _, context := range contexts { - ContainerStatsWrite(context.context, []StatsEntry{{ID: "b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc"}}, "linux", context.trunc) + statsFormatWrite(context.context, []StatsEntry{{ID: "b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc"}}, "linux", context.trunc) assert.Check(t, is.Equal(context.expected, out.String())) // Clean buffer out.Reset() diff --git a/cli/command/container/stats.go b/cli/command/container/stats.go index 4efcb19e65..e8309e3324 100644 --- a/cli/command/container/stats.go +++ b/cli/command/container/stats.go @@ -108,7 +108,7 @@ func runStats(dockerCli command.Cli, opts *statsOptions) error { closeChan <- err } for _, container := range cs { - s := formatter.NewContainerStats(container.ID[:12]) + s := NewStats(container.ID[:12]) if cStats.add(s) { waitFirst.Add(1) go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst) @@ -125,7 +125,7 @@ func runStats(dockerCli command.Cli, opts *statsOptions) error { eh := command.InitEventHandler() eh.Handle("create", func(e events.Message) { if opts.all { - s := formatter.NewContainerStats(e.ID[:12]) + s := NewStats(e.ID[:12]) if cStats.add(s) { waitFirst.Add(1) go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst) @@ -134,7 +134,7 @@ func runStats(dockerCli command.Cli, opts *statsOptions) error { }) eh.Handle("start", func(e events.Message) { - s := formatter.NewContainerStats(e.ID[:12]) + s := NewStats(e.ID[:12]) if cStats.add(s) { waitFirst.Add(1) go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst) @@ -160,7 +160,7 @@ func runStats(dockerCli command.Cli, opts *statsOptions) error { // Artificially send creation events for the containers we were asked to // monitor (same code path than we use when monitoring all containers). for _, name := range opts.containers { - s := formatter.NewContainerStats(name) + s := NewStats(name) if cStats.add(s) { waitFirst.Add(1) go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst) @@ -198,7 +198,7 @@ func runStats(dockerCli command.Cli, opts *statsOptions) error { } statsCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewStatsFormat(format, daemonOSType), + Format: NewStatsFormat(format, daemonOSType), } cleanScreen := func() { if !opts.noStream { @@ -210,13 +210,13 @@ func runStats(dockerCli command.Cli, opts *statsOptions) error { var err error for range time.Tick(500 * time.Millisecond) { cleanScreen() - ccstats := []formatter.StatsEntry{} + ccstats := []StatsEntry{} cStats.mu.Lock() for _, c := range cStats.cs { ccstats = append(ccstats, c.GetStatistics()) } cStats.mu.Unlock() - if err = formatter.ContainerStatsWrite(statsCtx, ccstats, daemonOSType, !opts.noTrunc); err != nil { + if err = statsFormatWrite(statsCtx, ccstats, daemonOSType, !opts.noTrunc); err != nil { break } if len(cStats.cs) == 0 && !showAll { diff --git a/cli/command/container/stats_helpers.go b/cli/command/container/stats_helpers.go index 2300ce5c43..d85891a5f3 100644 --- a/cli/command/container/stats_helpers.go +++ b/cli/command/container/stats_helpers.go @@ -8,7 +8,6 @@ import ( "sync" "time" - "github.com/docker/cli/cli/command/formatter" "github.com/docker/docker/api/types" "github.com/docker/docker/client" "github.com/pkg/errors" @@ -17,7 +16,7 @@ import ( type stats struct { mu sync.Mutex - cs []*formatter.ContainerStats + cs []*Stats } // daemonOSType is set once we have at least one stat for a container @@ -25,7 +24,7 @@ type stats struct { // on the daemon platform. var daemonOSType string -func (s *stats) add(cs *formatter.ContainerStats) bool { +func (s *stats) add(cs *Stats) bool { s.mu.Lock() defer s.mu.Unlock() if _, exists := s.isKnownContainer(cs.Container); !exists { @@ -52,7 +51,7 @@ func (s *stats) isKnownContainer(cid string) (int, bool) { return -1, false } -func collect(ctx context.Context, s *formatter.ContainerStats, cli client.APIClient, streamStats bool, waitFirst *sync.WaitGroup) { +func collect(ctx context.Context, s *Stats, cli client.APIClient, streamStats bool, waitFirst *sync.WaitGroup) { logrus.Debugf("collecting stats for %s", s.Container) var ( getFirst bool @@ -115,7 +114,7 @@ func collect(ctx context.Context, s *formatter.ContainerStats, cli client.APICli mem = float64(v.MemoryStats.PrivateWorkingSet) } netRx, netTx := calculateNetwork(v.Networks) - s.SetStatistics(formatter.StatsEntry{ + s.SetStatistics(StatsEntry{ Name: v.Name, ID: v.ID, CPUPercentage: cpuPercent, diff --git a/cli/command/engine/activate.go b/cli/command/engine/activate.go index 34751609a6..fe20671c62 100644 --- a/cli/command/engine/activate.go +++ b/cli/command/engine/activate.go @@ -156,10 +156,10 @@ func getLicenses(ctx context.Context, authConfig *types.AuthConfig, cli command. updatesCtx := formatter.Context{ Output: cli.Out(), - Format: formatter.NewSubscriptionsFormat(format, options.quiet), + Format: NewSubscriptionsFormat(format, options.quiet), Trunc: false, } - if err := formatter.SubscriptionsWrite(updatesCtx, subs); err != nil { + if err := SubscriptionsWrite(updatesCtx, subs); err != nil { return nil, err } if options.displayOnly { diff --git a/cli/command/engine/check.go b/cli/command/engine/check.go index 2a41392117..07e4734a77 100644 --- a/cli/command/engine/check.go +++ b/cli/command/engine/check.go @@ -99,10 +99,10 @@ func runCheck(dockerCli command.Cli, options checkOptions) error { updatesCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewUpdatesFormat(format, options.quiet), + Format: NewUpdatesFormat(format, options.quiet), Trunc: false, } - return formatter.UpdatesWrite(updatesCtx, availUpdates) + return UpdatesWrite(updatesCtx, availUpdates) } func processVersions(currentVersion, verType string, diff --git a/cli/command/formatter/licenses.go b/cli/command/engine/licenses.go similarity index 87% rename from cli/command/formatter/licenses.go rename to cli/command/engine/licenses.go index 317ee42fcb..37090ac28e 100644 --- a/cli/command/formatter/licenses.go +++ b/cli/command/engine/licenses.go @@ -1,8 +1,9 @@ -package formatter +package engine import ( "time" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/internal/licenseutils" "github.com/docker/licensing/model" ) @@ -27,25 +28,25 @@ const ( ) // NewSubscriptionsFormat returns a Format for rendering using a license Context -func NewSubscriptionsFormat(source string, quiet bool) Format { +func NewSubscriptionsFormat(source string, quiet bool) formatter.Format { switch source { - case TableFormatKey: + case formatter.TableFormatKey: if quiet { return defaultSubscriptionsQuietFormat } return defaultSubscriptionsTableFormat - case RawFormatKey: + case formatter.RawFormatKey: if quiet { return `license: {{.ID}}` } return `license: {{.ID}}\nname: {{.Name}}\nowner: {{.Owner}}\ncomponents: {{.ComponentsString}}\n` } - return Format(source) + return formatter.Format(source) } // SubscriptionsWrite writes the context -func SubscriptionsWrite(ctx Context, subs []licenseutils.LicenseDisplay) error { - render := func(format func(subContext subContext) error) error { +func SubscriptionsWrite(ctx formatter.Context, subs []licenseutils.LicenseDisplay) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, sub := range subs { licenseCtx := &licenseContext{trunc: ctx.Trunc, l: sub} if err := format(licenseCtx); err != nil { @@ -55,7 +56,7 @@ func SubscriptionsWrite(ctx Context, subs []licenseutils.LicenseDisplay) error { return nil } licenseCtx := licenseContext{} - licenseCtx.header = map[string]string{ + licenseCtx.Header = map[string]string{ "Num": numHeader, "Owner": ownerHeader, "Name": licenseNameHeader, @@ -74,13 +75,13 @@ func SubscriptionsWrite(ctx Context, subs []licenseutils.LicenseDisplay) error { } type licenseContext struct { - HeaderContext + formatter.HeaderContext trunc bool l licenseutils.LicenseDisplay } func (c *licenseContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return formatter.MarshalJSON(c) } func (c *licenseContext) Num() int { diff --git a/cli/command/formatter/licenses_test.go b/cli/command/engine/licenses_test.go similarity index 86% rename from cli/command/formatter/licenses_test.go rename to cli/command/engine/licenses_test.go index a96a63e5e1..bacde70cc7 100644 --- a/cli/command/formatter/licenses_test.go +++ b/cli/command/engine/licenses_test.go @@ -1,4 +1,4 @@ -package formatter +package engine import ( "bytes" @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/internal/licenseutils" "github.com/docker/licensing/model" "gotest.tools/assert" @@ -15,43 +16,43 @@ import ( func TestSubscriptionContextWrite(t *testing.T) { cases := []struct { - context Context + context formatter.Context expected string }{ // Errors { - Context{Format: "{{InvalidFunction}}"}, + formatter.Context{Format: "{{InvalidFunction}}"}, `Template parsing error: template: :1: function "InvalidFunction" not defined `, }, { - Context{Format: "{{nil}}"}, + formatter.Context{Format: "{{nil}}"}, `Template parsing error: template: :1:2: executing "" at : nil is not a command `, }, // Table format { - Context{Format: NewSubscriptionsFormat("table", false)}, + formatter.Context{Format: NewSubscriptionsFormat("table", false)}, `NUM OWNER PRODUCT ID EXPIRES PRICING COMPONENTS 1 owner1 productid1 2020-01-01 10:00:00 +0000 UTC compstring 2 owner2 productid2 2020-01-01 10:00:00 +0000 UTC compstring `, }, { - Context{Format: NewSubscriptionsFormat("table", true)}, + formatter.Context{Format: NewSubscriptionsFormat("table", true)}, `1:License Name: name1 Quantity: 10 nodes Expiration date: 2020-01-01 2:License Name: name2 Quantity: 20 nodes Expiration date: 2020-01-01 `, }, { - Context{Format: NewSubscriptionsFormat("table {{.Owner}}", false)}, + formatter.Context{Format: NewSubscriptionsFormat("table {{.Owner}}", false)}, `OWNER owner1 owner2 `, }, { - Context{Format: NewSubscriptionsFormat("table {{.Owner}}", true)}, + formatter.Context{Format: NewSubscriptionsFormat("table {{.Owner}}", true)}, `OWNER owner1 owner2 @@ -59,7 +60,7 @@ owner2 }, // Raw Format { - Context{Format: NewSubscriptionsFormat("raw", false)}, + formatter.Context{Format: NewSubscriptionsFormat("raw", false)}, `license: id1 name: name1 owner: owner1 @@ -73,14 +74,14 @@ components: compstring `, }, { - Context{Format: NewSubscriptionsFormat("raw", true)}, + formatter.Context{Format: NewSubscriptionsFormat("raw", true)}, `license: id1 license: id2 `, }, // Custom Format { - Context{Format: NewSubscriptionsFormat("{{.Owner}}", false)}, + formatter.Context{Format: NewSubscriptionsFormat("{{.Owner}}", false)}, `owner1 owner2 `, @@ -223,7 +224,7 @@ func TestSubscriptionContextWriteJSON(t *testing.T) { } out := &bytes.Buffer{} - err := SubscriptionsWrite(Context{Format: "{{json .}}", Output: out}, subscriptions) + err := SubscriptionsWrite(formatter.Context{Format: "{{json .}}", Output: out}, subscriptions) if err != nil { t.Fatal(err) } @@ -242,7 +243,7 @@ func TestSubscriptionContextWriteJSONField(t *testing.T) { {Num: 2, Owner: "owner2"}, } out := &bytes.Buffer{} - err := SubscriptionsWrite(Context{Format: "{{json .Owner}}", Output: out}, subscriptions) + err := SubscriptionsWrite(formatter.Context{Format: "{{json .Owner}}", Output: out}, subscriptions) if err != nil { t.Fatal(err) } diff --git a/cli/command/formatter/updates.go b/cli/command/engine/updates.go similarity index 72% rename from cli/command/formatter/updates.go rename to cli/command/engine/updates.go index e67e035b35..2ca4a55292 100644 --- a/cli/command/formatter/updates.go +++ b/cli/command/engine/updates.go @@ -1,6 +1,7 @@ -package formatter +package engine import ( + "github.com/docker/cli/cli/command/formatter" clitypes "github.com/docker/cli/types" ) @@ -14,25 +15,25 @@ const ( ) // NewUpdatesFormat returns a Format for rendering using a updates context -func NewUpdatesFormat(source string, quiet bool) Format { +func NewUpdatesFormat(source string, quiet bool) formatter.Format { switch source { - case TableFormatKey: + case formatter.TableFormatKey: if quiet { return defaultUpdatesQuietFormat } return defaultUpdatesTableFormat - case RawFormatKey: + case formatter.RawFormatKey: if quiet { return `update_version: {{.Version}}` } return `update_version: {{.Version}}\ntype: {{.Type}}\nnotes: {{.Notes}}\n` } - return Format(source) + return formatter.Format(source) } // UpdatesWrite writes the context -func UpdatesWrite(ctx Context, availableUpdates []clitypes.Update) error { - render := func(format func(subContext subContext) error) error { +func UpdatesWrite(ctx formatter.Context, availableUpdates []clitypes.Update) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, update := range availableUpdates { updatesCtx := &updateContext{trunc: ctx.Trunc, u: update} if err := format(updatesCtx); err != nil { @@ -42,7 +43,7 @@ func UpdatesWrite(ctx Context, availableUpdates []clitypes.Update) error { return nil } updatesCtx := updateContext{} - updatesCtx.header = map[string]string{ + updatesCtx.Header = map[string]string{ "Type": updatesTypeHeader, "Version": versionHeader, "Notes": notesHeader, @@ -51,13 +52,13 @@ func UpdatesWrite(ctx Context, availableUpdates []clitypes.Update) error { } type updateContext struct { - HeaderContext + formatter.HeaderContext trunc bool u clitypes.Update } func (c *updateContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return formatter.MarshalJSON(c) } func (c *updateContext) Type() string { diff --git a/cli/command/formatter/updates_test.go b/cli/command/engine/updates_test.go similarity index 76% rename from cli/command/formatter/updates_test.go rename to cli/command/engine/updates_test.go index a7343f3344..7fb2b0f152 100644 --- a/cli/command/formatter/updates_test.go +++ b/cli/command/engine/updates_test.go @@ -1,4 +1,4 @@ -package formatter +package engine import ( "bytes" @@ -6,6 +6,7 @@ import ( "strings" "testing" + "github.com/docker/cli/cli/command/formatter" clitypes "github.com/docker/cli/types" "gotest.tools/assert" is "gotest.tools/assert/cmp" @@ -13,43 +14,43 @@ import ( func TestUpdateContextWrite(t *testing.T) { cases := []struct { - context Context + context formatter.Context expected string }{ // Errors { - Context{Format: "{{InvalidFunction}}"}, + formatter.Context{Format: "{{InvalidFunction}}"}, `Template parsing error: template: :1: function "InvalidFunction" not defined `, }, { - Context{Format: "{{nil}}"}, + formatter.Context{Format: "{{nil}}"}, `Template parsing error: template: :1:2: executing "" at : nil is not a command `, }, // Table format { - Context{Format: NewUpdatesFormat("table", false)}, + formatter.Context{Format: NewUpdatesFormat("table", false)}, `TYPE VERSION NOTES updateType1 version1 description 1 updateType2 version2 description 2 `, }, { - Context{Format: NewUpdatesFormat("table", true)}, + formatter.Context{Format: NewUpdatesFormat("table", true)}, `version1 version2 `, }, { - Context{Format: NewUpdatesFormat("table {{.Version}}", false)}, + formatter.Context{Format: NewUpdatesFormat("table {{.Version}}", false)}, `VERSION version1 version2 `, }, { - Context{Format: NewUpdatesFormat("table {{.Version}}", true)}, + formatter.Context{Format: NewUpdatesFormat("table {{.Version}}", true)}, `VERSION version1 version2 @@ -57,7 +58,7 @@ version2 }, // Raw Format { - Context{Format: NewUpdatesFormat("raw", false)}, + formatter.Context{Format: NewUpdatesFormat("raw", false)}, `update_version: version1 type: updateType1 notes: description 1 @@ -69,14 +70,14 @@ notes: description 2 `, }, { - Context{Format: NewUpdatesFormat("raw", true)}, + formatter.Context{Format: NewUpdatesFormat("raw", true)}, `update_version: version1 update_version: version2 `, }, // Custom Format { - Context{Format: NewUpdatesFormat("{{.Version}}", false)}, + formatter.Context{Format: NewUpdatesFormat("{{.Version}}", false)}, `version1 version2 `, @@ -110,7 +111,7 @@ func TestUpdateContextWriteJSON(t *testing.T) { } out := &bytes.Buffer{} - err := UpdatesWrite(Context{Format: "{{json .}}", Output: out}, updates) + err := UpdatesWrite(formatter.Context{Format: "{{json .}}", Output: out}, updates) if err != nil { t.Fatal(err) } @@ -129,7 +130,7 @@ func TestUpdateContextWriteJSONField(t *testing.T) { {Type: "updateType2", Version: "version2"}, } out := &bytes.Buffer{} - err := UpdatesWrite(Context{Format: "{{json .Type}}", Output: out}, updates) + err := UpdatesWrite(formatter.Context{Format: "{{json .Type}}", Output: out}, updates) if err != nil { t.Fatal(err) } diff --git a/cli/command/formatter/buildcache.go b/cli/command/formatter/buildcache.go index 813e41c5c7..c8c3848146 100644 --- a/cli/command/formatter/buildcache.go +++ b/cli/command/formatter/buildcache.go @@ -28,7 +28,7 @@ func NewBuildCacheFormat(source string, quiet bool) Format { switch source { case TableFormatKey: if quiet { - return defaultQuietFormat + return DefaultQuietFormat } return Format(defaultBuildCacheTableFormat) case RawFormatKey: @@ -72,7 +72,7 @@ func buildCacheSort(buildCache []*types.BuildCache) { // BuildCacheWrite renders the context for a list of containers func BuildCacheWrite(ctx Context, buildCaches []*types.BuildCache) error { - render := func(format func(subContext subContext) error) error { + render := func(format func(subContext SubContext) error) error { buildCacheSort(buildCaches) for _, bc := range buildCaches { err := format(&buildCacheContext{trunc: ctx.Trunc, v: bc}) @@ -85,8 +85,6 @@ func BuildCacheWrite(ctx Context, buildCaches []*types.BuildCache) error { return ctx.Write(newBuildCacheContext(), render) } -type buildCacheHeaderContext map[string]string - type buildCacheContext struct { HeaderContext trunc bool @@ -95,23 +93,23 @@ type buildCacheContext struct { func newBuildCacheContext() *buildCacheContext { buildCacheCtx := buildCacheContext{} - buildCacheCtx.header = buildCacheHeaderContext{ + buildCacheCtx.Header = SubHeaderContext{ "ID": cacheIDHeader, "Parent": parentHeader, "CacheType": cacheTypeHeader, - "Size": sizeHeader, - "CreatedSince": createdSinceHeader, + "Size": SizeHeader, + "CreatedSince": CreatedSinceHeader, "LastUsedSince": lastUsedSinceHeader, "UsageCount": usageCountHeader, "InUse": inUseHeader, "Shared": sharedHeader, - "Description": descriptionHeader, + "Description": DescriptionHeader, } return &buildCacheCtx } func (c *buildCacheContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return MarshalJSON(c) } func (c *buildCacheContext) ID() string { diff --git a/cli/command/formatter/container.go b/cli/command/formatter/container.go index cf42555b79..e7e98cb998 100644 --- a/cli/command/formatter/container.go +++ b/cli/command/formatter/container.go @@ -16,15 +16,12 @@ import ( const ( defaultContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}" - containerIDHeader = "CONTAINER ID" - namesHeader = "NAMES" - commandHeader = "COMMAND" - runningForHeader = "CREATED" - statusHeader = "STATUS" - portsHeader = "PORTS" - mountsHeader = "MOUNTS" - localVolumes = "LOCAL VOLUMES" - networksHeader = "NETWORKS" + namesHeader = "NAMES" + commandHeader = "COMMAND" + runningForHeader = "CREATED" + mountsHeader = "MOUNTS" + localVolumes = "LOCAL VOLUMES" + networksHeader = "NETWORKS" ) // NewContainerFormat returns a Format for rendering using a Context @@ -32,7 +29,7 @@ func NewContainerFormat(source string, quiet bool, size bool) Format { switch source { case TableFormatKey: if quiet { - return defaultQuietFormat + return DefaultQuietFormat } format := defaultContainerTableFormat if size { @@ -62,7 +59,7 @@ ports: {{- pad .Ports 1 0}} // ContainerWrite renders the context for a list of containers func ContainerWrite(ctx Context, containers []types.Container) error { - render := func(format func(subContext subContext) error) error { + render := func(format func(subContext SubContext) error) error { for _, container := range containers { err := format(&containerContext{trunc: ctx.Trunc, c: container}) if err != nil { @@ -74,16 +71,6 @@ func ContainerWrite(ctx Context, containers []types.Container) error { 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 { HeaderContext trunc bool @@ -92,17 +79,17 @@ type containerContext struct { func newContainerContext() *containerContext { containerCtx := containerContext{} - containerCtx.header = containerHeaderContext{ - "ID": containerIDHeader, + containerCtx.Header = SubHeaderContext{ + "ID": ContainerIDHeader, "Names": namesHeader, - "Image": imageHeader, + "Image": ImageHeader, "Command": commandHeader, - "CreatedAt": createdAtHeader, + "CreatedAt": CreatedAtHeader, "RunningFor": runningForHeader, - "Ports": portsHeader, - "Status": statusHeader, - "Size": sizeHeader, - "Labels": labelsHeader, + "Ports": PortsHeader, + "Status": StatusHeader, + "Size": SizeHeader, + "Labels": LabelsHeader, "Mounts": mountsHeader, "LocalVolumes": localVolumes, "Networks": networksHeader, @@ -111,7 +98,7 @@ func newContainerContext() *containerContext { } func (c *containerContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return MarshalJSON(c) } func (c *containerContext) ID() string { diff --git a/cli/command/formatter/custom.go b/cli/command/formatter/custom.go index 73487f63ef..6be9ed7ab5 100644 --- a/cli/command/formatter/custom.go +++ b/cli/command/formatter/custom.go @@ -1,28 +1,48 @@ package formatter +import "strings" + +// Common header constants const ( - imageHeader = "IMAGE" - createdSinceHeader = "CREATED" - createdAtHeader = "CREATED AT" - sizeHeader = "SIZE" - labelsHeader = "LABELS" - nameHeader = "NAME" - driverHeader = "DRIVER" - scopeHeader = "SCOPE" + CreatedSinceHeader = "CREATED" + CreatedAtHeader = "CREATED AT" + SizeHeader = "SIZE" + LabelsHeader = "LABELS" + NameHeader = "NAME" + DescriptionHeader = "DESCRIPTION" + DriverHeader = "DRIVER" + ScopeHeader = "SCOPE" + StatusHeader = "STATUS" + PortsHeader = "PORTS" + ImageHeader = "IMAGE" + ContainerIDHeader = "CONTAINER ID" ) -type subContext interface { +// SubContext defines what Context implementation should provide +type SubContext interface { FullHeader() interface{} } +// SubHeaderContext is a map destined to formatter header (table format) +type SubHeaderContext map[string]string + +// Label returns the header label for the specified string +func (c SubHeaderContext) Label(name string) string { + n := strings.Split(name, ".") + r := strings.NewReplacer("-", " ", "_", " ") + h := r.Replace(n[len(n)-1]) + + return h +} + // HeaderContext provides the subContext interface for managing headers type HeaderContext struct { - header interface{} + Header interface{} } // FullHeader returns the header as an interface func (c *HeaderContext) FullHeader() interface{} { - return c.header + return c.Header } func stripNamePrefix(ss []string) []string { diff --git a/cli/command/formatter/custom_test.go b/cli/command/formatter/custom_test.go index 6a4bfec784..921bfd711a 100644 --- a/cli/command/formatter/custom_test.go +++ b/cli/command/formatter/custom_test.go @@ -1,28 +1,12 @@ package formatter import ( - "strings" "testing" - "gotest.tools/assert" - is "gotest.tools/assert/cmp" + "github.com/docker/cli/internal/test" ) +// Deprecated: use internal/test.CompareMultipleValues instead func compareMultipleValues(t *testing.T, value, expected string) { - // comma-separated values means probably a map input, which won't - // be guaranteed to have the same order as our expected value - // We'll create maps and use reflect.DeepEquals to check instead: - entriesMap := make(map[string]string) - expMap := make(map[string]string) - entries := strings.Split(value, ",") - expectedEntries := strings.Split(expected, ",") - for _, entry := range entries { - keyval := strings.Split(entry, "=") - entriesMap[keyval[0]] = keyval[1] - } - for _, expected := range expectedEntries { - keyval := strings.Split(expected, "=") - expMap[keyval[0]] = keyval[1] - } - assert.Check(t, is.DeepEqual(expMap, entriesMap)) + test.CompareMultipleValues(t, value, expected) } diff --git a/cli/command/formatter/disk_usage.go b/cli/command/formatter/disk_usage.go index 3b235497de..7ddb6e4837 100644 --- a/cli/command/formatter/disk_usage.go +++ b/cli/command/formatter/disk_usage.go @@ -122,11 +122,11 @@ func (ctx *DiskUsageContext) Write() (err error) { } diskUsageContainersCtx := diskUsageContainersContext{containers: []*types.Container{}} - diskUsageContainersCtx.header = map[string]string{ + diskUsageContainersCtx.Header = SubHeaderContext{ "Type": typeHeader, "TotalCount": totalHeader, "Active": activeHeader, - "Size": sizeHeader, + "Size": SizeHeader, "Reclaimable": reclaimableHeader, } ctx.postFormat(tmpl, &diskUsageContainersCtx) @@ -263,7 +263,7 @@ type diskUsageImagesContext struct { } func (c *diskUsageImagesContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return MarshalJSON(c) } func (c *diskUsageImagesContext) Type() string { @@ -315,7 +315,7 @@ type diskUsageContainersContext struct { } func (c *diskUsageContainersContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return MarshalJSON(c) } func (c *diskUsageContainersContext) Type() string { @@ -377,7 +377,7 @@ type diskUsageVolumesContext struct { } func (c *diskUsageVolumesContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return MarshalJSON(c) } func (c *diskUsageVolumesContext) Type() string { @@ -439,7 +439,7 @@ type diskUsageBuilderContext struct { } func (c *diskUsageBuilderContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return MarshalJSON(c) } func (c *diskUsageBuilderContext) Type() string { diff --git a/cli/command/formatter/formatter.go b/cli/command/formatter/formatter.go index c63e1a4908..ae828bfe47 100644 --- a/cli/command/formatter/formatter.go +++ b/cli/command/formatter/formatter.go @@ -17,7 +17,7 @@ const ( RawFormatKey = "raw" PrettyFormatKey = "pretty" - defaultQuietFormat = "{{.ID}}" + DefaultQuietFormat = "{{.ID}}" ) // Format is the format string rendered using the Context @@ -69,7 +69,7 @@ func (c *Context) parseFormat() (*template.Template, error) { return tmpl, err } -func (c *Context) postFormat(tmpl *template.Template, subContext subContext) { +func (c *Context) postFormat(tmpl *template.Template, subContext SubContext) { if c.Format.IsTable() { t := tabwriter.NewWriter(c.Output, 20, 1, 3, ' ', 0) buffer := bytes.NewBufferString("") @@ -83,7 +83,7 @@ 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 { return errors.Errorf("Template parsing error: %v\n", err) } @@ -95,10 +95,10 @@ func (c *Context) contextFormat(tmpl *template.Template, subContext subContext) } // SubFormat is a function type accepted by Write() -type SubFormat func(func(subContext) error) error +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 { +func (c *Context) Write(sub SubContext, f SubFormat) error { c.buffer = bytes.NewBufferString("") c.preFormat() @@ -107,7 +107,7 @@ func (c *Context) Write(sub subContext, f SubFormat) error { return err } - subFormat := func(subContext subContext) error { + subFormat := func(subContext SubContext) error { return c.contextFormat(tmpl, subContext) } if err := f(subFormat); err != nil { diff --git a/cli/command/formatter/image.go b/cli/command/formatter/image.go index e94785ef08..91893e8f97 100644 --- a/cli/command/formatter/image.go +++ b/cli/command/formatter/image.go @@ -36,7 +36,7 @@ func NewImageFormat(source string, quiet bool, digest bool) Format { case TableFormatKey: switch { case quiet: - return defaultQuietFormat + return DefaultQuietFormat case digest: return defaultImageTableFormatWithDigest default: @@ -73,7 +73,7 @@ virtual_size: {{.Size}} // ImageWrite writes the formatter images using the ImageContext func ImageWrite(ctx ImageContext, images []types.ImageSummary) error { - render := func(format func(subContext subContext) error) error { + render := func(format func(subContext SubContext) error) error { return imageFormat(ctx, images, format) } return ctx.Write(newImageContext(), render) @@ -84,7 +84,7 @@ func needDigest(ctx ImageContext) bool { return ctx.Digest || ctx.Format.Contains("{{.Digest}}") } -func imageFormat(ctx ImageContext, images []types.ImageSummary, format func(subContext subContext) error) error { +func imageFormat(ctx ImageContext, images []types.ImageSummary, format func(subContext SubContext) error) error { for _, image := range images { formatted := []*imageContext{} if isDangling(image) { @@ -194,16 +194,16 @@ type imageContext struct { func newImageContext() *imageContext { imageCtx := imageContext{} - imageCtx.header = map[string]string{ + imageCtx.Header = SubHeaderContext{ "ID": imageIDHeader, "Repository": repositoryHeader, "Tag": tagHeader, "Digest": digestHeader, - "CreatedSince": createdSinceHeader, - "CreatedAt": createdAtHeader, - "Size": sizeHeader, + "CreatedSince": CreatedSinceHeader, + "CreatedAt": CreatedAtHeader, + "Size": SizeHeader, "Containers": containersHeader, - "VirtualSize": sizeHeader, + "VirtualSize": SizeHeader, "SharedSize": sharedSizeHeader, "UniqueSize": uniqueSizeHeader, } @@ -211,7 +211,7 @@ func newImageContext() *imageContext { } func (c *imageContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return MarshalJSON(c) } func (c *imageContext) ID() string { diff --git a/cli/command/formatter/reflect.go b/cli/command/formatter/reflect.go index fd59404d05..9d153bc988 100644 --- a/cli/command/formatter/reflect.go +++ b/cli/command/formatter/reflect.go @@ -8,7 +8,9 @@ import ( "github.com/pkg/errors" ) -func marshalJSON(x interface{}) ([]byte, error) { +// MarshalJSON marshals x into json +// It differs a bit from encoding/json MarshalJSON function for formatter +func MarshalJSON(x interface{}) ([]byte, error) { m, err := marshalMap(x) if err != nil { return nil, err diff --git a/cli/command/formatter/volume.go b/cli/command/formatter/volume.go index 342f2fb934..67144591da 100644 --- a/cli/command/formatter/volume.go +++ b/cli/command/formatter/volume.go @@ -37,7 +37,7 @@ func NewVolumeFormat(source string, quiet bool) Format { // VolumeWrite writes formatted volumes using the Context func VolumeWrite(ctx Context, volumes []*types.Volume) error { - render := func(format func(subContext subContext) error) error { + render := func(format func(subContext SubContext) error) error { for _, volume := range volumes { if err := format(&volumeContext{v: *volume}); err != nil { return err @@ -48,16 +48,6 @@ func VolumeWrite(ctx Context, volumes []*types.Volume) error { 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 { HeaderContext v types.Volume @@ -65,20 +55,20 @@ type volumeContext struct { func newVolumeContext() *volumeContext { volumeCtx := volumeContext{} - volumeCtx.header = volumeHeaderContext{ + volumeCtx.Header = SubHeaderContext{ "Name": volumeNameHeader, - "Driver": driverHeader, - "Scope": scopeHeader, + "Driver": DriverHeader, + "Scope": ScopeHeader, "Mountpoint": mountpointHeader, - "Labels": labelsHeader, + "Labels": LabelsHeader, "Links": linksHeader, - "Size": sizeHeader, + "Size": SizeHeader, } return &volumeCtx } func (c *volumeContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return MarshalJSON(c) } func (c *volumeContext) Name() string { diff --git a/cli/command/formatter/history.go b/cli/command/image/formatter_history.go similarity index 74% rename from cli/command/formatter/history.go rename to cli/command/image/formatter_history.go index 6ad0a605c9..723066a580 100644 --- a/cli/command/formatter/history.go +++ b/cli/command/image/formatter_history.go @@ -1,10 +1,11 @@ -package formatter +package image import ( "strconv" "strings" "time" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/docker/api/types/image" "github.com/docker/docker/pkg/stringid" units "github.com/docker/go-units" @@ -20,12 +21,12 @@ const ( ) // NewHistoryFormat returns a format for rendering an HistoryContext -func NewHistoryFormat(source string, quiet bool, human bool) Format { +func NewHistoryFormat(source string, quiet bool, human bool) formatter.Format { switch source { - case TableFormatKey: + case formatter.TableFormatKey: switch { case quiet: - return defaultQuietFormat + return formatter.DefaultQuietFormat case !human: return nonHumanHistoryTableFormat default: @@ -33,12 +34,12 @@ func NewHistoryFormat(source string, quiet bool, human bool) Format { } } - return Format(source) + return formatter.Format(source) } // HistoryWrite writes the context -func HistoryWrite(ctx Context, human bool, histories []image.HistoryResponseItem) error { - render := func(format func(subContext subContext) error) error { +func HistoryWrite(ctx formatter.Context, human bool, histories []image.HistoryResponseItem) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, history := range histories { historyCtx := &historyContext{trunc: ctx.Trunc, h: history, human: human} if err := format(historyCtx); err != nil { @@ -48,26 +49,26 @@ func HistoryWrite(ctx Context, human bool, histories []image.HistoryResponseItem return nil } historyCtx := &historyContext{} - historyCtx.header = map[string]string{ + historyCtx.Header = formatter.SubHeaderContext{ "ID": historyIDHeader, - "CreatedSince": createdSinceHeader, - "CreatedAt": createdAtHeader, + "CreatedSince": formatter.CreatedSinceHeader, + "CreatedAt": formatter.CreatedAtHeader, "CreatedBy": createdByHeader, - "Size": sizeHeader, + "Size": formatter.SizeHeader, "Comment": commentHeader, } return ctx.Write(historyCtx, render) } type historyContext struct { - HeaderContext + formatter.HeaderContext trunc bool human bool h image.HistoryResponseItem } func (c *historyContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return formatter.MarshalJSON(c) } func (c *historyContext) ID() string { @@ -92,7 +93,7 @@ func (c *historyContext) CreatedSince() string { func (c *historyContext) CreatedBy() string { createdBy := strings.Replace(c.h.CreatedBy, "\t", " ", -1) if c.trunc { - return Ellipsis(createdBy, 45) + return formatter.Ellipsis(createdBy, 45) } return createdBy } diff --git a/cli/command/formatter/history_test.go b/cli/command/image/formatter_history_test.go similarity index 93% rename from cli/command/formatter/history_test.go rename to cli/command/image/formatter_history_test.go index 7dfc146be3..fe8e88a82b 100644 --- a/cli/command/formatter/history_test.go +++ b/cli/command/image/formatter_history_test.go @@ -1,4 +1,4 @@ -package formatter +package image import ( "bytes" @@ -7,6 +7,8 @@ import ( "testing" "time" + "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types/image" "github.com/docker/docker/pkg/stringid" "gotest.tools/assert" @@ -42,7 +44,7 @@ func TestHistoryContext_ID(t *testing.T) { ctx = c.historyCtx v := c.call() if strings.Contains(v, ",") { - compareMultipleValues(t, v, c.expValue) + test.CompareMultipleValues(t, v, c.expValue) } else if v != c.expValue { t.Fatalf("Expected %s, was %s\n", c.expValue, v) } @@ -73,7 +75,7 @@ func TestHistoryContext_CreatedSince(t *testing.T) { ctx = c.historyCtx v := c.call() if strings.Contains(v, ",") { - compareMultipleValues(t, v, c.expValue) + test.CompareMultipleValues(t, v, c.expValue) } else if v != c.expValue { t.Fatalf("Expected %s, was %s\n", c.expValue, v) } @@ -96,7 +98,7 @@ func TestHistoryContext_CreatedBy(t *testing.T) { historyContext{ h: image.HistoryResponseItem{CreatedBy: withTabs}, trunc: true, - }, Ellipsis(expected, 45), ctx.CreatedBy, + }, formatter.Ellipsis(expected, 45), ctx.CreatedBy, }, } @@ -104,7 +106,7 @@ func TestHistoryContext_CreatedBy(t *testing.T) { ctx = c.historyCtx v := c.call() if strings.Contains(v, ",") { - compareMultipleValues(t, v, c.expValue) + test.CompareMultipleValues(t, v, c.expValue) } else if v != c.expValue { t.Fatalf("Expected %s, was %s\n", c.expValue, v) } @@ -136,7 +138,7 @@ func TestHistoryContext_Size(t *testing.T) { ctx = c.historyCtx v := c.call() if strings.Contains(v, ",") { - compareMultipleValues(t, v, c.expValue) + test.CompareMultipleValues(t, v, c.expValue) } else if v != c.expValue { t.Fatalf("Expected %s, was %s\n", c.expValue, v) } @@ -160,7 +162,7 @@ func TestHistoryContext_Comment(t *testing.T) { ctx = c.historyCtx v := c.call() if strings.Contains(v, ",") { - compareMultipleValues(t, v, c.expValue) + test.CompareMultipleValues(t, v, c.expValue) } else if v != c.expValue { t.Fatalf("Expected %s, was %s\n", c.expValue, v) } @@ -198,17 +200,17 @@ imageID4 24 hours ago /bin/bash grep ` contexts := []struct { - context Context + context formatter.Context expected string }{ - {Context{ + {formatter.Context{ Format: NewHistoryFormat("table", false, true), Trunc: true, Output: out, }, expectedTrunc, }, - {Context{ + {formatter.Context{ Format: NewHistoryFormat("table", false, true), Trunc: false, Output: out, diff --git a/cli/command/image/history.go b/cli/command/image/history.go index 11acc93d31..f1559a37a2 100644 --- a/cli/command/image/history.go +++ b/cli/command/image/history.go @@ -57,8 +57,8 @@ func runHistory(dockerCli command.Cli, opts historyOptions) error { historyCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewHistoryFormat(format, opts.quiet, opts.human), + Format: NewHistoryFormat(format, opts.quiet, opts.human), Trunc: !opts.noTrunc, } - return formatter.HistoryWrite(historyCtx, opts.human, history) + return HistoryWrite(historyCtx, opts.human, history) } diff --git a/cli/command/formatter/network.go b/cli/command/network/formatter.go similarity index 68% rename from cli/command/formatter/network.go rename to cli/command/network/formatter.go index 4aeebd1750..ed8ac257a0 100644 --- a/cli/command/formatter/network.go +++ b/cli/command/network/formatter.go @@ -1,9 +1,10 @@ -package formatter +package network import ( "fmt" "strings" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" ) @@ -16,26 +17,26 @@ const ( internalHeader = "INTERNAL" ) -// NewNetworkFormat returns a Format for rendering using a network Context -func NewNetworkFormat(source string, quiet bool) Format { +// NewFormat returns a Format for rendering using a network Context +func NewFormat(source string, quiet bool) formatter.Format { switch source { - case TableFormatKey: + case formatter.TableFormatKey: if quiet { - return defaultQuietFormat + return formatter.DefaultQuietFormat } return defaultNetworkTableFormat - case RawFormatKey: + case formatter.RawFormatKey: if quiet { return `network_id: {{.ID}}` } return `network_id: {{.ID}}\nname: {{.Name}}\ndriver: {{.Driver}}\nscope: {{.Scope}}\n` } - return Format(source) + return formatter.Format(source) } -// NetworkWrite writes the context -func NetworkWrite(ctx Context, networks []types.NetworkResource) error { - render := func(format func(subContext subContext) error) error { +// FormatWrite writes the context +func FormatWrite(ctx formatter.Context, networks []types.NetworkResource) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, network := range networks { networkCtx := &networkContext{trunc: ctx.Trunc, n: network} if err := format(networkCtx); err != nil { @@ -45,37 +46,27 @@ func NetworkWrite(ctx Context, networks []types.NetworkResource) error { return nil } networkCtx := networkContext{} - networkCtx.header = networkHeaderContext{ + networkCtx.Header = formatter.SubHeaderContext{ "ID": networkIDHeader, - "Name": nameHeader, - "Driver": driverHeader, - "Scope": scopeHeader, + "Name": formatter.NameHeader, + "Driver": formatter.DriverHeader, + "Scope": formatter.ScopeHeader, "IPv6": ipv6Header, "Internal": internalHeader, - "Labels": labelsHeader, - "CreatedAt": createdAtHeader, + "Labels": formatter.LabelsHeader, + "CreatedAt": formatter.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 { - HeaderContext + formatter.HeaderContext trunc bool n types.NetworkResource } func (c *networkContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return formatter.MarshalJSON(c) } func (c *networkContext) ID() string { diff --git a/cli/command/formatter/network_test.go b/cli/command/network/formatter_test.go similarity index 82% rename from cli/command/formatter/network_test.go rename to cli/command/network/formatter_test.go index 6831926e46..4fd86d8e6c 100644 --- a/cli/command/formatter/network_test.go +++ b/cli/command/network/formatter_test.go @@ -1,4 +1,4 @@ -package formatter +package network import ( "bytes" @@ -8,6 +8,8 @@ import ( "testing" "time" + "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" "gotest.tools/assert" @@ -61,7 +63,7 @@ func TestNetworkContext(t *testing.T) { ctx = c.networkCtx v := c.call() if strings.Contains(v, ",") { - compareMultipleValues(t, v, c.expValue) + test.CompareMultipleValues(t, v, c.expValue) } else if v != c.expValue { t.Fatalf("Expected %s, was %s\n", c.expValue, v) } @@ -70,44 +72,44 @@ func TestNetworkContext(t *testing.T) { func TestNetworkContextWrite(t *testing.T) { cases := []struct { - context Context + context formatter.Context expected string }{ // Errors { - Context{Format: "{{InvalidFunction}}"}, + formatter.Context{Format: "{{InvalidFunction}}"}, `Template parsing error: template: :1: function "InvalidFunction" not defined `, }, { - Context{Format: "{{nil}}"}, + formatter.Context{Format: "{{nil}}"}, `Template parsing error: template: :1:2: executing "" at : nil is not a command `, }, // Table format { - Context{Format: NewNetworkFormat("table", false)}, + formatter.Context{Format: NewFormat("table", false)}, `NETWORK ID NAME DRIVER SCOPE networkID1 foobar_baz foo local networkID2 foobar_bar bar local `, }, { - Context{Format: NewNetworkFormat("table", true)}, + formatter.Context{Format: NewFormat("table", true)}, `networkID1 networkID2 `, }, { - Context{Format: NewNetworkFormat("table {{.Name}}", false)}, + formatter.Context{Format: NewFormat("table {{.Name}}", false)}, `NAME foobar_baz foobar_bar `, }, { - Context{Format: NewNetworkFormat("table {{.Name}}", true)}, + formatter.Context{Format: NewFormat("table {{.Name}}", true)}, `NAME foobar_baz foobar_bar @@ -115,7 +117,7 @@ foobar_bar }, // Raw Format { - Context{Format: NewNetworkFormat("raw", false)}, + formatter.Context{Format: NewFormat("raw", false)}, `network_id: networkID1 name: foobar_baz driver: foo @@ -129,21 +131,21 @@ scope: local `, }, { - Context{Format: NewNetworkFormat("raw", true)}, + formatter.Context{Format: NewFormat("raw", true)}, `network_id: networkID1 network_id: networkID2 `, }, // Custom Format { - Context{Format: NewNetworkFormat("{{.Name}}", false)}, + formatter.Context{Format: NewFormat("{{.Name}}", false)}, `foobar_baz foobar_bar `, }, // Custom Format with CreatedAt { - Context{Format: NewNetworkFormat("{{.Name}} {{.CreatedAt}}", false)}, + formatter.Context{Format: NewFormat("{{.Name}} {{.CreatedAt}}", false)}, `foobar_baz 2016-01-01 00:00:00 +0000 UTC foobar_bar 2017-01-01 00:00:00 +0000 UTC `, @@ -160,7 +162,7 @@ foobar_bar 2017-01-01 00:00:00 +0000 UTC } out := bytes.NewBufferString("") testcase.context.Output = out - err := NetworkWrite(testcase.context, networks) + err := FormatWrite(testcase.context, networks) if err != nil { assert.Error(t, err, testcase.expected) } else { @@ -180,7 +182,7 @@ func TestNetworkContextWriteJSON(t *testing.T) { } out := bytes.NewBufferString("") - err := NetworkWrite(Context{Format: "{{json .}}", Output: out}, networks) + err := FormatWrite(formatter.Context{Format: "{{json .}}", Output: out}, networks) if err != nil { t.Fatal(err) } @@ -199,7 +201,7 @@ func TestNetworkContextWriteJSONField(t *testing.T) { {ID: "networkID2", Name: "foobar_bar"}, } out := bytes.NewBufferString("") - err := NetworkWrite(Context{Format: "{{json .ID}}", Output: out}, networks) + err := FormatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, networks) if err != nil { t.Fatal(err) } diff --git a/cli/command/network/list.go b/cli/command/network/list.go index ad286190a4..b2700552e5 100644 --- a/cli/command/network/list.go +++ b/cli/command/network/list.go @@ -65,8 +65,8 @@ func runList(dockerCli command.Cli, options listOptions) error { networksCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewNetworkFormat(format, options.quiet), + Format: NewFormat(format, options.quiet), Trunc: !options.noTrunc, } - return formatter.NetworkWrite(networksCtx, networkResources) + return FormatWrite(networksCtx, networkResources) } diff --git a/cli/command/formatter/node.go b/cli/command/node/formatter.go similarity index 86% rename from cli/command/formatter/node.go rename to cli/command/node/formatter.go index 6cfc32380e..9cd9d015e0 100644 --- a/cli/command/formatter/node.go +++ b/cli/command/node/formatter.go @@ -1,4 +1,4 @@ -package formatter +package node import ( "encoding/base64" @@ -7,6 +7,7 @@ import ( "strings" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/inspect" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" @@ -14,8 +15,8 @@ import ( ) const ( - defaultNodeTableFormat = "table {{.ID}} {{if .Self}}*{{else}} {{ end }}\t{{.Hostname}}\t{{.Status}}\t{{.Availability}}\t{{.ManagerStatus}}\t{{.EngineVersion}}" - nodeInspectPrettyTemplate Format = `ID: {{.ID}} + defaultNodeTableFormat = "table {{.ID}} {{if .Self}}*{{else}} {{ end }}\t{{.Hostname}}\t{{.Status}}\t{{.Availability}}\t{{.ManagerStatus}}\t{{.EngineVersion}}" + nodeInspectPrettyTemplate formatter.Format = `ID: {{.ID}} {{- if .Name }} Name: {{.Name}} {{- end }} @@ -79,28 +80,28 @@ TLS Info: tlsStatusHeader = "TLS STATUS" ) -// NewNodeFormat returns a Format for rendering using a node Context -func NewNodeFormat(source string, quiet bool) Format { +// NewFormat returns a Format for rendering using a node Context +func NewFormat(source string, quiet bool) formatter.Format { switch source { - case PrettyFormatKey: + case formatter.PrettyFormatKey: return nodeInspectPrettyTemplate - case TableFormatKey: + case formatter.TableFormatKey: if quiet { - return defaultQuietFormat + return formatter.DefaultQuietFormat } return defaultNodeTableFormat - case RawFormatKey: + case formatter.RawFormatKey: if quiet { return `node_id: {{.ID}}` } return `node_id: {{.ID}}\nhostname: {{.Hostname}}\nstatus: {{.Status}}\navailability: {{.Availability}}\nmanager_status: {{.ManagerStatus}}\n` } - return Format(source) + return formatter.Format(source) } -// NodeWrite writes the context -func NodeWrite(ctx Context, nodes []swarm.Node, info types.Info) error { - render := func(format func(subContext subContext) error) error { +// FormatWrite writes the context +func FormatWrite(ctx formatter.Context, nodes []swarm.Node, info types.Info) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, node := range nodes { nodeCtx := &nodeContext{n: node, info: info} if err := format(nodeCtx); err != nil { @@ -109,31 +110,28 @@ func NodeWrite(ctx Context, nodes []swarm.Node, info types.Info) error { } return nil } - header := nodeHeaderContext{ + nodeCtx := nodeContext{} + nodeCtx.Header = formatter.SubHeaderContext{ "ID": nodeIDHeader, "Self": selfHeader, "Hostname": hostnameHeader, - "Status": statusHeader, + "Status": formatter.StatusHeader, "Availability": availabilityHeader, "ManagerStatus": managerStatusHeader, "EngineVersion": engineVersionHeader, "TLSStatus": tlsStatusHeader, } - nodeCtx := nodeContext{} - nodeCtx.header = header return ctx.Write(&nodeCtx, render) } -type nodeHeaderContext map[string]string - type nodeContext struct { - HeaderContext + formatter.HeaderContext n swarm.Node info types.Info } func (c *nodeContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return formatter.MarshalJSON(c) } func (c *nodeContext) ID() string { @@ -182,12 +180,12 @@ func (c *nodeContext) EngineVersion() string { return c.n.Description.Engine.EngineVersion } -// NodeInspectWrite renders the context for a list of nodes -func NodeInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) error { +// InspectFormatWrite renders the context for a list of nodes +func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.GetRefFunc) error { if ctx.Format != nodeInspectPrettyTemplate { return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef) } - render := func(format func(subContext subContext) error) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, ref := range refs { nodeI, _, err := getRef(ref) if err != nil { @@ -208,7 +206,7 @@ func NodeInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) err type nodeInspectContext struct { swarm.Node - subContext + formatter.SubContext } func (ctx *nodeInspectContext) ID() string { diff --git a/cli/command/formatter/node_test.go b/cli/command/node/formatter_test.go similarity index 86% rename from cli/command/formatter/node_test.go rename to cli/command/node/formatter_test.go index cf9ccbde82..65e13c33f1 100644 --- a/cli/command/formatter/node_test.go +++ b/cli/command/node/formatter_test.go @@ -1,4 +1,4 @@ -package formatter +package node import ( "bytes" @@ -7,6 +7,8 @@ import ( "strings" "testing" + "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/stringid" @@ -44,7 +46,7 @@ func TestNodeContext(t *testing.T) { ctx = c.nodeCtx v := c.call() if strings.Contains(v, ",") { - compareMultipleValues(t, v, c.expValue) + test.CompareMultipleValues(t, v, c.expValue) } else if v != c.expValue { t.Fatalf("Expected %s, was %s\n", c.expValue, v) } @@ -53,27 +55,27 @@ func TestNodeContext(t *testing.T) { func TestNodeContextWrite(t *testing.T) { cases := []struct { - context Context + context formatter.Context expected string clusterInfo swarm.ClusterInfo }{ // Errors { - context: Context{Format: "{{InvalidFunction}}"}, + context: formatter.Context{Format: "{{InvalidFunction}}"}, expected: `Template parsing error: template: :1: function "InvalidFunction" not defined `, clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, }, { - context: Context{Format: "{{nil}}"}, + context: formatter.Context{Format: "{{nil}}"}, expected: `Template parsing error: template: :1:2: executing "" at : nil is not a command `, clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, }, // Table format { - context: Context{Format: NewNodeFormat("table", false)}, + context: formatter.Context{Format: NewFormat("table", false)}, expected: `ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION nodeID1 foobar_baz Foo Drain Leader 18.03.0-ce nodeID2 foobar_bar Bar Active Reachable 1.2.3 @@ -81,7 +83,7 @@ nodeID3 foobar_boo Boo Active clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, }, { - context: Context{Format: NewNodeFormat("table", true)}, + context: formatter.Context{Format: NewFormat("table", true)}, expected: `nodeID1 nodeID2 nodeID3 @@ -89,7 +91,7 @@ nodeID3 clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, }, { - context: Context{Format: NewNodeFormat("table {{.Hostname}}", false)}, + context: formatter.Context{Format: NewFormat("table {{.Hostname}}", false)}, expected: `HOSTNAME foobar_baz foobar_bar @@ -98,7 +100,7 @@ foobar_boo clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, }, { - context: Context{Format: NewNodeFormat("table {{.Hostname}}", true)}, + context: formatter.Context{Format: NewFormat("table {{.Hostname}}", true)}, expected: `HOSTNAME foobar_baz foobar_bar @@ -107,7 +109,7 @@ foobar_boo clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, }, { - context: Context{Format: NewNodeFormat("table {{.ID}}\t{{.Hostname}}\t{{.TLSStatus}}", false)}, + context: formatter.Context{Format: NewFormat("table {{.ID}}\t{{.Hostname}}\t{{.TLSStatus}}", false)}, expected: `ID HOSTNAME TLS STATUS nodeID1 foobar_baz Needs Rotation nodeID2 foobar_bar Ready @@ -116,7 +118,7 @@ nodeID3 foobar_boo Unknown clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, }, { // no cluster TLS status info, TLS status for all nodes is unknown - context: Context{Format: NewNodeFormat("table {{.ID}}\t{{.Hostname}}\t{{.TLSStatus}}", false)}, + context: formatter.Context{Format: NewFormat("table {{.ID}}\t{{.Hostname}}\t{{.TLSStatus}}", false)}, expected: `ID HOSTNAME TLS STATUS nodeID1 foobar_baz Unknown nodeID2 foobar_bar Unknown @@ -126,7 +128,7 @@ nodeID3 foobar_boo Unknown }, // Raw Format { - context: Context{Format: NewNodeFormat("raw", false)}, + context: formatter.Context{Format: NewFormat("raw", false)}, expected: `node_id: nodeID1 hostname: foobar_baz status: Foo @@ -147,7 +149,7 @@ manager_status: ` + "\n\n", // to preserve whitespace clusterInfo: swarm.ClusterInfo{TLSInfo: swarm.TLSInfo{TrustRoot: "hi"}}, }, { - context: Context{Format: NewNodeFormat("raw", true)}, + context: formatter.Context{Format: NewFormat("raw", true)}, expected: `node_id: nodeID1 node_id: nodeID2 node_id: nodeID3 @@ -156,7 +158,7 @@ node_id: nodeID3 }, // Custom Format { - context: Context{Format: NewNodeFormat("{{.Hostname}} {{.TLSStatus}}", false)}, + context: formatter.Context{Format: NewFormat("{{.Hostname}} {{.TLSStatus}}", false)}, expected: `foobar_baz Needs Rotation foobar_bar Ready foobar_boo Unknown @@ -201,7 +203,7 @@ foobar_boo Unknown } out := bytes.NewBufferString("") testcase.context.Output = out - err := NodeWrite(testcase.context, nodes, types.Info{Swarm: swarm.Info{Cluster: &testcase.clusterInfo}}) + err := FormatWrite(testcase.context, nodes, types.Info{Swarm: swarm.Info{Cluster: &testcase.clusterInfo}}) if err != nil { assert.Error(t, err, testcase.expected) } else { @@ -247,7 +249,7 @@ func TestNodeContextWriteJSON(t *testing.T) { {ID: "nodeID3", Description: swarm.NodeDescription{Hostname: "foobar_boo", Engine: swarm.EngineDescription{EngineVersion: "18.03.0-ce"}}}, } out := bytes.NewBufferString("") - err := NodeWrite(Context{Format: "{{json .}}", Output: out}, nodes, testcase.info) + err := FormatWrite(formatter.Context{Format: "{{json .}}", Output: out}, nodes, testcase.info) if err != nil { t.Fatal(err) } @@ -267,7 +269,7 @@ func TestNodeContextWriteJSONField(t *testing.T) { {ID: "nodeID2", Description: swarm.NodeDescription{Hostname: "foobar_bar"}}, } out := bytes.NewBufferString("") - err := NodeWrite(Context{Format: "{{json .ID}}", Output: out}, nodes, types.Info{}) + err := FormatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, nodes, types.Info{}) if err != nil { t.Fatal(err) } @@ -311,11 +313,11 @@ func TestNodeInspectWriteContext(t *testing.T) { }, } out := bytes.NewBufferString("") - context := Context{ - Format: NewNodeFormat("pretty", false), + context := formatter.Context{ + Format: NewFormat("pretty", false), Output: out, } - err := NodeInspectWrite(context, []string{"nodeID1"}, func(string) (interface{}, []byte, error) { + err := InspectFormatWrite(context, []string{"nodeID1"}, func(string) (interface{}, []byte, error) { return node, nil, nil }) if err != nil { diff --git a/cli/command/node/inspect.go b/cli/command/node/inspect.go index 0dcb5db9b1..9c68f43b79 100644 --- a/cli/command/node/inspect.go +++ b/cli/command/node/inspect.go @@ -62,10 +62,10 @@ func runInspect(dockerCli command.Cli, opts inspectOptions) error { nodeCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewNodeFormat(f, false), + Format: NewFormat(f, false), } - if err := formatter.NodeInspectWrite(nodeCtx, opts.nodeIds, getRef); err != nil { + if err := InspectFormatWrite(nodeCtx, opts.nodeIds, getRef); err != nil { return cli.StatusError{StatusCode: 1, Status: err.Error()} } return nil diff --git a/cli/command/node/list.go b/cli/command/node/list.go index 69f40ddeb6..fdea329dea 100644 --- a/cli/command/node/list.go +++ b/cli/command/node/list.go @@ -69,10 +69,10 @@ func runList(dockerCli command.Cli, options listOptions) error { nodesCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewNodeFormat(format, options.quiet), + Format: NewFormat(format, options.quiet), } sort.Slice(nodes, func(i, j int) bool { return sortorder.NaturalLess(nodes[i].Description.Hostname, nodes[j].Description.Hostname) }) - return formatter.NodeWrite(nodesCtx, nodes, info) + return FormatWrite(nodesCtx, nodes, info) } diff --git a/cli/command/formatter/plugin.go b/cli/command/plugin/formatter.go similarity index 62% rename from cli/command/formatter/plugin.go rename to cli/command/plugin/formatter.go index 0877114904..438bf95171 100644 --- a/cli/command/formatter/plugin.go +++ b/cli/command/plugin/formatter.go @@ -1,8 +1,9 @@ -package formatter +package plugin import ( "strings" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" ) @@ -10,31 +11,30 @@ import ( const ( defaultPluginTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Description}}\t{{.Enabled}}" - pluginIDHeader = "ID" - descriptionHeader = "DESCRIPTION" - enabledHeader = "ENABLED" + enabledHeader = "ENABLED" + pluginIDHeader = "ID" ) -// NewPluginFormat returns a Format for rendering using a plugin Context -func NewPluginFormat(source string, quiet bool) Format { +// NewFormat returns a Format for rendering using a plugin Context +func NewFormat(source string, quiet bool) formatter.Format { switch source { - case TableFormatKey: + case formatter.TableFormatKey: if quiet { - return defaultQuietFormat + return formatter.DefaultQuietFormat } return defaultPluginTableFormat - case RawFormatKey: + case formatter.RawFormatKey: if quiet { return `plugin_id: {{.ID}}` } return `plugin_id: {{.ID}}\nname: {{.Name}}\ndescription: {{.Description}}\nenabled: {{.Enabled}}\n` } - return Format(source) + return formatter.Format(source) } -// PluginWrite writes the context -func PluginWrite(ctx Context, plugins []*types.Plugin) error { - render := func(format func(subContext subContext) error) error { +// FormatWrite writes the context +func FormatWrite(ctx formatter.Context, plugins []*types.Plugin) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, plugin := range plugins { pluginCtx := &pluginContext{trunc: ctx.Trunc, p: *plugin} if err := format(pluginCtx); err != nil { @@ -44,24 +44,24 @@ func PluginWrite(ctx Context, plugins []*types.Plugin) error { return nil } pluginCtx := pluginContext{} - pluginCtx.header = map[string]string{ + pluginCtx.Header = formatter.SubHeaderContext{ "ID": pluginIDHeader, - "Name": nameHeader, - "Description": descriptionHeader, + "Name": formatter.NameHeader, + "Description": formatter.DescriptionHeader, "Enabled": enabledHeader, - "PluginReference": imageHeader, + "PluginReference": formatter.ImageHeader, } return ctx.Write(&pluginCtx, render) } type pluginContext struct { - HeaderContext + formatter.HeaderContext trunc bool p types.Plugin } func (c *pluginContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return formatter.MarshalJSON(c) } func (c *pluginContext) ID() string { @@ -79,7 +79,7 @@ func (c *pluginContext) Description() string { desc := strings.Replace(c.p.Config.Description, "\n", "", -1) desc = strings.Replace(desc, "\r", "", -1) if c.trunc { - desc = Ellipsis(desc, 45) + desc = formatter.Ellipsis(desc, 45) } return desc diff --git a/cli/command/formatter/plugin_test.go b/cli/command/plugin/formatter_test.go similarity index 80% rename from cli/command/formatter/plugin_test.go rename to cli/command/plugin/formatter_test.go index b8551589e1..66853eb99c 100644 --- a/cli/command/formatter/plugin_test.go +++ b/cli/command/plugin/formatter_test.go @@ -1,4 +1,4 @@ -package formatter +package plugin import ( "bytes" @@ -6,6 +6,8 @@ import ( "strings" "testing" + "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" "gotest.tools/assert" @@ -41,7 +43,7 @@ func TestPluginContext(t *testing.T) { ctx = c.pluginCtx v := c.call() if strings.Contains(v, ",") { - compareMultipleValues(t, v, c.expValue) + test.CompareMultipleValues(t, v, c.expValue) } else if v != c.expValue { t.Fatalf("Expected %s, was %s\n", c.expValue, v) } @@ -50,44 +52,44 @@ func TestPluginContext(t *testing.T) { func TestPluginContextWrite(t *testing.T) { cases := []struct { - context Context + context formatter.Context expected string }{ // Errors { - Context{Format: "{{InvalidFunction}}"}, + formatter.Context{Format: "{{InvalidFunction}}"}, `Template parsing error: template: :1: function "InvalidFunction" not defined `, }, { - Context{Format: "{{nil}}"}, + formatter.Context{Format: "{{nil}}"}, `Template parsing error: template: :1:2: executing "" at : nil is not a command `, }, // Table format { - Context{Format: NewPluginFormat("table", false)}, + formatter.Context{Format: NewFormat("table", false)}, `ID NAME DESCRIPTION ENABLED pluginID1 foobar_baz description 1 true pluginID2 foobar_bar description 2 false `, }, { - Context{Format: NewPluginFormat("table", true)}, + formatter.Context{Format: NewFormat("table", true)}, `pluginID1 pluginID2 `, }, { - Context{Format: NewPluginFormat("table {{.Name}}", false)}, + formatter.Context{Format: NewFormat("table {{.Name}}", false)}, `NAME foobar_baz foobar_bar `, }, { - Context{Format: NewPluginFormat("table {{.Name}}", true)}, + formatter.Context{Format: NewFormat("table {{.Name}}", true)}, `NAME foobar_baz foobar_bar @@ -95,7 +97,7 @@ foobar_bar }, // Raw Format { - Context{Format: NewPluginFormat("raw", false)}, + formatter.Context{Format: NewFormat("raw", false)}, `plugin_id: pluginID1 name: foobar_baz description: description 1 @@ -109,14 +111,14 @@ enabled: false `, }, { - Context{Format: NewPluginFormat("raw", true)}, + formatter.Context{Format: NewFormat("raw", true)}, `plugin_id: pluginID1 plugin_id: pluginID2 `, }, // Custom Format { - Context{Format: NewPluginFormat("{{.Name}}", false)}, + formatter.Context{Format: NewFormat("{{.Name}}", false)}, `foobar_baz foobar_bar `, @@ -130,7 +132,7 @@ foobar_bar } out := bytes.NewBufferString("") testcase.context.Output = out - err := PluginWrite(testcase.context, plugins) + err := FormatWrite(testcase.context, plugins) if err != nil { assert.Error(t, err, testcase.expected) } else { @@ -150,7 +152,7 @@ func TestPluginContextWriteJSON(t *testing.T) { } out := bytes.NewBufferString("") - err := PluginWrite(Context{Format: "{{json .}}", Output: out}, plugins) + err := FormatWrite(formatter.Context{Format: "{{json .}}", Output: out}, plugins) if err != nil { t.Fatal(err) } @@ -169,7 +171,7 @@ func TestPluginContextWriteJSONField(t *testing.T) { {ID: "pluginID2", Name: "foobar_bar"}, } out := bytes.NewBufferString("") - err := PluginWrite(Context{Format: "{{json .ID}}", Output: out}, plugins) + err := FormatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, plugins) if err != nil { t.Fatal(err) } diff --git a/cli/command/plugin/list.go b/cli/command/plugin/list.go index 35612d1859..16c7db6cf4 100644 --- a/cli/command/plugin/list.go +++ b/cli/command/plugin/list.go @@ -63,8 +63,8 @@ func runList(dockerCli command.Cli, options listOptions) error { pluginsCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewPluginFormat(format, options.quiet), + Format: NewFormat(format, options.quiet), Trunc: !options.noTrunc, } - return formatter.PluginWrite(pluginsCtx, plugins) + return FormatWrite(pluginsCtx, plugins) } diff --git a/cli/command/formatter/search.go b/cli/command/registry/formatter_search.go similarity index 75% rename from cli/command/formatter/search.go rename to cli/command/registry/formatter_search.go index 8b5cfc4f9d..c536a511ab 100644 --- a/cli/command/formatter/search.go +++ b/cli/command/registry/formatter_search.go @@ -1,9 +1,10 @@ -package formatter +package registry import ( "strconv" "strings" + "github.com/docker/cli/cli/command/formatter" registry "github.com/docker/docker/api/types/registry" ) @@ -16,19 +17,19 @@ const ( ) // NewSearchFormat returns a Format for rendering using a network Context -func NewSearchFormat(source string) Format { +func NewSearchFormat(source string) formatter.Format { switch source { case "": return defaultSearchTableFormat - case TableFormatKey: + case formatter.TableFormatKey: return defaultSearchTableFormat } - return Format(source) + return formatter.Format(source) } // SearchWrite writes the context -func SearchWrite(ctx Context, results []registry.SearchResult, auto bool, stars int) error { - render := func(format func(subContext subContext) error) error { +func SearchWrite(ctx formatter.Context, results []registry.SearchResult, auto bool, stars int) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, result := range results { // --automated and -s, --stars are deprecated since Docker 1.12 if (auto && !result.IsAutomated) || (stars > result.StarCount) { @@ -42,9 +43,9 @@ func SearchWrite(ctx Context, results []registry.SearchResult, auto bool, stars return nil } searchCtx := searchContext{} - searchCtx.header = map[string]string{ - "Name": nameHeader, - "Description": descriptionHeader, + searchCtx.Header = formatter.SubHeaderContext{ + "Name": formatter.NameHeader, + "Description": formatter.DescriptionHeader, "StarCount": starsHeader, "IsOfficial": officialHeader, "IsAutomated": automatedHeader, @@ -53,7 +54,7 @@ func SearchWrite(ctx Context, results []registry.SearchResult, auto bool, stars } type searchContext struct { - HeaderContext + formatter.HeaderContext trunc bool json bool s registry.SearchResult @@ -61,7 +62,7 @@ type searchContext struct { func (c *searchContext) MarshalJSON() ([]byte, error) { c.json = true - return marshalJSON(c) + return formatter.MarshalJSON(c) } func (c *searchContext) Name() string { @@ -72,7 +73,7 @@ func (c *searchContext) Description() string { desc := strings.Replace(c.s.Description, "\n", " ", -1) desc = strings.Replace(desc, "\r", " ", -1) if c.trunc { - desc = Ellipsis(desc, 45) + desc = formatter.Ellipsis(desc, 45) } return desc } diff --git a/cli/command/formatter/search_test.go b/cli/command/registry/formatter_search_test.go similarity index 85% rename from cli/command/formatter/search_test.go rename to cli/command/registry/formatter_search_test.go index 2e5bac0fc8..0feb3ba53c 100644 --- a/cli/command/formatter/search_test.go +++ b/cli/command/registry/formatter_search_test.go @@ -1,4 +1,4 @@ -package formatter +package registry import ( "bytes" @@ -6,6 +6,8 @@ import ( "strings" "testing" + "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/internal/test" registrytypes "github.com/docker/docker/api/types/registry" "gotest.tools/assert" is "gotest.tools/assert/cmp" @@ -46,7 +48,7 @@ func TestSearchContext(t *testing.T) { ctx = c.searchCtx v := c.call() if strings.Contains(v, ",") { - compareMultipleValues(t, v, c.expValue) + test.CompareMultipleValues(t, v, c.expValue) } else if v != c.expValue { t.Fatalf("Expected %s, was %s\n", c.expValue, v) } @@ -79,7 +81,7 @@ func TestSearchContextDescription(t *testing.T) { {searchContext{ s: registrytypes.SearchResult{Description: longDescription}, trunc: true, - }, Ellipsis(longDescription, 45), ctx.Description}, + }, formatter.Ellipsis(longDescription, 45), ctx.Description}, {searchContext{ s: registrytypes.SearchResult{Description: descriptionWReturns}, trunc: false, @@ -87,14 +89,14 @@ func TestSearchContextDescription(t *testing.T) { {searchContext{ s: registrytypes.SearchResult{Description: descriptionWReturns}, trunc: true, - }, Ellipsis(longDescription, 45), ctx.Description}, + }, formatter.Ellipsis(longDescription, 45), ctx.Description}, } for _, c := range cases { ctx = c.searchCtx v := c.call() if strings.Contains(v, ",") { - compareMultipleValues(t, v, c.expValue) + test.CompareMultipleValues(t, v, c.expValue) } else if v != c.expValue { t.Fatalf("Expected %s, was %s\n", c.expValue, v) } @@ -103,28 +105,28 @@ func TestSearchContextDescription(t *testing.T) { func TestSearchContextWrite(t *testing.T) { cases := []struct { - context Context + context formatter.Context expected string }{ // Errors { - Context{Format: "{{InvalidFunction}}"}, + formatter.Context{Format: "{{InvalidFunction}}"}, `Template parsing error: template: :1: function "InvalidFunction" not defined `, }, { - Context{Format: "{{nil}}"}, + formatter.Context{Format: "{{nil}}"}, `Template parsing error: template: :1:2: executing "" at : nil is not a command `, }, // Table format { - Context{Format: NewSearchFormat("table")}, + formatter.Context{Format: NewSearchFormat("table")}, string(golden.Get(t, "search-context-write-table.golden")), }, { - Context{Format: NewSearchFormat("table {{.Name}}")}, + formatter.Context{Format: NewSearchFormat("table {{.Name}}")}, `NAME result1 result2 @@ -132,14 +134,14 @@ result2 }, // Custom Format { - Context{Format: NewSearchFormat("{{.Name}}")}, + formatter.Context{Format: NewSearchFormat("{{.Name}}")}, `result1 result2 `, }, // Custom Format with CreatedAt { - Context{Format: NewSearchFormat("{{.Name}} {{.StarCount}}")}, + formatter.Context{Format: NewSearchFormat("{{.Name}} {{.StarCount}}")}, `result1 5000 result2 5 `, @@ -164,19 +166,19 @@ result2 5 func TestSearchContextWriteAutomated(t *testing.T) { cases := []struct { - context Context + context formatter.Context expected string }{ // Table format { - Context{Format: NewSearchFormat("table")}, + formatter.Context{Format: NewSearchFormat("table")}, `NAME DESCRIPTION STARS OFFICIAL AUTOMATED result2 Not official 5 [OK] `, }, { - Context{Format: NewSearchFormat("table {{.Name}}")}, + formatter.Context{Format: NewSearchFormat("table {{.Name}}")}, `NAME result2 `, @@ -201,17 +203,17 @@ result2 func TestSearchContextWriteStars(t *testing.T) { cases := []struct { - context Context + context formatter.Context expected string }{ // Table format { - Context{Format: NewSearchFormat("table")}, + formatter.Context{Format: NewSearchFormat("table")}, string(golden.Get(t, "search-context-write-stars-table.golden")), }, { - Context{Format: NewSearchFormat("table {{.Name}}")}, + formatter.Context{Format: NewSearchFormat("table {{.Name}}")}, `NAME result1 `, @@ -245,7 +247,7 @@ func TestSearchContextWriteJSON(t *testing.T) { } out := bytes.NewBufferString("") - err := SearchWrite(Context{Format: "{{json .}}", Output: out}, results, false, 0) + err := SearchWrite(formatter.Context{Format: "{{json .}}", Output: out}, results, false, 0) if err != nil { t.Fatal(err) } @@ -265,7 +267,7 @@ func TestSearchContextWriteJSONField(t *testing.T) { {Name: "result2", Description: "Not official", StarCount: 5, IsOfficial: false, IsAutomated: true}, } out := bytes.NewBufferString("") - err := SearchWrite(Context{Format: "{{json .Name}}", Output: out}, results, false, 0) + err := SearchWrite(formatter.Context{Format: "{{json .Name}}", Output: out}, results, false, 0) if err != nil { t.Fatal(err) } diff --git a/cli/command/registry/search.go b/cli/command/registry/search.go index f048752218..7b9b82245c 100644 --- a/cli/command/registry/search.go +++ b/cli/command/registry/search.go @@ -90,8 +90,8 @@ func runSearch(dockerCli command.Cli, options searchOptions) error { }) searchCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewSearchFormat(options.format), + Format: NewSearchFormat(options.format), Trunc: !options.noTrunc, } - return formatter.SearchWrite(searchCtx, results, options.automated, int(options.stars)) + return SearchWrite(searchCtx, results, options.automated, int(options.stars)) } diff --git a/cli/command/formatter/testdata/search-context-write-stars-table.golden b/cli/command/registry/testdata/search-context-write-stars-table.golden similarity index 100% rename from cli/command/formatter/testdata/search-context-write-stars-table.golden rename to cli/command/registry/testdata/search-context-write-stars-table.golden diff --git a/cli/command/formatter/testdata/search-context-write-table.golden b/cli/command/registry/testdata/search-context-write-table.golden similarity index 100% rename from cli/command/formatter/testdata/search-context-write-table.golden rename to cli/command/registry/testdata/search-context-write-table.golden diff --git a/cli/command/formatter/secret.go b/cli/command/secret/formatter.go similarity index 70% rename from cli/command/formatter/secret.go rename to cli/command/secret/formatter.go index d025cd8f3b..24a56bda62 100644 --- a/cli/command/formatter/secret.go +++ b/cli/command/secret/formatter.go @@ -1,4 +1,4 @@ -package formatter +package secret import ( "fmt" @@ -6,17 +6,18 @@ import ( "time" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/inspect" "github.com/docker/docker/api/types/swarm" units "github.com/docker/go-units" ) const ( - defaultSecretTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Driver}}\t{{.CreatedAt}}\t{{.UpdatedAt}}" - secretIDHeader = "ID" - secretCreatedHeader = "CREATED" - secretUpdatedHeader = "UPDATED" - secretInspectPrettyTemplate Format = `ID: {{.ID}} + defaultSecretTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Driver}}\t{{.CreatedAt}}\t{{.UpdatedAt}}" + secretIDHeader = "ID" + secretCreatedHeader = "CREATED" + secretUpdatedHeader = "UPDATED" + secretInspectPrettyTemplate formatter.Format = `ID: {{.ID}} Name: {{.Name}} {{- if .Labels }} Labels: @@ -28,23 +29,23 @@ Created at: {{.CreatedAt}} Updated at: {{.UpdatedAt}}` ) -// NewSecretFormat returns a Format for rendering using a secret Context -func NewSecretFormat(source string, quiet bool) Format { +// NewFormat returns a Format for rendering using a secret Context +func NewFormat(source string, quiet bool) formatter.Format { switch source { - case PrettyFormatKey: + case formatter.PrettyFormatKey: return secretInspectPrettyTemplate - case TableFormatKey: + case formatter.TableFormatKey: if quiet { - return defaultQuietFormat + return formatter.DefaultQuietFormat } return defaultSecretTableFormat } - return Format(source) + return formatter.Format(source) } -// SecretWrite writes the context -func SecretWrite(ctx Context, secrets []swarm.Secret) error { - render := func(format func(subContext subContext) error) error { +// FormatWrite writes the context +func FormatWrite(ctx formatter.Context, secrets []swarm.Secret) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, secret := range secrets { secretCtx := &secretContext{s: secret} if err := format(secretCtx); err != nil { @@ -59,24 +60,24 @@ func SecretWrite(ctx Context, secrets []swarm.Secret) error { func newSecretContext() *secretContext { sCtx := &secretContext{} - sCtx.header = map[string]string{ + sCtx.Header = formatter.SubHeaderContext{ "ID": secretIDHeader, - "Name": nameHeader, - "Driver": driverHeader, + "Name": formatter.NameHeader, + "Driver": formatter.DriverHeader, "CreatedAt": secretCreatedHeader, "UpdatedAt": secretUpdatedHeader, - "Labels": labelsHeader, + "Labels": formatter.LabelsHeader, } return sCtx } type secretContext struct { - HeaderContext + formatter.HeaderContext s swarm.Secret } func (c *secretContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return formatter.MarshalJSON(c) } func (c *secretContext) ID() string { @@ -121,12 +122,12 @@ func (c *secretContext) Label(name string) string { return c.s.Spec.Annotations.Labels[name] } -// SecretInspectWrite renders the context for a list of secrets -func SecretInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) error { +// InspectFormatWrite renders the context for a list of secrets +func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.GetRefFunc) error { if ctx.Format != secretInspectPrettyTemplate { return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef) } - render := func(format func(subContext subContext) error) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, ref := range refs { secretI, _, err := getRef(ref) if err != nil { @@ -147,7 +148,7 @@ func SecretInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) e type secretInspectContext struct { swarm.Secret - subContext + formatter.SubContext } func (ctx *secretInspectContext) ID() string { diff --git a/cli/command/formatter/secret_test.go b/cli/command/secret/formatter_test.go similarity index 77% rename from cli/command/formatter/secret_test.go rename to cli/command/secret/formatter_test.go index 31119786ff..751e7c3daf 100644 --- a/cli/command/formatter/secret_test.go +++ b/cli/command/secret/formatter_test.go @@ -1,10 +1,11 @@ -package formatter +package secret import ( "bytes" "testing" "time" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/docker/api/types/swarm" "gotest.tools/assert" is "gotest.tools/assert/cmp" @@ -13,32 +14,32 @@ import ( func TestSecretContextFormatWrite(t *testing.T) { // Check default output format (verbose and non-verbose mode) for table headers cases := []struct { - context Context + context formatter.Context expected string }{ // Errors { - Context{Format: "{{InvalidFunction}}"}, + formatter.Context{Format: "{{InvalidFunction}}"}, `Template parsing error: template: :1: function "InvalidFunction" not defined `, }, { - Context{Format: "{{nil}}"}, + formatter.Context{Format: "{{nil}}"}, `Template parsing error: template: :1:2: executing "" at : nil is not a command `, }, // Table format - {Context{Format: NewSecretFormat("table", false)}, + {formatter.Context{Format: NewFormat("table", false)}, `ID NAME DRIVER CREATED UPDATED 1 passwords Less than a second ago Less than a second ago 2 id_rsa Less than a second ago Less than a second ago `}, - {Context{Format: NewSecretFormat("table {{.Name}}", true)}, + {formatter.Context{Format: NewFormat("table {{.Name}}", true)}, `NAME passwords id_rsa `}, - {Context{Format: NewSecretFormat("{{.ID}}-{{.Name}}", false)}, + {formatter.Context{Format: NewFormat("{{.ID}}-{{.Name}}", false)}, `1-passwords 2-id_rsa `}, @@ -55,7 +56,7 @@ id_rsa for _, testcase := range cases { out := bytes.NewBufferString("") testcase.context.Output = out - if err := SecretWrite(testcase.context, secrets); err != nil { + if err := FormatWrite(testcase.context, secrets); err != nil { assert.Error(t, err, testcase.expected) } else { assert.Check(t, is.Equal(testcase.expected, out.String())) diff --git a/cli/command/secret/inspect.go b/cli/command/secret/inspect.go index 1afcb5211f..f7866de1bf 100644 --- a/cli/command/secret/inspect.go +++ b/cli/command/secret/inspect.go @@ -55,10 +55,10 @@ func runSecretInspect(dockerCli command.Cli, opts inspectOptions) error { secretCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewSecretFormat(f, false), + Format: NewFormat(f, false), } - if err := formatter.SecretInspectWrite(secretCtx, opts.names, getRef); err != nil { + if err := InspectFormatWrite(secretCtx, opts.names, getRef); err != nil { return cli.StatusError{StatusCode: 1, Status: err.Error()} } return nil diff --git a/cli/command/secret/ls.go b/cli/command/secret/ls.go index 424ac57b3f..878f6fe977 100644 --- a/cli/command/secret/ls.go +++ b/cli/command/secret/ls.go @@ -63,7 +63,7 @@ func runSecretList(dockerCli command.Cli, options listOptions) error { secretCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewSecretFormat(format, options.quiet), + Format: NewFormat(format, options.quiet), } - return formatter.SecretWrite(secretCtx, secrets) + return FormatWrite(secretCtx, secrets) } diff --git a/cli/command/formatter/service.go b/cli/command/service/formatter.go similarity index 92% rename from cli/command/formatter/service.go rename to cli/command/service/formatter.go index 7e98c314c9..4c81406a4a 100644 --- a/cli/command/formatter/service.go +++ b/cli/command/service/formatter.go @@ -1,4 +1,4 @@ -package formatter +package service import ( "fmt" @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/inspect" "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" @@ -16,7 +17,7 @@ import ( "github.com/pkg/errors" ) -const serviceInspectPrettyTemplate Format = ` +const serviceInspectPrettyTemplate formatter.Format = ` ID: {{.ID}} Name: {{.Name}} {{- if .Labels }} @@ -144,13 +145,13 @@ Ports: {{- end }} {{ end -}} ` -// NewServiceFormat returns a Format for rendering using a Context -func NewServiceFormat(source string) Format { +// NewFormat returns a Format for rendering using a Context +func NewFormat(source string) formatter.Format { switch source { - case PrettyFormatKey: + case formatter.PrettyFormatKey: return serviceInspectPrettyTemplate default: - return Format(strings.TrimPrefix(source, RawFormatKey)) + return formatter.Format(strings.TrimPrefix(source, formatter.RawFormatKey)) } } @@ -166,12 +167,12 @@ func resolveNetworks(service swarm.Service, getNetwork inspect.GetRefFunc) map[s return networkNames } -// ServiceInspectWrite renders the context for a list of services -func ServiceInspectWrite(ctx Context, refs []string, getRef, getNetwork inspect.GetRefFunc) error { +// InspectFormatWrite renders the context for a list of services +func InspectFormatWrite(ctx formatter.Context, refs []string, getRef, getNetwork inspect.GetRefFunc) error { if ctx.Format != serviceInspectPrettyTemplate { return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef) } - render := func(format func(subContext subContext) error) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, ref := range refs { serviceI, _, err := getRef(ref) if err != nil { @@ -192,7 +193,7 @@ func ServiceInspectWrite(ctx Context, refs []string, getRef, getNetwork inspect. type serviceInspectContext struct { swarm.Service - subContext + formatter.SubContext // networkNames is a map from network IDs (as found in // Networks[x].Target) to network names. @@ -200,7 +201,7 @@ type serviceInspectContext struct { } func (ctx *serviceInspectContext) MarshalJSON() ([]byte, error) { - return marshalJSON(ctx) + return formatter.MarshalJSON(ctx) } func (ctx *serviceInspectContext) ID() string { @@ -462,32 +463,32 @@ const ( replicasHeader = "REPLICAS" ) -// NewServiceListFormat returns a Format for rendering using a service Context -func NewServiceListFormat(source string, quiet bool) Format { +// NewListFormat returns a Format for rendering using a service Context +func NewListFormat(source string, quiet bool) formatter.Format { switch source { - case TableFormatKey: + case formatter.TableFormatKey: if quiet { - return defaultQuietFormat + return formatter.DefaultQuietFormat } return defaultServiceTableFormat - case RawFormatKey: + case formatter.RawFormatKey: if quiet { return `id: {{.ID}}` } return `id: {{.ID}}\nname: {{.Name}}\nmode: {{.Mode}}\nreplicas: {{.Replicas}}\nimage: {{.Image}}\nports: {{.Ports}}\n` } - return Format(source) + return formatter.Format(source) } -// ServiceListInfo stores the information about mode and replicas to be used by template -type ServiceListInfo struct { +// ListInfo stores the information about mode and replicas to be used by template +type ListInfo struct { Mode string Replicas string } -// ServiceListWrite writes the context -func ServiceListWrite(ctx Context, services []swarm.Service, info map[string]ServiceListInfo) error { - render := func(format func(subContext subContext) error) error { +// ListFormatWrite writes the context +func ListFormatWrite(ctx formatter.Context, services []swarm.Service, info map[string]ListInfo) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, service := range services { serviceCtx := &serviceContext{service: service, mode: info[service.ID].Mode, replicas: info[service.ID].Replicas} if err := format(serviceCtx); err != nil { @@ -497,26 +498,26 @@ func ServiceListWrite(ctx Context, services []swarm.Service, info map[string]Ser return nil } serviceCtx := serviceContext{} - serviceCtx.header = map[string]string{ + serviceCtx.Header = formatter.SubHeaderContext{ "ID": serviceIDHeader, - "Name": nameHeader, + "Name": formatter.NameHeader, "Mode": modeHeader, "Replicas": replicasHeader, - "Image": imageHeader, - "Ports": portsHeader, + "Image": formatter.ImageHeader, + "Ports": formatter.PortsHeader, } return ctx.Write(&serviceCtx, render) } type serviceContext struct { - HeaderContext + formatter.HeaderContext service swarm.Service mode string replicas string } func (c *serviceContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return formatter.MarshalJSON(c) } func (c *serviceContext) ID() string { diff --git a/cli/command/formatter/service_test.go b/cli/command/service/formatter_test.go similarity index 88% rename from cli/command/formatter/service_test.go rename to cli/command/service/formatter_test.go index 243899d1b9..8addeba0c7 100644 --- a/cli/command/formatter/service_test.go +++ b/cli/command/service/formatter_test.go @@ -1,4 +1,4 @@ -package formatter +package service import ( "bytes" @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/docker/api/types/swarm" "gotest.tools/assert" is "gotest.tools/assert/cmp" @@ -15,43 +16,43 @@ import ( func TestServiceContextWrite(t *testing.T) { cases := []struct { - context Context + context formatter.Context expected string }{ // Errors { - Context{Format: "{{InvalidFunction}}"}, + formatter.Context{Format: "{{InvalidFunction}}"}, `Template parsing error: template: :1: function "InvalidFunction" not defined `, }, { - Context{Format: "{{nil}}"}, + formatter.Context{Format: "{{nil}}"}, `Template parsing error: template: :1:2: executing "" at : nil is not a command `, }, // Table format { - Context{Format: NewServiceListFormat("table", false)}, + formatter.Context{Format: NewListFormat("table", false)}, `ID NAME MODE REPLICAS IMAGE PORTS id_baz baz global 2/4 *:80->8080/tcp id_bar bar replicated 2/4 *:80->8080/tcp `, }, { - Context{Format: NewServiceListFormat("table", true)}, + formatter.Context{Format: NewListFormat("table", true)}, `id_baz id_bar `, }, { - Context{Format: NewServiceListFormat("table {{.Name}}", false)}, + formatter.Context{Format: NewListFormat("table {{.Name}}", false)}, `NAME baz bar `, }, { - Context{Format: NewServiceListFormat("table {{.Name}}", true)}, + formatter.Context{Format: NewListFormat("table {{.Name}}", true)}, `NAME baz bar @@ -59,18 +60,18 @@ bar }, // Raw Format { - Context{Format: NewServiceListFormat("raw", false)}, + formatter.Context{Format: NewListFormat("raw", false)}, string(golden.Get(t, "service-context-write-raw.golden")), }, { - Context{Format: NewServiceListFormat("raw", true)}, + formatter.Context{Format: NewListFormat("raw", true)}, `id: id_baz id: id_bar `, }, // Custom Format { - Context{Format: NewServiceListFormat("{{.Name}}", false)}, + formatter.Context{Format: NewListFormat("{{.Name}}", false)}, `baz bar `, @@ -112,7 +113,7 @@ bar }, }, } - info := map[string]ServiceListInfo{ + info := map[string]ListInfo{ "id_baz": { Mode: "global", Replicas: "2/4", @@ -124,7 +125,7 @@ bar } out := bytes.NewBufferString("") testcase.context.Output = out - err := ServiceListWrite(testcase.context, services, info) + err := ListFormatWrite(testcase.context, services, info) if err != nil { assert.Error(t, err, testcase.expected) } else { @@ -168,7 +169,7 @@ func TestServiceContextWriteJSON(t *testing.T) { }, }, } - info := map[string]ServiceListInfo{ + info := map[string]ListInfo{ "id_baz": { Mode: "global", Replicas: "2/4", @@ -184,7 +185,7 @@ func TestServiceContextWriteJSON(t *testing.T) { } out := bytes.NewBufferString("") - err := ServiceListWrite(Context{Format: "{{json .}}", Output: out}, services, info) + err := ListFormatWrite(formatter.Context{Format: "{{json .}}", Output: out}, services, info) if err != nil { t.Fatal(err) } @@ -201,7 +202,7 @@ func TestServiceContextWriteJSONField(t *testing.T) { {ID: "id_baz", Spec: swarm.ServiceSpec{Annotations: swarm.Annotations{Name: "baz"}}}, {ID: "id_bar", Spec: swarm.ServiceSpec{Annotations: swarm.Annotations{Name: "bar"}}}, } - info := map[string]ServiceListInfo{ + info := map[string]ListInfo{ "id_baz": { Mode: "global", Replicas: "2/4", @@ -212,7 +213,7 @@ func TestServiceContextWriteJSONField(t *testing.T) { }, } out := bytes.NewBufferString("") - err := ServiceListWrite(Context{Format: "{{json .Name}}", Output: out}, services, info) + err := ListFormatWrite(formatter.Context{Format: "{{json .Name}}", Output: out}, services, info) if err != nil { t.Fatal(err) } diff --git a/cli/command/service/inspect.go b/cli/command/service/inspect.go index 7f988fae56..38709963a1 100644 --- a/cli/command/service/inspect.go +++ b/cli/command/service/inspect.go @@ -83,10 +83,10 @@ func runInspect(dockerCli command.Cli, opts inspectOptions) error { serviceCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewServiceFormat(f), + Format: NewFormat(f), } - if err := formatter.ServiceInspectWrite(serviceCtx, opts.refs, getRef, getNetwork); err != nil { + if err := InspectFormatWrite(serviceCtx, opts.refs, getRef, getNetwork); err != nil { return cli.StatusError{StatusCode: 1, Status: err.Error()} } return nil diff --git a/cli/command/service/inspect_test.go b/cli/command/service/inspect_test.go index c27a9a8850..4559353e41 100644 --- a/cli/command/service/inspect_test.go +++ b/cli/command/service/inspect_test.go @@ -104,7 +104,7 @@ func formatServiceInspect(t *testing.T, format formatter.Format, now time.Time) Format: format, } - err := formatter.ServiceInspectWrite(ctx, []string{"de179gar9d0o7ltdybungplod"}, + err := InspectFormatWrite(ctx, []string{"de179gar9d0o7ltdybungplod"}, func(ref string) (interface{}, []byte, error) { return s, nil, nil }, @@ -122,7 +122,7 @@ func formatServiceInspect(t *testing.T, format formatter.Format, now time.Time) } func TestPrettyPrintWithNoUpdateConfig(t *testing.T) { - s := formatServiceInspect(t, formatter.NewServiceFormat("pretty"), time.Now()) + s := formatServiceInspect(t, NewFormat("pretty"), time.Now()) if strings.Contains(s, "UpdateStatus") { t.Fatal("Pretty print failed before parsing UpdateStatus") } @@ -135,8 +135,8 @@ func TestJSONFormatWithNoUpdateConfig(t *testing.T) { now := time.Now() // s1: [{"ID":..}] // s2: {"ID":..} - s1 := formatServiceInspect(t, formatter.NewServiceFormat(""), now) - s2 := formatServiceInspect(t, formatter.NewServiceFormat("{{json .}}"), now) + s1 := formatServiceInspect(t, NewFormat(""), now) + s2 := formatServiceInspect(t, NewFormat("{{json .}}"), now) var m1Wrap []map[string]interface{} if err := json.Unmarshal([]byte(s1), &m1Wrap); err != nil { t.Fatal(err) @@ -153,7 +153,7 @@ func TestJSONFormatWithNoUpdateConfig(t *testing.T) { } func TestPrettyPrintWithConfigsAndSecrets(t *testing.T) { - s := formatServiceInspect(t, formatter.NewServiceFormat("pretty"), time.Now()) + s := formatServiceInspect(t, NewFormat("pretty"), time.Now()) assert.Check(t, is.Contains(s, "Configs:"), "Pretty print missing configs") assert.Check(t, is.Contains(s, "Secrets:"), "Pretty print missing secrets") diff --git a/cli/command/service/list.go b/cli/command/service/list.go index e65a9be075..188ffb6e55 100644 --- a/cli/command/service/list.go +++ b/cli/command/service/list.go @@ -57,7 +57,7 @@ func runList(dockerCli command.Cli, options listOptions) error { sort.Slice(services, func(i, j int) bool { return sortorder.NaturalLess(services[i].Spec.Name, services[j].Spec.Name) }) - info := map[string]formatter.ServiceListInfo{} + info := map[string]ListInfo{} if len(services) > 0 && !options.quiet { // only non-empty services and not quiet, should we call TaskList and NodeList api taskFilter := filters.NewArgs() @@ -89,13 +89,13 @@ func runList(dockerCli command.Cli, options listOptions) error { servicesCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewServiceListFormat(format, options.quiet), + Format: NewListFormat(format, options.quiet), } - return formatter.ServiceListWrite(servicesCtx, services, info) + return ListFormatWrite(servicesCtx, services, info) } // GetServicesStatus returns a map of mode and replicas -func GetServicesStatus(services []swarm.Service, nodes []swarm.Node, tasks []swarm.Task) map[string]formatter.ServiceListInfo { +func GetServicesStatus(services []swarm.Service, nodes []swarm.Node, tasks []swarm.Task) map[string]ListInfo { running := map[string]int{} tasksNoShutdown := map[string]int{} @@ -116,16 +116,16 @@ func GetServicesStatus(services []swarm.Service, nodes []swarm.Node, tasks []swa } } - info := map[string]formatter.ServiceListInfo{} + info := map[string]ListInfo{} for _, service := range services { - info[service.ID] = formatter.ServiceListInfo{} + info[service.ID] = ListInfo{} if service.Spec.Mode.Replicated != nil && service.Spec.Mode.Replicated.Replicas != nil { - info[service.ID] = formatter.ServiceListInfo{ + info[service.ID] = ListInfo{ Mode: "replicated", Replicas: fmt.Sprintf("%d/%d", running[service.ID], *service.Spec.Mode.Replicated.Replicas), } } else if service.Spec.Mode.Global != nil { - info[service.ID] = formatter.ServiceListInfo{ + info[service.ID] = ListInfo{ Mode: "global", Replicas: fmt.Sprintf("%d/%d", running[service.ID], tasksNoShutdown[service.ID]), } diff --git a/cli/command/formatter/testdata/service-context-write-raw.golden b/cli/command/service/testdata/service-context-write-raw.golden similarity index 100% rename from cli/command/formatter/testdata/service-context-write-raw.golden rename to cli/command/service/testdata/service-context-write-raw.golden diff --git a/cli/command/formatter/stack.go b/cli/command/stack/formatter/formatter.go similarity index 73% rename from cli/command/formatter/stack.go rename to cli/command/stack/formatter/formatter.go index 965eaf60d5..7f1cb82d54 100644 --- a/cli/command/formatter/stack.go +++ b/cli/command/stack/formatter/formatter.go @@ -2,6 +2,8 @@ package formatter import ( "strconv" + + "github.com/docker/cli/cli/command/formatter" ) const ( @@ -13,8 +15,17 @@ const ( stackServicesHeader = "SERVICES" stackOrchestrastorHeader = "ORCHESTRATOR" stackNamespaceHeader = "NAMESPACE" + + // TableFormatKey is an alias for formatter.TableFormatKey + TableFormatKey = formatter.TableFormatKey ) +// Context is an alias for formatter.Context +type Context = formatter.Context + +// Format is an alias for formatter.Format +type Format = formatter.Format + // Stack contains deployed stack information. type Stack struct { // Name is the name of the stack @@ -28,8 +39,8 @@ type Stack struct { } // StackWrite writes formatted stacks using the Context -func StackWrite(ctx Context, stacks []*Stack) error { - render := func(format func(subContext subContext) error) error { +func StackWrite(ctx formatter.Context, stacks []*Stack) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, stack := range stacks { if err := format(&stackContext{s: stack}); err != nil { return err @@ -41,14 +52,14 @@ func StackWrite(ctx Context, stacks []*Stack) error { } type stackContext struct { - HeaderContext + formatter.HeaderContext s *Stack } func newStackContext() *stackContext { stackCtx := stackContext{} - stackCtx.header = map[string]string{ - "Name": nameHeader, + stackCtx.Header = formatter.SubHeaderContext{ + "Name": formatter.NameHeader, "Services": stackServicesHeader, "Orchestrator": stackOrchestrastorHeader, "Namespace": stackNamespaceHeader, @@ -57,7 +68,7 @@ func newStackContext() *stackContext { } func (s *stackContext) MarshalJSON() ([]byte, error) { - return marshalJSON(s) + return formatter.MarshalJSON(s) } func (s *stackContext) Name() string { diff --git a/cli/command/formatter/stack_test.go b/cli/command/stack/formatter/formatter_test.go similarity index 76% rename from cli/command/formatter/stack_test.go rename to cli/command/stack/formatter/formatter_test.go index 44a08406c3..0f2550ea7a 100644 --- a/cli/command/formatter/stack_test.go +++ b/cli/command/stack/formatter/formatter_test.go @@ -4,29 +4,30 @@ import ( "bytes" "testing" + "github.com/docker/cli/cli/command/formatter" "gotest.tools/assert" is "gotest.tools/assert/cmp" ) func TestStackContextWrite(t *testing.T) { cases := []struct { - context Context + context formatter.Context expected string }{ // Errors { - Context{Format: "{{InvalidFunction}}"}, + formatter.Context{Format: "{{InvalidFunction}}"}, `Template parsing error: template: :1: function "InvalidFunction" not defined `, }, { - Context{Format: "{{nil}}"}, + formatter.Context{Format: "{{nil}}"}, `Template parsing error: template: :1:2: executing "" at : nil is not a command `, }, // Table format { - Context{Format: Format(SwarmStackTableFormat)}, + formatter.Context{Format: formatter.Format(SwarmStackTableFormat)}, `NAME SERVICES ORCHESTRATOR baz 2 orchestrator1 bar 1 orchestrator2 @@ -34,14 +35,14 @@ bar 1 orchestrator2 }, // Kubernetes table format adds Namespace column { - Context{Format: Format(KubernetesStackTableFormat)}, + formatter.Context{Format: formatter.Format(KubernetesStackTableFormat)}, `NAME SERVICES ORCHESTRATOR NAMESPACE baz 2 orchestrator1 namespace1 bar 1 orchestrator2 namespace2 `, }, { - Context{Format: Format("table {{.Name}}")}, + formatter.Context{Format: formatter.Format("table {{.Name}}")}, `NAME baz bar @@ -49,7 +50,7 @@ bar }, // Custom Format { - Context{Format: Format("{{.Name}}")}, + formatter.Context{Format: formatter.Format("{{.Name}}")}, `baz bar `, diff --git a/cli/command/stack/kubernetes/conversion.go b/cli/command/stack/kubernetes/conversion.go index 83986661ec..cccfd88f8e 100644 --- a/cli/command/stack/kubernetes/conversion.go +++ b/cli/command/stack/kubernetes/conversion.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/cli/command/service" "github.com/docker/cli/kubernetes/labels" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" @@ -154,16 +154,16 @@ const ( publishedOnRandomPortSuffix = "-random-ports" ) -func convertToServices(replicas *appsv1beta2.ReplicaSetList, daemons *appsv1beta2.DaemonSetList, services *apiv1.ServiceList) ([]swarm.Service, map[string]formatter.ServiceListInfo, error) { +func convertToServices(replicas *appsv1beta2.ReplicaSetList, daemons *appsv1beta2.DaemonSetList, services *apiv1.ServiceList) ([]swarm.Service, map[string]service.ListInfo, error) { result := make([]swarm.Service, len(replicas.Items)) - infos := make(map[string]formatter.ServiceListInfo, len(replicas.Items)+len(daemons.Items)) + infos := make(map[string]service.ListInfo, len(replicas.Items)+len(daemons.Items)) for i, r := range replicas.Items { s, err := convertToService(r.Labels[labels.ForServiceName], services, r.Spec.Template.Spec.Containers) if err != nil { return nil, nil, err } result[i] = *s - infos[s.ID] = formatter.ServiceListInfo{ + infos[s.ID] = service.ListInfo{ Mode: "replicated", Replicas: fmt.Sprintf("%d/%d", r.Status.AvailableReplicas, r.Status.Replicas), } @@ -174,7 +174,7 @@ func convertToServices(replicas *appsv1beta2.ReplicaSetList, daemons *appsv1beta return nil, nil, err } result = append(result, *s) - infos[s.ID] = formatter.ServiceListInfo{ + infos[s.ID] = service.ListInfo{ Mode: "global", Replicas: fmt.Sprintf("%d/%d", d.Status.NumberReady, d.Status.DesiredNumberScheduled), } diff --git a/cli/command/stack/kubernetes/conversion_test.go b/cli/command/stack/kubernetes/conversion_test.go index 213e9ea60e..8545adaa39 100644 --- a/cli/command/stack/kubernetes/conversion_test.go +++ b/cli/command/stack/kubernetes/conversion_test.go @@ -3,7 +3,7 @@ package kubernetes import ( "testing" - "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/cli/command/service" "github.com/docker/cli/kubernetes/labels" "github.com/docker/docker/api/types/swarm" "gotest.tools/assert" @@ -28,7 +28,7 @@ func TestKubernetesServiceToSwarmServiceConversion(t *testing.T) { replicas *appsv1beta2.ReplicaSetList services *apiv1.ServiceList expectedServices []swarm.Service - expectedListInfo map[string]formatter.ServiceListInfo + expectedListInfo map[string]service.ListInfo }{ // Match replicas with headless stack services { @@ -49,7 +49,7 @@ func TestKubernetesServiceToSwarmServiceConversion(t *testing.T) { makeSwarmService("stack_service1", "uid1", nil), makeSwarmService("stack_service2", "uid2", nil), }, - map[string]formatter.ServiceListInfo{ + map[string]service.ListInfo{ "uid1": {Mode: "replicated", Replicas: "2/5"}, "uid2": {Mode: "replicated", Replicas: "3/3"}, }, @@ -83,7 +83,7 @@ func TestKubernetesServiceToSwarmServiceConversion(t *testing.T) { }, }), }, - map[string]formatter.ServiceListInfo{ + map[string]service.ListInfo{ "uid1": {Mode: "replicated", Replicas: "1/1"}, }, }, @@ -117,7 +117,7 @@ func TestKubernetesServiceToSwarmServiceConversion(t *testing.T) { }, }), }, - map[string]formatter.ServiceListInfo{ + map[string]service.ListInfo{ "uid1": {Mode: "replicated", Replicas: "1/1"}, }, }, diff --git a/cli/command/stack/kubernetes/list.go b/cli/command/stack/kubernetes/list.go index 240f4aa041..9706f4be56 100644 --- a/cli/command/stack/kubernetes/list.go +++ b/cli/command/stack/kubernetes/list.go @@ -8,7 +8,7 @@ import ( "net/url" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/cli/command/stack/formatter" "github.com/docker/cli/cli/command/stack/options" "github.com/docker/cli/cli/config/configfile" "github.com/pkg/errors" diff --git a/cli/command/stack/kubernetes/ps.go b/cli/command/stack/kubernetes/ps.go index a4ee14f636..c8a0600d64 100644 --- a/cli/command/stack/kubernetes/ps.go +++ b/cli/command/stack/kubernetes/ps.go @@ -5,7 +5,7 @@ import ( "sort" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/cli/command/stack/formatter" "github.com/docker/cli/cli/command/stack/options" "github.com/docker/cli/cli/command/task" "github.com/docker/docker/api/types/swarm" @@ -83,11 +83,11 @@ func printTasks(dockerCli command.Cli, options options.PS, namespace string, cli tasksCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewTaskFormat(format, options.Quiet), + Format: task.NewTaskFormat(format, options.Quiet), Trunc: !options.NoTrunc, } - return formatter.TaskWrite(tasksCtx, tasks, names, nodes) + return task.FormatWrite(tasksCtx, tasks, names, nodes) } func resolveNode(name string, nodes *apiv1.NodeList, noResolve bool) (string, error) { diff --git a/cli/command/stack/kubernetes/services.go b/cli/command/stack/kubernetes/services.go index d5dbfb421c..d237e79728 100644 --- a/cli/command/stack/kubernetes/services.go +++ b/cli/command/stack/kubernetes/services.go @@ -4,7 +4,8 @@ import ( "fmt" "strings" - "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/cli/command/service" + "github.com/docker/cli/cli/command/stack/formatter" "github.com/docker/cli/cli/command/stack/options" "github.com/docker/cli/kubernetes/labels" "github.com/docker/docker/api/types/filters" @@ -115,7 +116,7 @@ func RunServices(dockerCli *KubeCli, opts options.Services) error { services = filterServicesByName(services, filters.Get("name"), stackName) if opts.Quiet { - info = map[string]formatter.ServiceListInfo{} + info = map[string]service.ListInfo{} } format := opts.Format @@ -129,9 +130,9 @@ func RunServices(dockerCli *KubeCli, opts options.Services) error { servicesCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewServiceListFormat(format, opts.Quiet), + Format: service.NewListFormat(format, opts.Quiet), } - return formatter.ServiceListWrite(servicesCtx, services, info) + return service.ListFormatWrite(servicesCtx, services, info) } func filterServicesByName(services []swarm.Service, names []string, stackName string) []swarm.Service { diff --git a/cli/command/stack/list.go b/cli/command/stack/list.go index a0aa3d1cf5..bc06926e73 100644 --- a/cli/command/stack/list.go +++ b/cli/command/stack/list.go @@ -5,7 +5,7 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/cli/command/stack/formatter" "github.com/docker/cli/cli/command/stack/kubernetes" "github.com/docker/cli/cli/command/stack/options" "github.com/docker/cli/cli/command/stack/swarm" diff --git a/cli/command/stack/swarm/list.go b/cli/command/stack/swarm/list.go index c0c19d0a56..817275e8be 100644 --- a/cli/command/stack/swarm/list.go +++ b/cli/command/stack/swarm/list.go @@ -4,7 +4,7 @@ import ( "context" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/formatter" + "github.com/docker/cli/cli/command/stack/formatter" "github.com/docker/cli/cli/compose/convert" "github.com/docker/docker/api/types" "github.com/pkg/errors" diff --git a/cli/command/stack/swarm/services.go b/cli/command/stack/swarm/services.go index 07b990adc8..cd7208f8b7 100644 --- a/cli/command/stack/swarm/services.go +++ b/cli/command/stack/swarm/services.go @@ -5,8 +5,8 @@ import ( "fmt" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/service" + "github.com/docker/cli/cli/command/stack/formatter" "github.com/docker/cli/cli/command/stack/options" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" @@ -29,7 +29,7 @@ func RunServices(dockerCli command.Cli, opts options.Services) error { return nil } - info := map[string]formatter.ServiceListInfo{} + info := map[string]service.ListInfo{} if !opts.Quiet { taskFilter := filters.NewArgs() for _, service := range services { @@ -60,7 +60,7 @@ func RunServices(dockerCli command.Cli, opts options.Services) error { servicesCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewServiceListFormat(format, opts.Quiet), + Format: service.NewListFormat(format, opts.Quiet), } - return formatter.ServiceListWrite(servicesCtx, services, info) + return service.ListFormatWrite(servicesCtx, services, info) } diff --git a/cli/command/formatter/task.go b/cli/command/task/formatter.go similarity index 81% rename from cli/command/formatter/task.go rename to cli/command/task/formatter.go index 6172b320ef..0958a1e89e 100644 --- a/cli/command/formatter/task.go +++ b/cli/command/task/formatter.go @@ -1,4 +1,4 @@ -package formatter +package task import ( "fmt" @@ -6,6 +6,7 @@ import ( "time" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/distribution/reference" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/stringid" @@ -25,25 +26,25 @@ const ( ) // NewTaskFormat returns a Format for rendering using a task Context -func NewTaskFormat(source string, quiet bool) Format { +func NewTaskFormat(source string, quiet bool) formatter.Format { switch source { - case TableFormatKey: + case formatter.TableFormatKey: if quiet { - return defaultQuietFormat + return formatter.DefaultQuietFormat } return defaultTaskTableFormat - case RawFormatKey: + case formatter.RawFormatKey: if quiet { return `id: {{.ID}}` } return `id: {{.ID}}\nname: {{.Name}}\nimage: {{.Image}}\nnode: {{.Node}}\ndesired_state: {{.DesiredState}}\ncurrent_state: {{.CurrentState}}\nerror: {{.Error}}\nports: {{.Ports}}\n` } - return Format(source) + return formatter.Format(source) } -// TaskWrite writes the context -func TaskWrite(ctx Context, tasks []swarm.Task, names map[string]string, nodes map[string]string) error { - render := func(format func(subContext subContext) error) error { +// FormatWrite writes the context +func FormatWrite(ctx formatter.Context, tasks []swarm.Task, names map[string]string, nodes map[string]string) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, task := range tasks { taskCtx := &taskContext{trunc: ctx.Trunc, task: task, name: names[task.ID], node: nodes[task.ID]} if err := format(taskCtx); err != nil { @@ -53,23 +54,21 @@ func TaskWrite(ctx Context, tasks []swarm.Task, names map[string]string, nodes m return nil } taskCtx := taskContext{} - taskCtx.header = taskHeaderContext{ + taskCtx.Header = formatter.SubHeaderContext{ "ID": taskIDHeader, - "Name": nameHeader, - "Image": imageHeader, + "Name": formatter.NameHeader, + "Image": formatter.ImageHeader, "Node": nodeHeader, "DesiredState": desiredStateHeader, "CurrentState": currentStateHeader, "Error": errorHeader, - "Ports": portsHeader, + "Ports": formatter.PortsHeader, } return ctx.Write(&taskCtx, render) } -type taskHeaderContext map[string]string - type taskContext struct { - HeaderContext + formatter.HeaderContext trunc bool task swarm.Task name string @@ -77,7 +76,7 @@ type taskContext struct { } func (c *taskContext) MarshalJSON() ([]byte, error) { - return marshalJSON(c) + return formatter.MarshalJSON(c) } func (c *taskContext) ID() string { diff --git a/cli/command/formatter/task_test.go b/cli/command/task/formatter_test.go similarity index 70% rename from cli/command/formatter/task_test.go rename to cli/command/task/formatter_test.go index 84bdbfeb97..2de2acc4e5 100644 --- a/cli/command/formatter/task_test.go +++ b/cli/command/task/formatter_test.go @@ -1,4 +1,4 @@ -package formatter +package task import ( "bytes" @@ -6,6 +6,7 @@ import ( "strings" "testing" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/docker/api/types/swarm" "gotest.tools/assert" is "gotest.tools/assert/cmp" @@ -14,44 +15,44 @@ import ( func TestTaskContextWrite(t *testing.T) { cases := []struct { - context Context + context formatter.Context expected string }{ { - Context{Format: "{{InvalidFunction}}"}, + formatter.Context{Format: "{{InvalidFunction}}"}, `Template parsing error: template: :1: function "InvalidFunction" not defined `, }, { - Context{Format: "{{nil}}"}, + formatter.Context{Format: "{{nil}}"}, `Template parsing error: template: :1:2: executing "" at : nil is not a command `, }, { - Context{Format: NewTaskFormat("table", true)}, + formatter.Context{Format: NewTaskFormat("table", true)}, `taskID1 taskID2 `, }, { - Context{Format: NewTaskFormat("table {{.Name}}\t{{.Node}}\t{{.Ports}}", false)}, + formatter.Context{Format: NewTaskFormat("table {{.Name}}\t{{.Node}}\t{{.Ports}}", false)}, string(golden.Get(t, "task-context-write-table-custom.golden")), }, { - Context{Format: NewTaskFormat("table {{.Name}}", true)}, + formatter.Context{Format: NewTaskFormat("table {{.Name}}", true)}, `NAME foobar_baz foobar_bar `, }, { - Context{Format: NewTaskFormat("raw", true)}, + formatter.Context{Format: NewTaskFormat("raw", true)}, `id: taskID1 id: taskID2 `, }, { - Context{Format: NewTaskFormat("{{.Name}} {{.Node}}", false)}, + formatter.Context{Format: NewTaskFormat("{{.Name}} {{.Node}}", false)}, `foobar_baz foo1 foobar_bar foo2 `, @@ -73,7 +74,7 @@ foobar_bar foo2 } out := bytes.NewBufferString("") testcase.context.Output = out - err := TaskWrite(testcase.context, tasks, names, nodes) + err := FormatWrite(testcase.context, tasks, names, nodes) if err != nil { assert.Error(t, err, testcase.expected) } else { @@ -92,7 +93,7 @@ func TestTaskContextWriteJSONField(t *testing.T) { "taskID2": "foobar_bar", } out := bytes.NewBufferString("") - err := TaskWrite(Context{Format: "{{json .ID}}", Output: out}, tasks, names, map[string]string{}) + err := FormatWrite(formatter.Context{Format: "{{json .ID}}", Output: out}, tasks, names, map[string]string{}) if err != nil { t.Fatal(err) } diff --git a/cli/command/task/print.go b/cli/command/task/print.go index 0f430e509e..761a5e8fa5 100644 --- a/cli/command/task/print.go +++ b/cli/command/task/print.go @@ -43,7 +43,7 @@ func Print(ctx context.Context, dockerCli command.Cli, tasks []swarm.Task, resol tasksCtx := formatter.Context{ Output: dockerCli.Out(), - Format: formatter.NewTaskFormat(format, quiet), + Format: NewTaskFormat(format, quiet), Trunc: trunc, } @@ -80,7 +80,7 @@ func Print(ctx context.Context, dockerCli command.Cli, tasks []swarm.Task, resol nodes[task.ID] = nodeValue } - return formatter.TaskWrite(tasksCtx, tasks, names, nodes) + return FormatWrite(tasksCtx, tasks, names, nodes) } // DefaultFormat returns the default format from the config file, or table diff --git a/cli/command/formatter/testdata/task-context-write-table-custom.golden b/cli/command/task/testdata/task-context-write-table-custom.golden similarity index 100% rename from cli/command/formatter/testdata/task-context-write-table-custom.golden rename to cli/command/task/testdata/task-context-write-table-custom.golden diff --git a/cli/command/formatter/trust.go b/cli/command/trust/formatter.go similarity index 81% rename from cli/command/formatter/trust.go rename to cli/command/trust/formatter.go index 42f9d4509c..5cf9e9d335 100644 --- a/cli/command/formatter/trust.go +++ b/cli/command/trust/formatter.go @@ -1,9 +1,10 @@ -package formatter +package trust import ( "sort" "strings" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/docker/pkg/stringid" ) @@ -36,18 +37,18 @@ type SignerInfo struct { } // NewTrustTagFormat returns a Format for rendering using a trusted tag Context -func NewTrustTagFormat() Format { +func NewTrustTagFormat() formatter.Format { return defaultTrustTagTableFormat } // NewSignerInfoFormat returns a Format for rendering a signer role info Context -func NewSignerInfoFormat() Format { +func NewSignerInfoFormat() formatter.Format { return defaultSignerInfoTableFormat } -// TrustTagWrite writes the context -func TrustTagWrite(ctx Context, signedTagInfoList []SignedTagInfo) error { - render := func(format func(subContext subContext) error) error { +// TagWrite writes the context +func TagWrite(ctx formatter.Context, signedTagInfoList []SignedTagInfo) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, signedTag := range signedTagInfoList { if err := format(&trustTagContext{s: signedTag}); err != nil { return err @@ -56,7 +57,7 @@ func TrustTagWrite(ctx Context, signedTagInfoList []SignedTagInfo) error { return nil } trustTagCtx := trustTagContext{} - trustTagCtx.header = trustTagHeaderContext{ + trustTagCtx.Header = formatter.SubHeaderContext{ "SignedTag": signedTagNameHeader, "Digest": trustedDigestHeader, "Signers": signersHeader, @@ -64,10 +65,8 @@ func TrustTagWrite(ctx Context, signedTagInfoList []SignedTagInfo) error { return ctx.Write(&trustTagCtx, render) } -type trustTagHeaderContext map[string]string - type trustTagContext struct { - HeaderContext + formatter.HeaderContext s SignedTagInfo } @@ -88,8 +87,8 @@ func (c *trustTagContext) Signers() string { } // SignerInfoWrite writes the context -func SignerInfoWrite(ctx Context, signerInfoList []SignerInfo) error { - render := func(format func(subContext subContext) error) error { +func SignerInfoWrite(ctx formatter.Context, signerInfoList []SignerInfo) error { + render := func(format func(subContext formatter.SubContext) error) error { for _, signerInfo := range signerInfoList { if err := format(&signerInfoContext{ trunc: ctx.Trunc, @@ -101,17 +100,15 @@ func SignerInfoWrite(ctx Context, signerInfoList []SignerInfo) error { return nil } signerInfoCtx := signerInfoContext{} - signerInfoCtx.header = signerInfoHeaderContext{ + signerInfoCtx.Header = formatter.SubHeaderContext{ "Signer": signerNameHeader, "Keys": keysHeader, } return ctx.Write(&signerInfoCtx, render) } -type signerInfoHeaderContext map[string]string - type signerInfoContext struct { - HeaderContext + formatter.HeaderContext trunc bool s SignerInfo } diff --git a/cli/command/formatter/trust_test.go b/cli/command/trust/formatter_test.go similarity index 89% rename from cli/command/formatter/trust_test.go rename to cli/command/trust/formatter_test.go index c84ecd2951..300f6c8c20 100644 --- a/cli/command/formatter/trust_test.go +++ b/cli/command/trust/formatter_test.go @@ -1,9 +1,10 @@ -package formatter +package trust import ( "bytes" "testing" + "github.com/docker/cli/cli/command/formatter" "github.com/docker/docker/pkg/stringid" "gotest.tools/assert" is "gotest.tools/assert/cmp" @@ -86,19 +87,19 @@ func TestTrustTag(t *testing.T) { func TestTrustTagContextWrite(t *testing.T) { cases := []struct { - context Context + context formatter.Context expected string }{ // Errors { - Context{ + formatter.Context{ Format: "{{InvalidFunction}}", }, `Template parsing error: template: :1: function "InvalidFunction" not defined `, }, { - Context{ + formatter.Context{ Format: "{{nil}}", }, `Template parsing error: template: :1:2: executing "" at : nil is not a command @@ -106,7 +107,7 @@ func TestTrustTagContextWrite(t *testing.T) { }, // Table Format { - Context{ + formatter.Context{ Format: NewTrustTagFormat(), }, `SIGNED TAG DIGEST SIGNERS @@ -125,7 +126,7 @@ tag3 bbbbbbbb } out := bytes.NewBufferString("") testcase.context.Output = out - err := TrustTagWrite(testcase.context, signedTags) + err := TagWrite(testcase.context, signedTags) if err != nil { assert.Error(t, err, testcase.expected) } else { @@ -134,15 +135,15 @@ tag3 bbbbbbbb } } -// With no trust data, the TrustTagWrite will print an empty table: +// With no trust data, the TagWrite will print an empty table: // it's up to the caller to decide whether or not to print this versus an error func TestTrustTagContextEmptyWrite(t *testing.T) { emptyCase := struct { - context Context + context formatter.Context expected string }{ - Context{ + formatter.Context{ Format: NewTrustTagFormat(), }, `SIGNED TAG DIGEST SIGNERS @@ -152,17 +153,17 @@ func TestTrustTagContextEmptyWrite(t *testing.T) { emptySignedTags := []SignedTagInfo{} out := bytes.NewBufferString("") emptyCase.context.Output = out - err := TrustTagWrite(emptyCase.context, emptySignedTags) + err := TagWrite(emptyCase.context, emptySignedTags) assert.NilError(t, err) assert.Check(t, is.Equal(emptyCase.expected, out.String())) } func TestSignerInfoContextEmptyWrite(t *testing.T) { emptyCase := struct { - context Context + context formatter.Context expected string }{ - Context{ + formatter.Context{ Format: NewSignerInfoFormat(), }, `SIGNER KEYS @@ -178,19 +179,19 @@ func TestSignerInfoContextEmptyWrite(t *testing.T) { func TestSignerInfoContextWrite(t *testing.T) { cases := []struct { - context Context + context formatter.Context expected string }{ // Errors { - Context{ + formatter.Context{ Format: "{{InvalidFunction}}", }, `Template parsing error: template: :1: function "InvalidFunction" not defined `, }, { - Context{ + formatter.Context{ Format: "{{nil}}", }, `Template parsing error: template: :1:2: executing "" at : nil is not a command @@ -198,7 +199,7 @@ func TestSignerInfoContextWrite(t *testing.T) { }, // Table Format { - Context{ + formatter.Context{ Format: NewSignerInfoFormat(), Trunc: true, }, @@ -210,7 +211,7 @@ eve foobarbazqux, key31, key32 }, // No truncation { - Context{ + formatter.Context{ Format: NewSignerInfoFormat(), }, `SIGNER KEYS diff --git a/cli/command/trust/inspect_pretty.go b/cli/command/trust/inspect_pretty.go index 24c811b904..dbe0f795e2 100644 --- a/cli/command/trust/inspect_pretty.go +++ b/cli/command/trust/inspect_pretty.go @@ -55,33 +55,33 @@ func printSortedAdminKeys(out io.Writer, adminRoles []client.RoleWithSignatures) func printSignatures(out io.Writer, signatureRows []trustTagRow) error { trustTagCtx := formatter.Context{ Output: out, - Format: formatter.NewTrustTagFormat(), + Format: NewTrustTagFormat(), } // convert the formatted type before printing - formattedTags := []formatter.SignedTagInfo{} + formattedTags := []SignedTagInfo{} for _, sigRow := range signatureRows { formattedSigners := sigRow.Signers if len(formattedSigners) == 0 { formattedSigners = append(formattedSigners, fmt.Sprintf("(%s)", releasedRoleName)) } - formattedTags = append(formattedTags, formatter.SignedTagInfo{ + formattedTags = append(formattedTags, SignedTagInfo{ Name: sigRow.SignedTag, Digest: sigRow.Digest, Signers: formattedSigners, }) } - return formatter.TrustTagWrite(trustTagCtx, formattedTags) + return TagWrite(trustTagCtx, formattedTags) } func printSignerInfo(out io.Writer, roleToKeyIDs map[string][]string) error { signerInfoCtx := formatter.Context{ Output: out, - Format: formatter.NewSignerInfoFormat(), + Format: NewSignerInfoFormat(), Trunc: true, } - formattedSignerInfo := []formatter.SignerInfo{} + formattedSignerInfo := []SignerInfo{} for name, keyIDs := range roleToKeyIDs { - formattedSignerInfo = append(formattedSignerInfo, formatter.SignerInfo{ + formattedSignerInfo = append(formattedSignerInfo, SignerInfo{ Name: name, Keys: keyIDs, }) @@ -89,5 +89,5 @@ func printSignerInfo(out io.Writer, roleToKeyIDs map[string][]string) error { sort.Slice(formattedSignerInfo, func(i, j int) bool { return sortorder.NaturalLess(formattedSignerInfo[i].Name, formattedSignerInfo[j].Name) }) - return formatter.SignerInfoWrite(signerInfoCtx, formattedSignerInfo) + return SignerInfoWrite(signerInfoCtx, formattedSignerInfo) } diff --git a/internal/test/strings.go b/internal/test/strings.go new file mode 100644 index 0000000000..c0c952e6a5 --- /dev/null +++ b/internal/test/strings.go @@ -0,0 +1,29 @@ +package test + +import ( + "strings" + "testing" + + "gotest.tools/assert" + is "gotest.tools/assert/cmp" +) + +// CompareMultipleValues compares comma-separated values, whatever the order is +func CompareMultipleValues(t *testing.T, value, expected string) { + // comma-separated values means probably a map input, which won't + // be guaranteed to have the same order as our expected value + // We'll create maps and use reflect.DeepEquals to check instead: + entriesMap := make(map[string]string) + expMap := make(map[string]string) + entries := strings.Split(value, ",") + expectedEntries := strings.Split(expected, ",") + for _, entry := range entries { + keyval := strings.Split(entry, "=") + entriesMap[keyval[0]] = keyval[1] + } + for _, expected := range expectedEntries { + keyval := strings.Split(expected, "=") + expMap[keyval[0]] = keyval[1] + } + assert.Check(t, is.DeepEqual(expMap, entriesMap)) +}