diff --git a/daemon/list.go b/daemon/list.go index 750079f966..563bc076be 100644 --- a/daemon/list.go +++ b/daemon/list.go @@ -146,7 +146,7 @@ func (daemon *Daemon) filterByNameIDMatches(view container.View, ctx *listContex continue } for _, eachName := range idNames { - if ctx.filters.Match("name", eachName) { + if ctx.filters.Match("name", strings.TrimPrefix(eachName, "/")) { matches[id] = true } } @@ -429,7 +429,7 @@ func includeContainerInList(container *container.Snapshot, ctx *listContext) ite } // Do not include container if the name doesn't match - if !ctx.filters.Match("name", container.Name) { + if !ctx.filters.Match("name", strings.TrimPrefix(container.Name, "/")) { return excludeContainer } diff --git a/daemon/list_test.go b/daemon/list_test.go index 3be510d13d..34eef43ded 100644 --- a/daemon/list_test.go +++ b/daemon/list_test.go @@ -1,15 +1,82 @@ package daemon import ( + "io/ioutil" + "os" + "path/filepath" + "strings" "testing" "github.com/docker/docker/api/types" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/container" + "github.com/docker/docker/image" + "github.com/opencontainers/go-digest" + "github.com/pborman/uuid" "gotest.tools/assert" is "gotest.tools/assert/cmp" ) +var root string + +func TestMain(m *testing.M) { + var err error + root, err = ioutil.TempDir("", "docker-container-test-") + if err != nil { + panic(err) + } + defer os.RemoveAll(root) + + os.Exit(m.Run()) +} + +// This sets up a container with a name so that name filters +// work against it. It takes in a pointer to Daemon so that +// minor operations are not repeated by the caller +func setupContainerWithName(t *testing.T, name string, daemon *Daemon) *container.Container { + var ( + id = uuid.New() + computedImageID = digest.FromString(id) + cRoot = filepath.Join(root, id) + ) + if err := os.MkdirAll(cRoot, 0755); err != nil { + t.Fatal(err) + } + + c := container.NewBaseContainer(id, cRoot) + // these are for passing includeContainerInList + c.Name = name + c.Running = true + c.HostConfig = &containertypes.HostConfig{} + + // these are for passing the refreshImage reducer + c.ImageID = image.IDFromDigest(computedImageID) + c.Config = &containertypes.Config{ + Image: computedImageID.String(), + } + + // this is done here to avoid requiring these + // operations n x number of containers in the + // calling function + daemon.containersReplica.Save(c) + daemon.reserveName(id, name) + + return c +} + +func containerListContainsName(containers []*types.Container, name string) bool { + for _, container := range containers { + for _, containerName := range container.Names { + if strings.TrimPrefix(containerName, "/") == name { + return true + } + } + } + + return false +} + func TestListInvalidFilter(t *testing.T) { db, err := container.NewViewDB() assert.Assert(t, err == nil) @@ -24,3 +91,35 @@ func TestListInvalidFilter(t *testing.T) { }) assert.Assert(t, is.Error(err, "Invalid filter 'invalid'")) } + +func TestNameFilter(t *testing.T) { + db, err := container.NewViewDB() + assert.Assert(t, err == nil) + d := &Daemon{ + containersReplica: db, + } + + var ( + one = setupContainerWithName(t, "a1", d) + two = setupContainerWithName(t, "a2", d) + three = setupContainerWithName(t, "b1", d) + ) + + // moby/moby #37453 - ^ regex not working due to prefix slash + // not being stripped + containerList, err := d.Containers(&types.ContainerListOptions{ + Filters: filters.NewArgs(filters.Arg("name", "^a")), + }) + assert.Assert(t, err == nil) + assert.Assert(t, is.Len(containerList, 2)) + assert.Assert(t, containerListContainsName(containerList, one.Name)) + assert.Assert(t, containerListContainsName(containerList, two.Name)) + + // Same as above but make sure it works for exact names + containerList, err = d.Containers(&types.ContainerListOptions{ + Filters: filters.NewArgs(filters.Arg("name", "b1")), + }) + assert.Assert(t, err == nil) + assert.Assert(t, is.Len(containerList, 1)) + assert.Assert(t, containerListContainsName(containerList, three.Name)) +}