package container import ( "errors" "io" "testing" "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test/builders" "github.com/docker/cli/opts" "github.com/docker/docker/api/types/container" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/golden" ) func TestContainerListBuildContainerListOptions(t *testing.T) { filters := opts.NewFilterOpt() assert.NilError(t, filters.Set("foo=bar")) assert.NilError(t, filters.Set("baz=foo")) contexts := []struct { psOpts *psOptions expectedAll bool expectedSize bool expectedLimit int expectedFilters map[string]string }{ { psOpts: &psOptions{ all: true, size: true, last: 5, filter: filters, }, expectedAll: true, expectedSize: true, expectedLimit: 5, expectedFilters: map[string]string{ "foo": "bar", "baz": "foo", }, }, { psOpts: &psOptions{ all: true, size: true, last: -1, nLatest: true, }, expectedAll: true, expectedSize: true, expectedLimit: 1, expectedFilters: make(map[string]string), }, { psOpts: &psOptions{ all: true, size: false, last: 5, filter: filters, // With .Size, size should be true format: "{{.Size}}", }, expectedAll: true, expectedSize: true, expectedLimit: 5, expectedFilters: map[string]string{ "foo": "bar", "baz": "foo", }, }, { psOpts: &psOptions{ all: true, size: false, last: 5, filter: filters, // With .Size, size should be true format: "{{.Size}} {{.CreatedAt}} {{upper .Networks}}", }, expectedAll: true, expectedSize: true, expectedLimit: 5, expectedFilters: map[string]string{ "foo": "bar", "baz": "foo", }, }, { psOpts: &psOptions{ all: true, size: false, last: 5, filter: filters, // Without .Size, size should be false format: "{{.CreatedAt}} {{.Networks}}", }, expectedAll: true, expectedSize: false, expectedLimit: 5, expectedFilters: map[string]string{ "foo": "bar", "baz": "foo", }, }, } for _, c := range contexts { options, err := buildContainerListOptions(c.psOpts) assert.NilError(t, err) assert.Check(t, is.Equal(c.expectedAll, options.All)) assert.Check(t, is.Equal(c.expectedSize, options.Size)) assert.Check(t, is.Equal(c.expectedLimit, options.Limit)) assert.Check(t, is.Equal(len(c.expectedFilters), options.Filters.Len())) for k, v := range c.expectedFilters { f := options.Filters if !f.ExactMatch(k, v) { t.Fatalf("Expected filter with key %s to be %s but got %s", k, v, f.Get(k)) } } } } func TestContainerListErrors(t *testing.T) { testCases := []struct { flags map[string]string containerListFunc func(container.ListOptions) ([]container.Summary, error) expectedError string }{ { flags: map[string]string{ "format": "{{invalid}}", }, expectedError: `function "invalid" not defined`, }, { flags: map[string]string{ "format": "{{join}}", }, expectedError: `wrong number of args for join`, }, { containerListFunc: func(_ container.ListOptions) ([]container.Summary, error) { return nil, errors.New("error listing containers") }, expectedError: "error listing containers", }, } for _, tc := range testCases { cmd := newListCommand( test.NewFakeCli(&fakeClient{ containerListFunc: tc.containerListFunc, }), ) for key, value := range tc.flags { assert.Check(t, cmd.Flags().Set(key, value)) } cmd.SetArgs([]string{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) assert.ErrorContains(t, cmd.Execute(), tc.expectedError) } } func TestContainerListWithoutFormat(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ containerListFunc: func(_ container.ListOptions) ([]container.Summary, error) { return []container.Summary{ *builders.Container("c1"), *builders.Container("c2", builders.WithName("foo")), *builders.Container("c3", builders.WithPort(80, 80, builders.TCP), builders.WithPort(81, 81, builders.TCP), builders.WithPort(82, 82, builders.TCP)), *builders.Container("c4", builders.WithPort(81, 81, builders.UDP)), *builders.Container("c5", builders.WithPort(82, 82, builders.IP("8.8.8.8"), builders.TCP)), }, nil }, }) cmd := newListCommand(cli) cmd.SetArgs([]string{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "container-list-without-format.golden") } func TestContainerListNoTrunc(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ containerListFunc: func(_ container.ListOptions) ([]container.Summary, error) { return []container.Summary{ *builders.Container("c1"), *builders.Container("c2", builders.WithName("foo/bar")), }, nil }, }) cmd := newListCommand(cli) cmd.SetArgs([]string{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) assert.Check(t, cmd.Flags().Set("no-trunc", "true")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "container-list-without-format-no-trunc.golden") } // Test for GitHub issue docker/docker#21772 func TestContainerListNamesMultipleTime(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ containerListFunc: func(_ container.ListOptions) ([]container.Summary, error) { return []container.Summary{ *builders.Container("c1"), *builders.Container("c2", builders.WithName("foo/bar")), }, nil }, }) cmd := newListCommand(cli) cmd.SetArgs([]string{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) assert.Check(t, cmd.Flags().Set("format", "{{.Names}} {{.Names}}")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "container-list-format-name-name.golden") } // Test for GitHub issue docker/docker#30291 func TestContainerListFormatTemplateWithArg(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ containerListFunc: func(_ container.ListOptions) ([]container.Summary, error) { return []container.Summary{ *builders.Container("c1", builders.WithLabel("some.label", "value")), *builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")), }, nil }, }) cmd := newListCommand(cli) cmd.SetArgs([]string{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) assert.Check(t, cmd.Flags().Set("format", `{{.Names}} {{.Label "some.label"}}`)) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "container-list-format-with-arg.golden") } func TestContainerListFormatSizeSetsOption(t *testing.T) { tests := []struct { doc, format, sizeFlag string sizeExpected bool }{ { doc: "detect with all fields", format: `{{json .}}`, sizeExpected: true, }, { doc: "detect with explicit field", format: `{{.Size}}`, sizeExpected: true, }, { doc: "detect no size", format: `{{.Names}}`, sizeExpected: false, }, { doc: "override enable", format: `{{.Names}}`, sizeFlag: "true", sizeExpected: true, }, { doc: "override disable", format: `{{.Size}}`, sizeFlag: "false", sizeExpected: false, }, } for _, tc := range tests { t.Run(tc.doc, func(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ containerListFunc: func(options container.ListOptions) ([]container.Summary, error) { assert.Check(t, is.Equal(options.Size, tc.sizeExpected)) return []container.Summary{}, nil }, }) cmd := newListCommand(cli) cmd.SetArgs([]string{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) assert.Check(t, cmd.Flags().Set("format", tc.format)) if tc.sizeFlag != "" { assert.Check(t, cmd.Flags().Set("size", tc.sizeFlag)) } assert.NilError(t, cmd.Execute()) }) } } func TestContainerListWithConfigFormat(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ containerListFunc: func(_ container.ListOptions) ([]container.Summary, error) { return []container.Summary{ *builders.Container("c1", builders.WithLabel("some.label", "value"), builders.WithSize(10700000)), *builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar"), builders.WithSize(3200000)), }, nil }, }) cli.SetConfigFile(&configfile.ConfigFile{ PsFormat: "{{ .Names }} {{ .Image }} {{ .Labels }} {{ .Size}}", }) cmd := newListCommand(cli) cmd.SetArgs([]string{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "container-list-with-config-format.golden") } func TestContainerListWithFormat(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ containerListFunc: func(_ container.ListOptions) ([]container.Summary, error) { return []container.Summary{ *builders.Container("c1", builders.WithLabel("some.label", "value")), *builders.Container("c2", builders.WithName("foo/bar"), builders.WithLabel("foo", "bar")), }, nil }, }) t.Run("with format", func(t *testing.T) { cli.OutBuffer().Reset() cmd := newListCommand(cli) cmd.SetArgs([]string{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) assert.Check(t, cmd.Flags().Set("format", "{{ .Names }} {{ .Image }} {{ .Labels }}")) assert.NilError(t, cmd.Execute()) golden.Assert(t, cli.OutBuffer().String(), "container-list-with-format.golden") }) t.Run("with format and quiet", func(t *testing.T) { cli.OutBuffer().Reset() cmd := newListCommand(cli) cmd.SetArgs([]string{}) cmd.SetOut(io.Discard) cmd.SetErr(io.Discard) assert.Check(t, cmd.Flags().Set("format", "{{ .Names }} {{ .Image }} {{ .Labels }}")) assert.Check(t, cmd.Flags().Set("quiet", "true")) assert.NilError(t, cmd.Execute()) assert.Equal(t, cli.ErrBuffer().String(), "WARNING: Ignoring custom format, because both --format and --quiet are set.\n") golden.Assert(t, cli.OutBuffer().String(), "container-list-quiet.golden") }) }