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:
88
cli/command/stack/formatter/formatter.go
Normal file
88
cli/command/stack/formatter/formatter.go
Normal file
@ -0,0 +1,88 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
)
|
||||
|
||||
const (
|
||||
// KubernetesStackTableFormat is the default Kubernetes stack format
|
||||
KubernetesStackTableFormat = "table {{.Name}}\t{{.Services}}\t{{.Orchestrator}}\t{{.Namespace}}"
|
||||
// SwarmStackTableFormat is the default Swarm stack format
|
||||
SwarmStackTableFormat = "table {{.Name}}\t{{.Services}}\t{{.Orchestrator}}"
|
||||
|
||||
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
|
||||
Name string
|
||||
// Services is the number of the services
|
||||
Services int
|
||||
// Orchestrator is the platform where the stack is deployed
|
||||
Orchestrator string
|
||||
// Namespace is the Kubernetes namespace assigned to the stack
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// StackWrite writes formatted stacks using the Context
|
||||
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
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ctx.Write(newStackContext(), render)
|
||||
}
|
||||
|
||||
type stackContext struct {
|
||||
formatter.HeaderContext
|
||||
s *Stack
|
||||
}
|
||||
|
||||
func newStackContext() *stackContext {
|
||||
stackCtx := stackContext{}
|
||||
stackCtx.Header = formatter.SubHeaderContext{
|
||||
"Name": formatter.NameHeader,
|
||||
"Services": stackServicesHeader,
|
||||
"Orchestrator": stackOrchestrastorHeader,
|
||||
"Namespace": stackNamespaceHeader,
|
||||
}
|
||||
return &stackCtx
|
||||
}
|
||||
|
||||
func (s *stackContext) MarshalJSON() ([]byte, error) {
|
||||
return formatter.MarshalJSON(s)
|
||||
}
|
||||
|
||||
func (s *stackContext) Name() string {
|
||||
return s.s.Name
|
||||
}
|
||||
|
||||
func (s *stackContext) Services() string {
|
||||
return strconv.Itoa(s.s.Services)
|
||||
}
|
||||
|
||||
func (s *stackContext) Orchestrator() string {
|
||||
return s.s.Orchestrator
|
||||
}
|
||||
|
||||
func (s *stackContext) Namespace() string {
|
||||
return s.s.Namespace
|
||||
}
|
||||
74
cli/command/stack/formatter/formatter_test.go
Normal file
74
cli/command/stack/formatter/formatter_test.go
Normal file
@ -0,0 +1,74 @@
|
||||
package formatter
|
||||
|
||||
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 formatter.Context
|
||||
expected string
|
||||
}{
|
||||
// Errors
|
||||
{
|
||||
formatter.Context{Format: "{{InvalidFunction}}"},
|
||||
`Template parsing error: template: :1: function "InvalidFunction" not defined
|
||||
`,
|
||||
},
|
||||
{
|
||||
formatter.Context{Format: "{{nil}}"},
|
||||
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
||||
`,
|
||||
},
|
||||
// Table format
|
||||
{
|
||||
formatter.Context{Format: formatter.Format(SwarmStackTableFormat)},
|
||||
`NAME SERVICES ORCHESTRATOR
|
||||
baz 2 orchestrator1
|
||||
bar 1 orchestrator2
|
||||
`,
|
||||
},
|
||||
// Kubernetes table format adds Namespace column
|
||||
{
|
||||
formatter.Context{Format: formatter.Format(KubernetesStackTableFormat)},
|
||||
`NAME SERVICES ORCHESTRATOR NAMESPACE
|
||||
baz 2 orchestrator1 namespace1
|
||||
bar 1 orchestrator2 namespace2
|
||||
`,
|
||||
},
|
||||
{
|
||||
formatter.Context{Format: formatter.Format("table {{.Name}}")},
|
||||
`NAME
|
||||
baz
|
||||
bar
|
||||
`,
|
||||
},
|
||||
// Custom Format
|
||||
{
|
||||
formatter.Context{Format: formatter.Format("{{.Name}}")},
|
||||
`baz
|
||||
bar
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
stacks := []*Stack{
|
||||
{Name: "baz", Services: 2, Orchestrator: "orchestrator1", Namespace: "namespace1"},
|
||||
{Name: "bar", Services: 1, Orchestrator: "orchestrator2", Namespace: "namespace2"},
|
||||
}
|
||||
for _, testcase := range cases {
|
||||
out := bytes.NewBufferString("")
|
||||
testcase.context.Output = out
|
||||
err := StackWrite(testcase.context, stacks)
|
||||
if err != nil {
|
||||
assert.Check(t, is.ErrorContains(err, testcase.expected))
|
||||
} else {
|
||||
assert.Check(t, is.Equal(out.String(), testcase.expected))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user