Merge pull request #14570 from vdemeester/13365-ps-image-filter

Add docker ps ancestor filter for image
This commit is contained in:
Sebastiaan van Stijn 2015-08-28 19:47:43 +02:00
commit b1cb1b1df4
5 changed files with 202 additions and 6 deletions

View File

@ -6,7 +6,9 @@ import (
"strconv"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/types"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/graphdb"
"github.com/docker/docker/pkg/nat"
"github.com/docker/docker/pkg/parsers/filters"
@ -37,13 +39,15 @@ type ContainersConfig struct {
// Containers returns a list of all the containers.
func (daemon *Daemon) Containers(config *ContainersConfig) ([]*types.Container, error) {
var (
foundBefore bool
displayed int
all = config.All
n = config.Limit
psFilters filters.Args
filtExited []int
foundBefore bool
displayed int
ancestorFilter bool
all = config.All
n = config.Limit
psFilters filters.Args
filtExited []int
)
imagesFilter := map[string]bool{}
containers := []*types.Container{}
psFilters, err := filters.FromParam(config.Filters)
@ -70,6 +74,27 @@ func (daemon *Daemon) Containers(config *ContainersConfig) ([]*types.Container,
}
}
}
if ancestors, ok := psFilters["ancestor"]; ok {
ancestorFilter = true
byParents := daemon.Graph().ByParent()
// The idea is to walk the graph down the most "efficient" way.
for _, ancestor := range ancestors {
// First, get the imageId of the ancestor filter (yay)
image, err := daemon.Repositories().LookupImage(ancestor)
if err != nil {
logrus.Warnf("Error while looking up for image %v", ancestor)
continue
}
if imagesFilter[ancestor] {
// Already seen this ancestor, skip it
continue
}
// Then walk down the graph and put the imageIds in imagesFilter
populateImageFilterByParents(imagesFilter, image.ID, byParents)
}
}
names := map[string][]string{}
daemon.containerGraph().Walk("/", func(p string, e *graphdb.Entity) error {
names[e.ID()] = append(names[e.ID()], p)
@ -140,6 +165,16 @@ func (daemon *Daemon) Containers(config *ContainersConfig) ([]*types.Container,
if !psFilters.Match("status", container.State.StateString()) {
return nil
}
if ancestorFilter {
if len(imagesFilter) == 0 {
return nil
}
if !imagesFilter[container.ImageID] {
return nil
}
}
displayed++
newC := &types.Container{
ID: container.ID,
@ -254,3 +289,14 @@ func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, error) {
}
return volumesOut, nil
}
func populateImageFilterByParents(ancestorMap map[string]bool, imageID string, byParents map[string][]*image.Image) {
if !ancestorMap[imageID] {
if images, ok := byParents[imageID]; ok {
for _, image := range images {
populateImageFilterByParents(ancestorMap, image.ID, byParents)
}
}
ancestorMap[imageID] = true
}
}

View File

@ -50,6 +50,7 @@ The currently supported filters are:
* name (container's name)
* exited (int - the code of exited containers. Only useful with `--all`)
* status (created|restarting|running|paused|exited)
* ancestor (`<image-name>[:<tag>]`, `<image id>` or `<image@digest>`) - filters containers that were created from the given image or a descendant.
#### Label

View File

@ -387,6 +387,43 @@ func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) {
}
}
func (s *DockerRegistrySuite) TestPsListContainersFilterAncestorImageByDigest(c *check.C) {
digest, err := setupImage(c)
c.Assert(err, check.IsNil, check.Commentf("error setting up image: %v", err))
imageReference := fmt.Sprintf("%s@%s", repoName, digest)
// pull from the registry using the <name>@<digest> reference
dockerCmd(c, "pull", imageReference)
// build a image from it
imageName1 := "images_ps_filter_test"
_, err = buildImage(imageName1, fmt.Sprintf(
`FROM %s
LABEL match me 1`, imageReference), true)
c.Assert(err, check.IsNil)
// run a container based on that
out, _ := dockerCmd(c, "run", "-d", imageReference, "echo", "hello")
expectedID := strings.TrimSpace(out)
// run a container based on the a descendant of that too
out, _ = dockerCmd(c, "run", "-d", imageName1, "echo", "hello")
expectedID1 := strings.TrimSpace(out)
expectedIDs := []string{expectedID, expectedID1}
// Invalid imageReference
out, _ = dockerCmd(c, "ps", "-a", "-q", "--no-trunc", fmt.Sprintf("--filter=ancestor=busybox@%s", digest))
if strings.TrimSpace(out) != "" {
c.Fatalf("Expected filter container for %s ancestor filter to be empty, got %v", fmt.Sprintf("busybox@%s", digest), strings.TrimSpace(out))
}
// Valid imageReference
out, _ = dockerCmd(c, "ps", "-a", "-q", "--no-trunc", "--filter=ancestor="+imageReference)
checkPsAncestorFilterOutput(c, out, imageReference, expectedIDs)
}
func (s *DockerRegistrySuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C) {
pushDigest, err := setupImage(c)
if err != nil {

View File

@ -12,6 +12,9 @@ import (
"time"
"github.com/go-check/check"
"sort"
"github.com/docker/docker/pkg/stringid"
)
func (s *DockerSuite) TestPsListContainers(c *check.C) {
@ -278,6 +281,113 @@ func (s *DockerSuite) TestPsListContainersFilterName(c *check.C) {
}
// Test for the ancestor filter for ps.
// There is also the same test but with image:tag@digest in docker_cli_by_digest_test.go
//
// What the test setups :
// - Create 2 image based on busybox using the same repository but different tags
// - Create an image based on the previous image (images_ps_filter_test2)
// - Run containers for each of those image (busybox, images_ps_filter_test1, images_ps_filter_test2)
// - Filter them out :P
func (s *DockerSuite) TestPsListContainersFilterAncestorImage(c *check.C) {
// Build images
imageName1 := "images_ps_filter_test1"
imageID1, err := buildImage(imageName1,
`FROM busybox
LABEL match me 1`, true)
c.Assert(err, check.IsNil)
imageName1Tagged := "images_ps_filter_test1:tag"
imageID1Tagged, err := buildImage(imageName1Tagged,
`FROM busybox
LABEL match me 1 tagged`, true)
c.Assert(err, check.IsNil)
imageName2 := "images_ps_filter_test2"
imageID2, err := buildImage(imageName2,
fmt.Sprintf(`FROM %s
LABEL match me 2`, imageName1), true)
c.Assert(err, check.IsNil)
// start containers
out, _ := dockerCmd(c, "run", "-d", "busybox", "echo", "hello")
firstID := strings.TrimSpace(out)
// start another container
out, _ = dockerCmd(c, "run", "-d", "busybox", "echo", "hello")
secondID := strings.TrimSpace(out)
// start third container
out, _ = dockerCmd(c, "run", "-d", imageName1, "echo", "hello")
thirdID := strings.TrimSpace(out)
// start fourth container
out, _ = dockerCmd(c, "run", "-d", imageName1Tagged, "echo", "hello")
fourthID := strings.TrimSpace(out)
// start fifth container
out, _ = dockerCmd(c, "run", "-d", imageName2, "echo", "hello")
fifthID := strings.TrimSpace(out)
var filterTestSuite = []struct {
filterName string
expectedIDs []string
}{
// non existent stuff
{"nonexistent", []string{}},
{"nonexistent:tag", []string{}},
// image
{"busybox", []string{firstID, secondID, thirdID, fourthID, fifthID}},
{imageName1, []string{thirdID, fifthID}},
{imageName2, []string{fifthID}},
// image:tag
{fmt.Sprintf("%s:latest", imageName1), []string{thirdID, fifthID}},
{imageName1Tagged, []string{fourthID}},
// short-id
{stringid.TruncateID(imageID1), []string{thirdID, fifthID}},
{stringid.TruncateID(imageID2), []string{fifthID}},
// full-id
{imageID1, []string{thirdID, fifthID}},
{imageID1Tagged, []string{fourthID}},
{imageID2, []string{fifthID}},
}
for _, filter := range filterTestSuite {
out, _ = dockerCmd(c, "ps", "-a", "-q", "--no-trunc", "--filter=ancestor="+filter.filterName)
checkPsAncestorFilterOutput(c, out, filter.filterName, filter.expectedIDs)
}
// Multiple ancestor filter
out, _ = dockerCmd(c, "ps", "-a", "-q", "--no-trunc", "--filter=ancestor="+imageName2, "--filter=ancestor="+imageName1Tagged)
checkPsAncestorFilterOutput(c, out, imageName2+","+imageName1Tagged, []string{fourthID, fifthID})
}
func checkPsAncestorFilterOutput(c *check.C, out string, filterName string, expectedIDs []string) {
actualIDs := []string{}
if out != "" {
actualIDs = strings.Split(out[:len(out)-1], "\n")
}
sort.Strings(actualIDs)
sort.Strings(expectedIDs)
if len(actualIDs) != len(expectedIDs) {
c.Fatalf("Expected filtered container(s) for %s ancestor filter to be %v:%v, got %v:%v", filterName, len(expectedIDs), expectedIDs, len(actualIDs), actualIDs)
}
if len(expectedIDs) > 0 {
same := true
for i := range expectedIDs {
if actualIDs[i] != expectedIDs[i] {
c.Logf("%s, %s", actualIDs[i], expectedIDs[i])
same = false
break
}
}
if !same {
c.Fatalf("Expected filtered container(s) for %s ancestor filter to be %v, got %v", filterName, expectedIDs, actualIDs)
}
}
}
func (s *DockerSuite) TestPsListContainersFilterLabel(c *check.C) {
// start container
out, _ := dockerCmd(c, "run", "-d", "-l", "match=me", "-l", "second=tag", "busybox")

View File

@ -41,6 +41,8 @@ the running containers.
status=(created|restarting|running|paused|exited)
name=<string> - container's name
id=<ID> - container's ID
ancestor=(<image-name>[:tag]|<image-id>|<image@digest>) - filters containers that were
created from the given image or a descendant.
**-l**, **--latest**=*true*|*false*
Show only the latest created container, include non-running ones. The default is *false*.