mirror of
https://github.com/docker/cli.git
synced 2026-01-13 18:22:35 +03:00
Merge pull request #6073 from thaJeztah/format_cleanups_and_fixes
cli/command/formatter: fix .Labels format being randomized
This commit is contained in:
@@ -68,16 +68,14 @@ ports: {{- pad .Ports 1 0}}
|
||||
|
||||
// ContainerWrite renders the context for a list of containers
|
||||
func ContainerWrite(ctx Context, containers []container.Summary) error {
|
||||
render := func(format func(subContext SubContext) error) error {
|
||||
return ctx.Write(NewContainerContext(), func(format func(subContext SubContext) error) error {
|
||||
for _, ctr := range containers {
|
||||
err := format(&ContainerContext{trunc: ctx.Trunc, c: ctr})
|
||||
if err != nil {
|
||||
if err := format(&ContainerContext{trunc: ctx.Trunc, c: ctr}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ctx.Write(NewContainerContext(), render)
|
||||
})
|
||||
}
|
||||
|
||||
// ContainerContext is a struct used for rendering a list of containers in a Go template.
|
||||
@@ -256,6 +254,7 @@ func (c *ContainerContext) Labels() string {
|
||||
for k, v := range c.c.Labels {
|
||||
joinLabels = append(joinLabels, k+"="+v)
|
||||
}
|
||||
sort.Strings(joinLabels)
|
||||
return strings.Join(joinLabels, ",")
|
||||
}
|
||||
|
||||
|
||||
@@ -371,9 +371,6 @@ size: 0B
|
||||
}
|
||||
|
||||
func TestContainerContextWriteWithNoContainers(t *testing.T) {
|
||||
out := bytes.NewBufferString("")
|
||||
containers := []container.Summary{}
|
||||
|
||||
cases := []struct {
|
||||
context Context
|
||||
expected string
|
||||
@@ -381,40 +378,34 @@ func TestContainerContextWriteWithNoContainers(t *testing.T) {
|
||||
{
|
||||
context: Context{
|
||||
Format: "{{.Image}}",
|
||||
Output: out,
|
||||
},
|
||||
},
|
||||
{
|
||||
context: Context{
|
||||
Format: "table {{.Image}}",
|
||||
Output: out,
|
||||
},
|
||||
expected: "IMAGE\n",
|
||||
},
|
||||
{
|
||||
context: Context{
|
||||
Format: NewContainerFormat("{{.Image}}", false, true),
|
||||
Output: out,
|
||||
},
|
||||
},
|
||||
{
|
||||
context: Context{
|
||||
Format: NewContainerFormat("table {{.Image}}", false, true),
|
||||
Output: out,
|
||||
},
|
||||
expected: "IMAGE\n",
|
||||
},
|
||||
{
|
||||
context: Context{
|
||||
Format: "table {{.Image}}\t{{.Size}}",
|
||||
Output: out,
|
||||
},
|
||||
expected: "IMAGE SIZE\n",
|
||||
},
|
||||
{
|
||||
context: Context{
|
||||
Format: NewContainerFormat("table {{.Image}}\t{{.Size}}", false, true),
|
||||
Output: out,
|
||||
},
|
||||
expected: "IMAGE SIZE\n",
|
||||
},
|
||||
@@ -422,11 +413,11 @@ func TestContainerContextWriteWithNoContainers(t *testing.T) {
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
err := ContainerWrite(tc.context, containers)
|
||||
out := new(bytes.Buffer)
|
||||
tc.context.Output = out
|
||||
err := ContainerWrite(tc.context, nil)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, out.String(), tc.expected)
|
||||
// Clean buffer
|
||||
out.Reset()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -506,28 +497,59 @@ func TestContainerContextWriteJSONField(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestContainerBackCompat(t *testing.T) {
|
||||
containers := []container.Summary{{ID: "brewhaha"}}
|
||||
cases := []string{
|
||||
"ID",
|
||||
"Names",
|
||||
"Image",
|
||||
"Command",
|
||||
"CreatedAt",
|
||||
"RunningFor",
|
||||
"Ports",
|
||||
"Status",
|
||||
"Size",
|
||||
"Labels",
|
||||
"Mounts",
|
||||
createdAtTime := time.Now().AddDate(-1, 0, 0) // 1 year ago
|
||||
|
||||
ctrContext := container.Summary{
|
||||
ID: "aabbccddeeff",
|
||||
Names: []string{"/foobar_baz"},
|
||||
Image: "docker.io/library/ubuntu", // should this have canonical format or not?
|
||||
ImageID: "sha256:a5a665ff33eced1e0803148700880edab4269067ed77e27737a708d0d293fbf5", // should this have algo-prefix or not?
|
||||
ImageManifestDescriptor: nil,
|
||||
Command: "/bin/sh",
|
||||
Created: createdAtTime.UTC().Unix(),
|
||||
Ports: []container.Port{{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}},
|
||||
SizeRw: 123,
|
||||
SizeRootFs: 12345,
|
||||
Labels: map[string]string{"label1": "value1", "label2": "value2"},
|
||||
State: "running",
|
||||
Status: "running",
|
||||
HostConfig: struct {
|
||||
NetworkMode string `json:",omitempty"`
|
||||
Annotations map[string]string `json:",omitempty"`
|
||||
}{
|
||||
NetworkMode: "bridge",
|
||||
Annotations: map[string]string{
|
||||
"com.example.annotation": "hello",
|
||||
},
|
||||
},
|
||||
NetworkSettings: nil,
|
||||
Mounts: nil,
|
||||
}
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for _, c := range cases {
|
||||
ctx := Context{Format: Format(fmt.Sprintf("{{ .%s }}", c)), Output: buf}
|
||||
if err := ContainerWrite(ctx, containers); err != nil {
|
||||
t.Logf("could not render template for field '%s': %v", c, err)
|
||||
t.Fail()
|
||||
}
|
||||
buf.Reset()
|
||||
|
||||
tests := []struct {
|
||||
field string
|
||||
expected string
|
||||
}{
|
||||
{field: "ID", expected: "aabbccddeeff"},
|
||||
{field: "Names", expected: "foobar_baz"},
|
||||
{field: "Image", expected: "docker.io/library/ubuntu"},
|
||||
{field: "Command", expected: `"/bin/sh"`},
|
||||
{field: "CreatedAt", expected: time.Unix(createdAtTime.Unix(), 0).String()},
|
||||
{field: "RunningFor", expected: "12 months ago"},
|
||||
{field: "Ports", expected: "8080/tcp"},
|
||||
{field: "Status", expected: "running"},
|
||||
{field: "Size", expected: "123B (virtual 12.3kB)"},
|
||||
{field: "Labels", expected: "label1=value1,label2=value2"},
|
||||
{field: "Mounts", expected: ""},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.field, func(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
ctx := Context{Format: Format(fmt.Sprintf("{{ .%s }}", tc.field)), Output: buf}
|
||||
assert.NilError(t, ContainerWrite(ctx, []container.Summary{ctrContext}))
|
||||
assert.Check(t, is.Equal(strings.TrimSpace(buf.String()), tc.expected))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ type DiskUsageContext struct {
|
||||
}
|
||||
|
||||
func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template, error) {
|
||||
ctx.buffer = bytes.NewBufferString("")
|
||||
ctx.buffer = &bytes.Buffer{}
|
||||
ctx.header = ""
|
||||
ctx.Format = Format(format)
|
||||
ctx.preFormat()
|
||||
@@ -87,7 +87,7 @@ func (ctx *DiskUsageContext) Write() (err error) {
|
||||
if ctx.Verbose {
|
||||
return ctx.verboseWrite()
|
||||
}
|
||||
ctx.buffer = bytes.NewBufferString("")
|
||||
ctx.buffer = &bytes.Buffer{}
|
||||
ctx.preFormat()
|
||||
|
||||
tmpl, err := ctx.parseFormat()
|
||||
|
||||
@@ -82,6 +82,9 @@ func (c *Context) parseFormat() (*template.Template, error) {
|
||||
}
|
||||
|
||||
func (c *Context) postFormat(tmpl *template.Template, subContext SubContext) {
|
||||
if c.Output == nil {
|
||||
c.Output = io.Discard
|
||||
}
|
||||
if c.Format.IsTable() {
|
||||
t := tabwriter.NewWriter(c.Output, 10, 1, 3, ' ', 0)
|
||||
buffer := bytes.NewBufferString("")
|
||||
@@ -111,7 +114,7 @@ type SubFormat func(func(SubContext) error) error
|
||||
|
||||
// Write the template to the buffer using this Context
|
||||
func (c *Context) Write(sub SubContext, f SubFormat) error {
|
||||
c.buffer = bytes.NewBufferString("")
|
||||
c.buffer = &bytes.Buffer{}
|
||||
c.preFormat()
|
||||
|
||||
tmpl, err := c.parseFormat()
|
||||
|
||||
@@ -26,23 +26,29 @@ type Inspector interface {
|
||||
|
||||
// TemplateInspector uses a text template to inspect elements.
|
||||
type TemplateInspector struct {
|
||||
outputStream io.Writer
|
||||
buffer *bytes.Buffer
|
||||
tmpl *template.Template
|
||||
out io.Writer
|
||||
buffer *bytes.Buffer
|
||||
tmpl *template.Template
|
||||
}
|
||||
|
||||
// NewTemplateInspector creates a new inspector with a template.
|
||||
func NewTemplateInspector(outputStream io.Writer, tmpl *template.Template) Inspector {
|
||||
func NewTemplateInspector(out io.Writer, tmpl *template.Template) *TemplateInspector {
|
||||
if out == nil {
|
||||
out = io.Discard
|
||||
}
|
||||
return &TemplateInspector{
|
||||
outputStream: outputStream,
|
||||
buffer: new(bytes.Buffer),
|
||||
tmpl: tmpl,
|
||||
out: out,
|
||||
buffer: new(bytes.Buffer),
|
||||
tmpl: tmpl,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTemplateInspectorFromString creates a new TemplateInspector from a string
|
||||
// which is compiled into a template.
|
||||
func NewTemplateInspectorFromString(out io.Writer, tmplStr string) (Inspector, error) {
|
||||
if out == nil {
|
||||
return nil, errors.New("no output stream")
|
||||
}
|
||||
if tmplStr == "" {
|
||||
return NewIndentedInspector(out), nil
|
||||
}
|
||||
@@ -65,6 +71,9 @@ type GetRefFunc func(ref string) (any, []byte, error)
|
||||
// Inspect fetches objects by reference using GetRefFunc and writes the json
|
||||
// representation to the output writer.
|
||||
func Inspect(out io.Writer, references []string, tmplStr string, getRef GetRefFunc) error {
|
||||
if out == nil {
|
||||
return errors.New("no output stream")
|
||||
}
|
||||
inspector, err := NewTemplateInspectorFromString(out, tmplStr)
|
||||
if err != nil {
|
||||
return cli.StatusError{StatusCode: 64, Status: err.Error()}
|
||||
@@ -138,18 +147,21 @@ func (i *TemplateInspector) tryRawInspectFallback(rawElement []byte) error {
|
||||
// Flush writes the result of inspecting all elements into the output stream.
|
||||
func (i *TemplateInspector) Flush() error {
|
||||
if i.buffer.Len() == 0 {
|
||||
_, err := io.WriteString(i.outputStream, "\n")
|
||||
_, err := io.WriteString(i.out, "\n")
|
||||
return err
|
||||
}
|
||||
_, err := io.Copy(i.outputStream, i.buffer)
|
||||
_, err := io.Copy(i.out, i.buffer)
|
||||
return err
|
||||
}
|
||||
|
||||
// NewIndentedInspector generates a new inspector with an indented representation
|
||||
// of elements.
|
||||
func NewIndentedInspector(outputStream io.Writer) Inspector {
|
||||
return &elementsInspector{
|
||||
outputStream: outputStream,
|
||||
func NewIndentedInspector(out io.Writer) Inspector {
|
||||
if out == nil {
|
||||
out = io.Discard
|
||||
}
|
||||
return &jsonInspector{
|
||||
out: out,
|
||||
raw: func(dst *bytes.Buffer, src []byte) error {
|
||||
return json.Indent(dst, src, "", " ")
|
||||
},
|
||||
@@ -161,23 +173,26 @@ func NewIndentedInspector(outputStream io.Writer) Inspector {
|
||||
|
||||
// NewJSONInspector generates a new inspector with a compact representation
|
||||
// of elements.
|
||||
func NewJSONInspector(outputStream io.Writer) Inspector {
|
||||
return &elementsInspector{
|
||||
outputStream: outputStream,
|
||||
raw: json.Compact,
|
||||
el: json.Marshal,
|
||||
func NewJSONInspector(out io.Writer) Inspector {
|
||||
if out == nil {
|
||||
out = io.Discard
|
||||
}
|
||||
return &jsonInspector{
|
||||
out: out,
|
||||
raw: json.Compact,
|
||||
el: json.Marshal,
|
||||
}
|
||||
}
|
||||
|
||||
type elementsInspector struct {
|
||||
outputStream io.Writer
|
||||
elements []any
|
||||
rawElements [][]byte
|
||||
raw func(dst *bytes.Buffer, src []byte) error
|
||||
el func(v any) ([]byte, error)
|
||||
type jsonInspector struct {
|
||||
out io.Writer
|
||||
elements []any
|
||||
rawElements [][]byte
|
||||
raw func(dst *bytes.Buffer, src []byte) error
|
||||
el func(v any) ([]byte, error)
|
||||
}
|
||||
|
||||
func (e *elementsInspector) Inspect(typedElement any, rawElement []byte) error {
|
||||
func (e *jsonInspector) Inspect(typedElement any, rawElement []byte) error {
|
||||
if rawElement != nil {
|
||||
e.rawElements = append(e.rawElements, rawElement)
|
||||
} else {
|
||||
@@ -186,9 +201,9 @@ func (e *elementsInspector) Inspect(typedElement any, rawElement []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *elementsInspector) Flush() error {
|
||||
func (e *jsonInspector) Flush() error {
|
||||
if len(e.elements) == 0 && len(e.rawElements) == 0 {
|
||||
_, err := io.WriteString(e.outputStream, "[]\n")
|
||||
_, err := io.WriteString(e.out, "[]\n")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -216,9 +231,9 @@ func (e *elementsInspector) Flush() error {
|
||||
buffer = bytes.NewReader(b)
|
||||
}
|
||||
|
||||
if _, err := io.Copy(e.outputStream, buffer); err != nil {
|
||||
if _, err := io.Copy(e.out, buffer); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := io.WriteString(e.outputStream, "\n")
|
||||
_, err := io.WriteString(e.out, "\n")
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user