mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
![Paulo Ribeiro](/assets/img/avatar_default.png)
This fix addresses an issue where including the `{{.Size}}` format field in conjunction with `docker ps --format`, without the `--size` flag, would not correctly display the size of containers. This is done by doing a check on the custom format string, and setting the size flag on the options struct if the field is found. This struct gets passed to the engine API which then generates the correct query. An integration test is included which runs `docker ps --format "table {{.Size}}"` without `--size`, and checks that the returned output is not `0 B`. Fixes #21991 As suggested by @cpuguy83, a parser is implemented to process the format string as a template, and then traverses the template tree to determine if `.Size` was called. This was then reworked by making use of template execution with a pre-processor struct that will set the `--size` option if the template calls for the field. The pre-processor now also sets a boolean in the context passed to the writer. There is an integration test for this that calls `docker ps --size --format "{{.Size}}"` and then checks that `size: {{.Size}}` is not appended, as it would with previous behavior. Finally, a change was made to the formatter to not automatically add a `{{.Size}}` if a custom format is provided. Signed-off-by: Paulo Ribeiro <paigr.io@gmail.com> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
554 lines
11 KiB
Go
554 lines
11 KiB
Go
package formatter
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/docker/engine-api/types"
|
|
)
|
|
|
|
func TestContainerContextWrite(t *testing.T) {
|
|
unixTime := time.Now().AddDate(0, 0, -1).Unix()
|
|
expectedTime := time.Unix(unixTime, 0).String()
|
|
|
|
contexts := []struct {
|
|
context ContainerContext
|
|
expected string
|
|
}{
|
|
// Errors
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "{{InvalidFunction}}",
|
|
},
|
|
},
|
|
`Template parsing error: template: :1: function "InvalidFunction" not defined
|
|
`,
|
|
},
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "{{nil}}",
|
|
},
|
|
},
|
|
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
|
`,
|
|
},
|
|
// Table Format
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "table",
|
|
},
|
|
},
|
|
`CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
|
containerID1 ubuntu "" 24 hours ago foobar_baz
|
|
containerID2 ubuntu "" 24 hours ago foobar_bar
|
|
`,
|
|
},
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "table {{.Image}}",
|
|
},
|
|
},
|
|
"IMAGE\nubuntu\nubuntu\n",
|
|
},
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "table {{.Image}}",
|
|
},
|
|
Size: true,
|
|
},
|
|
"IMAGE\nubuntu\nubuntu\n",
|
|
},
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "table {{.Image}}",
|
|
Quiet: true,
|
|
},
|
|
},
|
|
"IMAGE\nubuntu\nubuntu\n",
|
|
},
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "table",
|
|
Quiet: true,
|
|
},
|
|
},
|
|
"containerID1\ncontainerID2\n",
|
|
},
|
|
// Raw Format
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "raw",
|
|
},
|
|
},
|
|
fmt.Sprintf(`container_id: containerID1
|
|
image: ubuntu
|
|
command: ""
|
|
created_at: %s
|
|
status:
|
|
names: foobar_baz
|
|
labels:
|
|
ports:
|
|
|
|
container_id: containerID2
|
|
image: ubuntu
|
|
command: ""
|
|
created_at: %s
|
|
status:
|
|
names: foobar_bar
|
|
labels:
|
|
ports:
|
|
|
|
`, expectedTime, expectedTime),
|
|
},
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "raw",
|
|
},
|
|
Size: true,
|
|
},
|
|
fmt.Sprintf(`container_id: containerID1
|
|
image: ubuntu
|
|
command: ""
|
|
created_at: %s
|
|
status:
|
|
names: foobar_baz
|
|
labels:
|
|
ports:
|
|
size: 0 B
|
|
|
|
container_id: containerID2
|
|
image: ubuntu
|
|
command: ""
|
|
created_at: %s
|
|
status:
|
|
names: foobar_bar
|
|
labels:
|
|
ports:
|
|
size: 0 B
|
|
|
|
`, expectedTime, expectedTime),
|
|
},
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "raw",
|
|
Quiet: true,
|
|
},
|
|
},
|
|
"container_id: containerID1\ncontainer_id: containerID2\n",
|
|
},
|
|
// Custom Format
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "{{.Image}}",
|
|
},
|
|
},
|
|
"ubuntu\nubuntu\n",
|
|
},
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "{{.Image}}",
|
|
},
|
|
Size: true,
|
|
},
|
|
"ubuntu\nubuntu\n",
|
|
},
|
|
}
|
|
|
|
for _, context := range contexts {
|
|
containers := []types.Container{
|
|
{ID: "containerID1", Names: []string{"/foobar_baz"}, Image: "ubuntu", Created: unixTime},
|
|
{ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu", Created: unixTime},
|
|
}
|
|
out := bytes.NewBufferString("")
|
|
context.context.Output = out
|
|
context.context.Containers = containers
|
|
context.context.Write()
|
|
actual := out.String()
|
|
if actual != context.expected {
|
|
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
|
}
|
|
// Clean buffer
|
|
out.Reset()
|
|
}
|
|
}
|
|
|
|
func TestContainerContextWriteWithNoContainers(t *testing.T) {
|
|
out := bytes.NewBufferString("")
|
|
containers := []types.Container{}
|
|
|
|
contexts := []struct {
|
|
context ContainerContext
|
|
expected string
|
|
}{
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "{{.Image}}",
|
|
Output: out,
|
|
},
|
|
},
|
|
"",
|
|
},
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "table {{.Image}}",
|
|
Output: out,
|
|
},
|
|
},
|
|
"IMAGE\n",
|
|
},
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "{{.Image}}",
|
|
Output: out,
|
|
},
|
|
Size: true,
|
|
},
|
|
"",
|
|
},
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "table {{.Image}}",
|
|
Output: out,
|
|
},
|
|
Size: true,
|
|
},
|
|
"IMAGE\n",
|
|
},
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "table {{.Image}}\t{{.Size}}",
|
|
Output: out,
|
|
},
|
|
},
|
|
"IMAGE SIZE\n",
|
|
},
|
|
{
|
|
ContainerContext{
|
|
Context: Context{
|
|
Format: "table {{.Image}}\t{{.Size}}",
|
|
Output: out,
|
|
},
|
|
Size: true,
|
|
},
|
|
"IMAGE SIZE\n",
|
|
},
|
|
}
|
|
|
|
for _, context := range contexts {
|
|
context.context.Containers = containers
|
|
context.context.Write()
|
|
actual := out.String()
|
|
if actual != context.expected {
|
|
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
|
}
|
|
// Clean buffer
|
|
out.Reset()
|
|
}
|
|
}
|
|
|
|
func TestImageContextWrite(t *testing.T) {
|
|
unixTime := time.Now().AddDate(0, 0, -1).Unix()
|
|
expectedTime := time.Unix(unixTime, 0).String()
|
|
|
|
contexts := []struct {
|
|
context ImageContext
|
|
expected string
|
|
}{
|
|
// Errors
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "{{InvalidFunction}}",
|
|
},
|
|
},
|
|
`Template parsing error: template: :1: function "InvalidFunction" not defined
|
|
`,
|
|
},
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "{{nil}}",
|
|
},
|
|
},
|
|
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
|
`,
|
|
},
|
|
// Table Format
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "table",
|
|
},
|
|
},
|
|
`REPOSITORY TAG IMAGE ID CREATED SIZE
|
|
image tag1 imageID1 24 hours ago 0 B
|
|
image <none> imageID1 24 hours ago 0 B
|
|
image tag2 imageID2 24 hours ago 0 B
|
|
<none> <none> imageID3 24 hours ago 0 B
|
|
`,
|
|
},
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "table {{.Repository}}",
|
|
},
|
|
},
|
|
"REPOSITORY\nimage\nimage\nimage\n<none>\n",
|
|
},
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "table {{.Repository}}",
|
|
},
|
|
Digest: true,
|
|
},
|
|
`REPOSITORY DIGEST
|
|
image <none>
|
|
image sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
|
|
image <none>
|
|
<none> <none>
|
|
`,
|
|
},
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "table {{.Repository}}",
|
|
Quiet: true,
|
|
},
|
|
},
|
|
"REPOSITORY\nimage\nimage\nimage\n<none>\n",
|
|
},
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "table",
|
|
Quiet: true,
|
|
},
|
|
},
|
|
"imageID1\nimageID1\nimageID2\nimageID3\n",
|
|
},
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "table",
|
|
Quiet: false,
|
|
},
|
|
Digest: true,
|
|
},
|
|
`REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
|
|
image tag1 <none> imageID1 24 hours ago 0 B
|
|
image <none> sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf imageID1 24 hours ago 0 B
|
|
image tag2 <none> imageID2 24 hours ago 0 B
|
|
<none> <none> <none> imageID3 24 hours ago 0 B
|
|
`,
|
|
},
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "table",
|
|
Quiet: true,
|
|
},
|
|
Digest: true,
|
|
},
|
|
"imageID1\nimageID1\nimageID2\nimageID3\n",
|
|
},
|
|
// Raw Format
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "raw",
|
|
},
|
|
},
|
|
fmt.Sprintf(`repository: image
|
|
tag: tag1
|
|
image_id: imageID1
|
|
created_at: %s
|
|
virtual_size: 0 B
|
|
|
|
repository: image
|
|
tag: <none>
|
|
image_id: imageID1
|
|
created_at: %s
|
|
virtual_size: 0 B
|
|
|
|
repository: image
|
|
tag: tag2
|
|
image_id: imageID2
|
|
created_at: %s
|
|
virtual_size: 0 B
|
|
|
|
repository: <none>
|
|
tag: <none>
|
|
image_id: imageID3
|
|
created_at: %s
|
|
virtual_size: 0 B
|
|
|
|
`, expectedTime, expectedTime, expectedTime, expectedTime),
|
|
},
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "raw",
|
|
},
|
|
Digest: true,
|
|
},
|
|
fmt.Sprintf(`repository: image
|
|
tag: tag1
|
|
digest: <none>
|
|
image_id: imageID1
|
|
created_at: %s
|
|
virtual_size: 0 B
|
|
|
|
repository: image
|
|
tag: <none>
|
|
digest: sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
|
|
image_id: imageID1
|
|
created_at: %s
|
|
virtual_size: 0 B
|
|
|
|
repository: image
|
|
tag: tag2
|
|
digest: <none>
|
|
image_id: imageID2
|
|
created_at: %s
|
|
virtual_size: 0 B
|
|
|
|
repository: <none>
|
|
tag: <none>
|
|
digest: <none>
|
|
image_id: imageID3
|
|
created_at: %s
|
|
virtual_size: 0 B
|
|
|
|
`, expectedTime, expectedTime, expectedTime, expectedTime),
|
|
},
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "raw",
|
|
Quiet: true,
|
|
},
|
|
},
|
|
`image_id: imageID1
|
|
image_id: imageID1
|
|
image_id: imageID2
|
|
image_id: imageID3
|
|
`,
|
|
},
|
|
// Custom Format
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "{{.Repository}}",
|
|
},
|
|
},
|
|
"image\nimage\nimage\n<none>\n",
|
|
},
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "{{.Repository}}",
|
|
},
|
|
Digest: true,
|
|
},
|
|
"image\nimage\nimage\n<none>\n",
|
|
},
|
|
}
|
|
|
|
for _, context := range contexts {
|
|
images := []types.Image{
|
|
{ID: "imageID1", RepoTags: []string{"image:tag1"}, RepoDigests: []string{"image@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf"}, Created: unixTime},
|
|
{ID: "imageID2", RepoTags: []string{"image:tag2"}, Created: unixTime},
|
|
{ID: "imageID3", RepoTags: []string{"<none>:<none>"}, RepoDigests: []string{"<none>@<none>"}, Created: unixTime},
|
|
}
|
|
out := bytes.NewBufferString("")
|
|
context.context.Output = out
|
|
context.context.Images = images
|
|
context.context.Write()
|
|
actual := out.String()
|
|
if actual != context.expected {
|
|
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
|
}
|
|
// Clean buffer
|
|
out.Reset()
|
|
}
|
|
}
|
|
|
|
func TestImageContextWriteWithNoImage(t *testing.T) {
|
|
out := bytes.NewBufferString("")
|
|
images := []types.Image{}
|
|
|
|
contexts := []struct {
|
|
context ImageContext
|
|
expected string
|
|
}{
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "{{.Repository}}",
|
|
Output: out,
|
|
},
|
|
},
|
|
"",
|
|
},
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "table {{.Repository}}",
|
|
Output: out,
|
|
},
|
|
},
|
|
"REPOSITORY\n",
|
|
},
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "{{.Repository}}",
|
|
Output: out,
|
|
},
|
|
Digest: true,
|
|
},
|
|
"",
|
|
},
|
|
{
|
|
ImageContext{
|
|
Context: Context{
|
|
Format: "table {{.Repository}}",
|
|
Output: out,
|
|
},
|
|
Digest: true,
|
|
},
|
|
"REPOSITORY DIGEST\n",
|
|
},
|
|
}
|
|
|
|
for _, context := range contexts {
|
|
context.context.Images = images
|
|
context.context.Write()
|
|
actual := out.String()
|
|
if actual != context.expected {
|
|
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
|
}
|
|
// Clean buffer
|
|
out.Reset()
|
|
}
|
|
}
|