Merge pull request #1369 from vdemeester/formatter-refacto

formatter package heavy refactoring
This commit is contained in:
Silvin Lubecki 2018-10-26 10:43:13 +01:00 committed by GitHub
commit 0904fbfc77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 750 additions and 711 deletions

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)
}

View File

@ -1,4 +1,4 @@
package formatter
package config
import (
"fmt"
@ -6,6 +6,7 @@ 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"
@ -16,7 +17,7 @@ const (
configIDHeader = "ID"
configCreatedHeader = "CREATED"
configUpdatedHeader = "UPDATED"
configInspectPrettyTemplate Format = `ID: {{.ID}}
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 {

View File

@ -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>: 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))

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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("--")
}

View File

@ -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>: 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()

View File

@ -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 {

View File

@ -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,

View File

@ -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 {

View File

@ -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,

View File

@ -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 {

View File

@ -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>: 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)
}

View File

@ -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 {

View File

@ -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>: 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)
}

View File

@ -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 {

View File

@ -16,12 +16,9 @@ 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"
@ -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 {

View File

@ -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 {

View File

@ -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)
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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,

View File

@ -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)
}

View File

@ -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 {

View File

@ -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>: 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)
}

View File

@ -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)
}

View File

@ -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"
@ -15,7 +16,7 @@ import (
const (
defaultNodeTableFormat = "table {{.ID}} {{if .Self}}*{{else}} {{ end }}\t{{.Hostname}}\t{{.Status}}\t{{.Availability}}\t{{.ManagerStatus}}\t{{.EngineVersion}}"
nodeInspectPrettyTemplate Format = `ID: {{.ID}}
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 {

View File

@ -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>: 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 {

View File

@ -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

View File

@ -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)
}

View File

@ -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"
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

View File

@ -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>: 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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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>: 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)
}

View File

@ -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))
}

View File

@ -1,4 +1,4 @@
package formatter
package secret
import (
"fmt"
@ -6,6 +6,7 @@ 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"
@ -16,7 +17,7 @@ const (
secretIDHeader = "ID"
secretCreatedHeader = "CREATED"
secretUpdatedHeader = "UPDATED"
secretInspectPrettyTemplate Format = `ID: {{.ID}}
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 {

View File

@ -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>: 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()))

View File

@ -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

View File

@ -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)
}

View File

@ -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 {

View File

@ -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>: 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)
}

View File

@ -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

View File

@ -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")

View File

@ -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]),
}

View File

@ -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 {

View File

@ -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>: 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
`,

View File

@ -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),
}

View File

@ -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"},
},
},

View File

@ -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"

View File

@ -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) {

View File

@ -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 {

View File

@ -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"

View File

@ -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"

View File

@ -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)
}

View File

@ -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 {

View File

@ -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>: 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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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>: 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>: 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

View File

@ -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)
}

29
internal/test/strings.go Normal file
View File

@ -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))
}