Merge pull request #14570 from vdemeester/13365-ps-image-filter
Add docker ps ancestor filter for image
This commit is contained in:
commit
b1cb1b1df4
|
@ -6,7 +6,9 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/image"
|
||||||
"github.com/docker/docker/pkg/graphdb"
|
"github.com/docker/docker/pkg/graphdb"
|
||||||
"github.com/docker/docker/pkg/nat"
|
"github.com/docker/docker/pkg/nat"
|
||||||
"github.com/docker/docker/pkg/parsers/filters"
|
"github.com/docker/docker/pkg/parsers/filters"
|
||||||
|
@ -39,11 +41,13 @@ func (daemon *Daemon) Containers(config *ContainersConfig) ([]*types.Container,
|
||||||
var (
|
var (
|
||||||
foundBefore bool
|
foundBefore bool
|
||||||
displayed int
|
displayed int
|
||||||
|
ancestorFilter bool
|
||||||
all = config.All
|
all = config.All
|
||||||
n = config.Limit
|
n = config.Limit
|
||||||
psFilters filters.Args
|
psFilters filters.Args
|
||||||
filtExited []int
|
filtExited []int
|
||||||
)
|
)
|
||||||
|
imagesFilter := map[string]bool{}
|
||||||
containers := []*types.Container{}
|
containers := []*types.Container{}
|
||||||
|
|
||||||
psFilters, err := filters.FromParam(config.Filters)
|
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{}
|
names := map[string][]string{}
|
||||||
daemon.containerGraph().Walk("/", func(p string, e *graphdb.Entity) error {
|
daemon.containerGraph().Walk("/", func(p string, e *graphdb.Entity) error {
|
||||||
names[e.ID()] = append(names[e.ID()], p)
|
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()) {
|
if !psFilters.Match("status", container.State.StateString()) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ancestorFilter {
|
||||||
|
if len(imagesFilter) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !imagesFilter[container.ImageID] {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
displayed++
|
displayed++
|
||||||
newC := &types.Container{
|
newC := &types.Container{
|
||||||
ID: container.ID,
|
ID: container.ID,
|
||||||
|
@ -254,3 +289,14 @@ func (daemon *Daemon) Volumes(filter string) ([]*types.Volume, error) {
|
||||||
}
|
}
|
||||||
return volumesOut, nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ The currently supported filters are:
|
||||||
* name (container's name)
|
* name (container's name)
|
||||||
* exited (int - the code of exited containers. Only useful with `--all`)
|
* exited (int - the code of exited containers. Only useful with `--all`)
|
||||||
* status (created|restarting|running|paused|exited)
|
* 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
|
#### Label
|
||||||
|
|
|
@ -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) {
|
func (s *DockerRegistrySuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C) {
|
||||||
pushDigest, err := setupImage(c)
|
pushDigest, err := setupImage(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -12,6 +12,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-check/check"
|
"github.com/go-check/check"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/stringid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *DockerSuite) TestPsListContainers(c *check.C) {
|
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) {
|
func (s *DockerSuite) TestPsListContainersFilterLabel(c *check.C) {
|
||||||
// start container
|
// start container
|
||||||
out, _ := dockerCmd(c, "run", "-d", "-l", "match=me", "-l", "second=tag", "busybox")
|
out, _ := dockerCmd(c, "run", "-d", "-l", "match=me", "-l", "second=tag", "busybox")
|
||||||
|
|
|
@ -41,6 +41,8 @@ the running containers.
|
||||||
status=(created|restarting|running|paused|exited)
|
status=(created|restarting|running|paused|exited)
|
||||||
name=<string> - container's name
|
name=<string> - container's name
|
||||||
id=<ID> - container's ID
|
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*
|
**-l**, **--latest**=*true*|*false*
|
||||||
Show only the latest created container, include non-running ones. The default is *false*.
|
Show only the latest created container, include non-running ones. The default is *false*.
|
||||||
|
|
Loading…
Reference in New Issue