mirror of https://github.com/docker/cli.git
formatter package heavy refactoring
- make it possible to extract the formatter implementation from the "common" code, that way, the formatter package stays small - extract some formatter into their own packages This is essentially moving the "formatter" implementation of each type in their respective packages. The *main* reason to do that, is to be able to depend on `cli/command/formatter` without depending of the implementation detail of the formatter. As of now, depending on `cli/command/formatter` means we depend on `docker/docker/api/types`, `docker/licensing`, … — that should not be the case. `formatter` should hold the common code (or helpers) to easily create formatter, not all formatter implementations. Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
parent
ea836abed5
commit
69fdd2a4ad
|
@ -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 {
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package formatter
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -6,17 +6,18 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/cli/command/inspect"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultConfigTableFormat = "table {{.ID}}\t{{.Name}}\t{{.CreatedAt}}\t{{.UpdatedAt}}"
|
||||
configIDHeader = "ID"
|
||||
configCreatedHeader = "CREATED"
|
||||
configUpdatedHeader = "UPDATED"
|
||||
configInspectPrettyTemplate Format = `ID: {{.ID}}
|
||||
defaultConfigTableFormat = "table {{.ID}}\t{{.Name}}\t{{.CreatedAt}}\t{{.UpdatedAt}}"
|
||||
configIDHeader = "ID"
|
||||
configCreatedHeader = "CREATED"
|
||||
configUpdatedHeader = "UPDATED"
|
||||
configInspectPrettyTemplate formatter.Format = `ID: {{.ID}}
|
||||
Name: {{.Name}}
|
||||
{{- if .Labels }}
|
||||
Labels:
|
||||
|
@ -29,23 +30,23 @@ Data:
|
|||
{{.Data}}`
|
||||
)
|
||||
|
||||
// NewConfigFormat returns a Format for rendering using a config Context
|
||||
func NewConfigFormat(source string, quiet bool) Format {
|
||||
// NewFormat returns a Format for rendering using a config Context
|
||||
func NewFormat(source string, quiet bool) formatter.Format {
|
||||
switch source {
|
||||
case PrettyFormatKey:
|
||||
case formatter.PrettyFormatKey:
|
||||
return configInspectPrettyTemplate
|
||||
case TableFormatKey:
|
||||
case formatter.TableFormatKey:
|
||||
if quiet {
|
||||
return defaultQuietFormat
|
||||
return formatter.DefaultQuietFormat
|
||||
}
|
||||
return defaultConfigTableFormat
|
||||
}
|
||||
return Format(source)
|
||||
return formatter.Format(source)
|
||||
}
|
||||
|
||||
// ConfigWrite writes the context
|
||||
func ConfigWrite(ctx Context, configs []swarm.Config) error {
|
||||
render := func(format func(subContext subContext) error) error {
|
||||
// FormatWrite writes the context
|
||||
func FormatWrite(ctx formatter.Context, configs []swarm.Config) error {
|
||||
render := func(format func(subContext formatter.SubContext) error) error {
|
||||
for _, config := range configs {
|
||||
configCtx := &configContext{c: config}
|
||||
if err := format(configCtx); err != nil {
|
||||
|
@ -60,23 +61,23 @@ func ConfigWrite(ctx Context, configs []swarm.Config) error {
|
|||
func newConfigContext() *configContext {
|
||||
cCtx := &configContext{}
|
||||
|
||||
cCtx.header = map[string]string{
|
||||
cCtx.Header = formatter.SubHeaderContext{
|
||||
"ID": configIDHeader,
|
||||
"Name": nameHeader,
|
||||
"Name": formatter.NameHeader,
|
||||
"CreatedAt": configCreatedHeader,
|
||||
"UpdatedAt": configUpdatedHeader,
|
||||
"Labels": labelsHeader,
|
||||
"Labels": formatter.LabelsHeader,
|
||||
}
|
||||
return cCtx
|
||||
}
|
||||
|
||||
type configContext struct {
|
||||
HeaderContext
|
||||
formatter.HeaderContext
|
||||
c swarm.Config
|
||||
}
|
||||
|
||||
func (c *configContext) MarshalJSON() ([]byte, error) {
|
||||
return marshalJSON(c)
|
||||
return formatter.MarshalJSON(c)
|
||||
}
|
||||
|
||||
func (c *configContext) ID() string {
|
||||
|
@ -114,12 +115,12 @@ func (c *configContext) Label(name string) string {
|
|||
return c.c.Spec.Annotations.Labels[name]
|
||||
}
|
||||
|
||||
// ConfigInspectWrite renders the context for a list of configs
|
||||
func ConfigInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) error {
|
||||
// InspectFormatWrite renders the context for a list of configs
|
||||
func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.GetRefFunc) error {
|
||||
if ctx.Format != configInspectPrettyTemplate {
|
||||
return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef)
|
||||
}
|
||||
render := func(format func(subContext subContext) error) error {
|
||||
render := func(format func(subContext formatter.SubContext) error) error {
|
||||
for _, ref := range refs {
|
||||
configI, _, err := getRef(ref)
|
||||
if err != nil {
|
||||
|
@ -140,7 +141,7 @@ func ConfigInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) e
|
|||
|
||||
type configInspectContext struct {
|
||||
swarm.Config
|
||||
subContext
|
||||
formatter.SubContext
|
||||
}
|
||||
|
||||
func (ctx *configInspectContext) ID() string {
|
|
@ -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))
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
|
@ -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 {
|
|
@ -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("--")
|
||||
}
|
|
@ -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()
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
|
@ -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)
|
||||
}
|
|
@ -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 {
|
|
@ -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)
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -16,15 +16,12 @@ import (
|
|||
const (
|
||||
defaultContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.RunningFor}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}"
|
||||
|
||||
containerIDHeader = "CONTAINER ID"
|
||||
namesHeader = "NAMES"
|
||||
commandHeader = "COMMAND"
|
||||
runningForHeader = "CREATED"
|
||||
statusHeader = "STATUS"
|
||||
portsHeader = "PORTS"
|
||||
mountsHeader = "MOUNTS"
|
||||
localVolumes = "LOCAL VOLUMES"
|
||||
networksHeader = "NETWORKS"
|
||||
namesHeader = "NAMES"
|
||||
commandHeader = "COMMAND"
|
||||
runningForHeader = "CREATED"
|
||||
mountsHeader = "MOUNTS"
|
||||
localVolumes = "LOCAL VOLUMES"
|
||||
networksHeader = "NETWORKS"
|
||||
)
|
||||
|
||||
// NewContainerFormat returns a Format for rendering using a Context
|
||||
|
@ -32,7 +29,7 @@ func NewContainerFormat(source string, quiet bool, size bool) Format {
|
|||
switch source {
|
||||
case TableFormatKey:
|
||||
if quiet {
|
||||
return defaultQuietFormat
|
||||
return DefaultQuietFormat
|
||||
}
|
||||
format := defaultContainerTableFormat
|
||||
if size {
|
||||
|
@ -62,7 +59,7 @@ ports: {{- pad .Ports 1 0}}
|
|||
|
||||
// ContainerWrite renders the context for a list of containers
|
||||
func ContainerWrite(ctx Context, containers []types.Container) error {
|
||||
render := func(format func(subContext subContext) error) error {
|
||||
render := func(format func(subContext SubContext) error) error {
|
||||
for _, container := range containers {
|
||||
err := format(&containerContext{trunc: ctx.Trunc, c: container})
|
||||
if err != nil {
|
||||
|
@ -74,16 +71,6 @@ func ContainerWrite(ctx Context, containers []types.Container) error {
|
|||
return ctx.Write(newContainerContext(), render)
|
||||
}
|
||||
|
||||
type containerHeaderContext map[string]string
|
||||
|
||||
func (c containerHeaderContext) Label(name string) string {
|
||||
n := strings.Split(name, ".")
|
||||
r := strings.NewReplacer("-", " ", "_", " ")
|
||||
h := r.Replace(n[len(n)-1])
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
type containerContext struct {
|
||||
HeaderContext
|
||||
trunc bool
|
||||
|
@ -92,17 +79,17 @@ type containerContext struct {
|
|||
|
||||
func newContainerContext() *containerContext {
|
||||
containerCtx := containerContext{}
|
||||
containerCtx.header = containerHeaderContext{
|
||||
"ID": containerIDHeader,
|
||||
containerCtx.Header = SubHeaderContext{
|
||||
"ID": ContainerIDHeader,
|
||||
"Names": namesHeader,
|
||||
"Image": imageHeader,
|
||||
"Image": ImageHeader,
|
||||
"Command": commandHeader,
|
||||
"CreatedAt": createdAtHeader,
|
||||
"CreatedAt": CreatedAtHeader,
|
||||
"RunningFor": runningForHeader,
|
||||
"Ports": portsHeader,
|
||||
"Status": statusHeader,
|
||||
"Size": sizeHeader,
|
||||
"Labels": labelsHeader,
|
||||
"Ports": PortsHeader,
|
||||
"Status": StatusHeader,
|
||||
"Size": SizeHeader,
|
||||
"Labels": LabelsHeader,
|
||||
"Mounts": mountsHeader,
|
||||
"LocalVolumes": localVolumes,
|
||||
"Networks": networksHeader,
|
||||
|
@ -111,7 +98,7 @@ func newContainerContext() *containerContext {
|
|||
}
|
||||
|
||||
func (c *containerContext) MarshalJSON() ([]byte, error) {
|
||||
return marshalJSON(c)
|
||||
return MarshalJSON(c)
|
||||
}
|
||||
|
||||
func (c *containerContext) ID() string {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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,
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package formatter
|
||||
package node
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/cli/command/inspect"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
|
@ -14,8 +15,8 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
defaultNodeTableFormat = "table {{.ID}} {{if .Self}}*{{else}} {{ end }}\t{{.Hostname}}\t{{.Status}}\t{{.Availability}}\t{{.ManagerStatus}}\t{{.EngineVersion}}"
|
||||
nodeInspectPrettyTemplate Format = `ID: {{.ID}}
|
||||
defaultNodeTableFormat = "table {{.ID}} {{if .Self}}*{{else}} {{ end }}\t{{.Hostname}}\t{{.Status}}\t{{.Availability}}\t{{.ManagerStatus}}\t{{.EngineVersion}}"
|
||||
nodeInspectPrettyTemplate formatter.Format = `ID: {{.ID}}
|
||||
{{- if .Name }}
|
||||
Name: {{.Name}}
|
||||
{{- end }}
|
||||
|
@ -79,28 +80,28 @@ TLS Info:
|
|||
tlsStatusHeader = "TLS STATUS"
|
||||
)
|
||||
|
||||
// NewNodeFormat returns a Format for rendering using a node Context
|
||||
func NewNodeFormat(source string, quiet bool) Format {
|
||||
// NewFormat returns a Format for rendering using a node Context
|
||||
func NewFormat(source string, quiet bool) formatter.Format {
|
||||
switch source {
|
||||
case PrettyFormatKey:
|
||||
case formatter.PrettyFormatKey:
|
||||
return nodeInspectPrettyTemplate
|
||||
case TableFormatKey:
|
||||
case formatter.TableFormatKey:
|
||||
if quiet {
|
||||
return defaultQuietFormat
|
||||
return formatter.DefaultQuietFormat
|
||||
}
|
||||
return defaultNodeTableFormat
|
||||
case RawFormatKey:
|
||||
case formatter.RawFormatKey:
|
||||
if quiet {
|
||||
return `node_id: {{.ID}}`
|
||||
}
|
||||
return `node_id: {{.ID}}\nhostname: {{.Hostname}}\nstatus: {{.Status}}\navailability: {{.Availability}}\nmanager_status: {{.ManagerStatus}}\n`
|
||||
}
|
||||
return Format(source)
|
||||
return formatter.Format(source)
|
||||
}
|
||||
|
||||
// NodeWrite writes the context
|
||||
func NodeWrite(ctx Context, nodes []swarm.Node, info types.Info) error {
|
||||
render := func(format func(subContext subContext) error) error {
|
||||
// FormatWrite writes the context
|
||||
func FormatWrite(ctx formatter.Context, nodes []swarm.Node, info types.Info) error {
|
||||
render := func(format func(subContext formatter.SubContext) error) error {
|
||||
for _, node := range nodes {
|
||||
nodeCtx := &nodeContext{n: node, info: info}
|
||||
if err := format(nodeCtx); err != nil {
|
||||
|
@ -109,31 +110,28 @@ func NodeWrite(ctx Context, nodes []swarm.Node, info types.Info) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
header := nodeHeaderContext{
|
||||
nodeCtx := nodeContext{}
|
||||
nodeCtx.Header = formatter.SubHeaderContext{
|
||||
"ID": nodeIDHeader,
|
||||
"Self": selfHeader,
|
||||
"Hostname": hostnameHeader,
|
||||
"Status": statusHeader,
|
||||
"Status": formatter.StatusHeader,
|
||||
"Availability": availabilityHeader,
|
||||
"ManagerStatus": managerStatusHeader,
|
||||
"EngineVersion": engineVersionHeader,
|
||||
"TLSStatus": tlsStatusHeader,
|
||||
}
|
||||
nodeCtx := nodeContext{}
|
||||
nodeCtx.header = header
|
||||
return ctx.Write(&nodeCtx, render)
|
||||
}
|
||||
|
||||
type nodeHeaderContext map[string]string
|
||||
|
||||
type nodeContext struct {
|
||||
HeaderContext
|
||||
formatter.HeaderContext
|
||||
n swarm.Node
|
||||
info types.Info
|
||||
}
|
||||
|
||||
func (c *nodeContext) MarshalJSON() ([]byte, error) {
|
||||
return marshalJSON(c)
|
||||
return formatter.MarshalJSON(c)
|
||||
}
|
||||
|
||||
func (c *nodeContext) ID() string {
|
||||
|
@ -182,12 +180,12 @@ func (c *nodeContext) EngineVersion() string {
|
|||
return c.n.Description.Engine.EngineVersion
|
||||
}
|
||||
|
||||
// NodeInspectWrite renders the context for a list of nodes
|
||||
func NodeInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) error {
|
||||
// InspectFormatWrite renders the context for a list of nodes
|
||||
func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.GetRefFunc) error {
|
||||
if ctx.Format != nodeInspectPrettyTemplate {
|
||||
return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef)
|
||||
}
|
||||
render := func(format func(subContext subContext) error) error {
|
||||
render := func(format func(subContext formatter.SubContext) error) error {
|
||||
for _, ref := range refs {
|
||||
nodeI, _, err := getRef(ref)
|
||||
if err != nil {
|
||||
|
@ -208,7 +206,7 @@ func NodeInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) err
|
|||
|
||||
type nodeInspectContext struct {
|
||||
swarm.Node
|
||||
subContext
|
||||
formatter.SubContext
|
||||
}
|
||||
|
||||
func (ctx *nodeInspectContext) ID() string {
|
|
@ -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 {
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package formatter
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
)
|
||||
|
@ -10,31 +11,30 @@ import (
|
|||
const (
|
||||
defaultPluginTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Description}}\t{{.Enabled}}"
|
||||
|
||||
pluginIDHeader = "ID"
|
||||
descriptionHeader = "DESCRIPTION"
|
||||
enabledHeader = "ENABLED"
|
||||
enabledHeader = "ENABLED"
|
||||
pluginIDHeader = "ID"
|
||||
)
|
||||
|
||||
// NewPluginFormat returns a Format for rendering using a plugin Context
|
||||
func NewPluginFormat(source string, quiet bool) Format {
|
||||
// NewFormat returns a Format for rendering using a plugin Context
|
||||
func NewFormat(source string, quiet bool) formatter.Format {
|
||||
switch source {
|
||||
case TableFormatKey:
|
||||
case formatter.TableFormatKey:
|
||||
if quiet {
|
||||
return defaultQuietFormat
|
||||
return formatter.DefaultQuietFormat
|
||||
}
|
||||
return defaultPluginTableFormat
|
||||
case RawFormatKey:
|
||||
case formatter.RawFormatKey:
|
||||
if quiet {
|
||||
return `plugin_id: {{.ID}}`
|
||||
}
|
||||
return `plugin_id: {{.ID}}\nname: {{.Name}}\ndescription: {{.Description}}\nenabled: {{.Enabled}}\n`
|
||||
}
|
||||
return Format(source)
|
||||
return formatter.Format(source)
|
||||
}
|
||||
|
||||
// PluginWrite writes the context
|
||||
func PluginWrite(ctx Context, plugins []*types.Plugin) error {
|
||||
render := func(format func(subContext subContext) error) error {
|
||||
// FormatWrite writes the context
|
||||
func FormatWrite(ctx formatter.Context, plugins []*types.Plugin) error {
|
||||
render := func(format func(subContext formatter.SubContext) error) error {
|
||||
for _, plugin := range plugins {
|
||||
pluginCtx := &pluginContext{trunc: ctx.Trunc, p: *plugin}
|
||||
if err := format(pluginCtx); err != nil {
|
||||
|
@ -44,24 +44,24 @@ func PluginWrite(ctx Context, plugins []*types.Plugin) error {
|
|||
return nil
|
||||
}
|
||||
pluginCtx := pluginContext{}
|
||||
pluginCtx.header = map[string]string{
|
||||
pluginCtx.Header = formatter.SubHeaderContext{
|
||||
"ID": pluginIDHeader,
|
||||
"Name": nameHeader,
|
||||
"Description": descriptionHeader,
|
||||
"Name": formatter.NameHeader,
|
||||
"Description": formatter.DescriptionHeader,
|
||||
"Enabled": enabledHeader,
|
||||
"PluginReference": imageHeader,
|
||||
"PluginReference": formatter.ImageHeader,
|
||||
}
|
||||
return ctx.Write(&pluginCtx, render)
|
||||
}
|
||||
|
||||
type pluginContext struct {
|
||||
HeaderContext
|
||||
formatter.HeaderContext
|
||||
trunc bool
|
||||
p types.Plugin
|
||||
}
|
||||
|
||||
func (c *pluginContext) MarshalJSON() ([]byte, error) {
|
||||
return marshalJSON(c)
|
||||
return formatter.MarshalJSON(c)
|
||||
}
|
||||
|
||||
func (c *pluginContext) ID() string {
|
||||
|
@ -79,7 +79,7 @@ func (c *pluginContext) Description() string {
|
|||
desc := strings.Replace(c.p.Config.Description, "\n", "", -1)
|
||||
desc = strings.Replace(desc, "\r", "", -1)
|
||||
if c.trunc {
|
||||
desc = Ellipsis(desc, 45)
|
||||
desc = formatter.Ellipsis(desc, 45)
|
||||
}
|
||||
|
||||
return desc
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package formatter
|
||||
package secret
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -6,17 +6,18 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/cli/command/inspect"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultSecretTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Driver}}\t{{.CreatedAt}}\t{{.UpdatedAt}}"
|
||||
secretIDHeader = "ID"
|
||||
secretCreatedHeader = "CREATED"
|
||||
secretUpdatedHeader = "UPDATED"
|
||||
secretInspectPrettyTemplate Format = `ID: {{.ID}}
|
||||
defaultSecretTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Driver}}\t{{.CreatedAt}}\t{{.UpdatedAt}}"
|
||||
secretIDHeader = "ID"
|
||||
secretCreatedHeader = "CREATED"
|
||||
secretUpdatedHeader = "UPDATED"
|
||||
secretInspectPrettyTemplate formatter.Format = `ID: {{.ID}}
|
||||
Name: {{.Name}}
|
||||
{{- if .Labels }}
|
||||
Labels:
|
||||
|
@ -28,23 +29,23 @@ Created at: {{.CreatedAt}}
|
|||
Updated at: {{.UpdatedAt}}`
|
||||
)
|
||||
|
||||
// NewSecretFormat returns a Format for rendering using a secret Context
|
||||
func NewSecretFormat(source string, quiet bool) Format {
|
||||
// NewFormat returns a Format for rendering using a secret Context
|
||||
func NewFormat(source string, quiet bool) formatter.Format {
|
||||
switch source {
|
||||
case PrettyFormatKey:
|
||||
case formatter.PrettyFormatKey:
|
||||
return secretInspectPrettyTemplate
|
||||
case TableFormatKey:
|
||||
case formatter.TableFormatKey:
|
||||
if quiet {
|
||||
return defaultQuietFormat
|
||||
return formatter.DefaultQuietFormat
|
||||
}
|
||||
return defaultSecretTableFormat
|
||||
}
|
||||
return Format(source)
|
||||
return formatter.Format(source)
|
||||
}
|
||||
|
||||
// SecretWrite writes the context
|
||||
func SecretWrite(ctx Context, secrets []swarm.Secret) error {
|
||||
render := func(format func(subContext subContext) error) error {
|
||||
// FormatWrite writes the context
|
||||
func FormatWrite(ctx formatter.Context, secrets []swarm.Secret) error {
|
||||
render := func(format func(subContext formatter.SubContext) error) error {
|
||||
for _, secret := range secrets {
|
||||
secretCtx := &secretContext{s: secret}
|
||||
if err := format(secretCtx); err != nil {
|
||||
|
@ -59,24 +60,24 @@ func SecretWrite(ctx Context, secrets []swarm.Secret) error {
|
|||
func newSecretContext() *secretContext {
|
||||
sCtx := &secretContext{}
|
||||
|
||||
sCtx.header = map[string]string{
|
||||
sCtx.Header = formatter.SubHeaderContext{
|
||||
"ID": secretIDHeader,
|
||||
"Name": nameHeader,
|
||||
"Driver": driverHeader,
|
||||
"Name": formatter.NameHeader,
|
||||
"Driver": formatter.DriverHeader,
|
||||
"CreatedAt": secretCreatedHeader,
|
||||
"UpdatedAt": secretUpdatedHeader,
|
||||
"Labels": labelsHeader,
|
||||
"Labels": formatter.LabelsHeader,
|
||||
}
|
||||
return sCtx
|
||||
}
|
||||
|
||||
type secretContext struct {
|
||||
HeaderContext
|
||||
formatter.HeaderContext
|
||||
s swarm.Secret
|
||||
}
|
||||
|
||||
func (c *secretContext) MarshalJSON() ([]byte, error) {
|
||||
return marshalJSON(c)
|
||||
return formatter.MarshalJSON(c)
|
||||
}
|
||||
|
||||
func (c *secretContext) ID() string {
|
||||
|
@ -121,12 +122,12 @@ func (c *secretContext) Label(name string) string {
|
|||
return c.s.Spec.Annotations.Labels[name]
|
||||
}
|
||||
|
||||
// SecretInspectWrite renders the context for a list of secrets
|
||||
func SecretInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) error {
|
||||
// InspectFormatWrite renders the context for a list of secrets
|
||||
func InspectFormatWrite(ctx formatter.Context, refs []string, getRef inspect.GetRefFunc) error {
|
||||
if ctx.Format != secretInspectPrettyTemplate {
|
||||
return inspect.Inspect(ctx.Output, refs, string(ctx.Format), getRef)
|
||||
}
|
||||
render := func(format func(subContext subContext) error) error {
|
||||
render := func(format func(subContext formatter.SubContext) error) error {
|
||||
for _, ref := range refs {
|
||||
secretI, _, err := getRef(ref)
|
||||
if err != nil {
|
||||
|
@ -147,7 +148,7 @@ func SecretInspectWrite(ctx Context, refs []string, getRef inspect.GetRefFunc) e
|
|||
|
||||
type secretInspectContext struct {
|
||||
swarm.Secret
|
||||
subContext
|
||||
formatter.SubContext
|
||||
}
|
||||
|
||||
func (ctx *secretInspectContext) ID() string {
|
|
@ -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()))
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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]),
|
||||
}
|
||||
|
|
|
@ -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 {
|
|
@ -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
|
||||
`,
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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"},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
Loading…
Reference in New Issue