mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Merge pull request #14570 from vdemeester/13365-ps-image-filter
Add docker ps ancestor filter for image
This commit is contained in:
commit
b1cb1b1df4
5 changed files with 202 additions and 6 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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*.
|
||||
|
|
Loading…
Reference in a new issue