From 5afc1e4924a13c8e7cd3588b4350873f725adaa3 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Wed, 30 Sep 2015 17:26:35 -0400 Subject: [PATCH 1/2] Update samalba/dockerclient dependency. Signed-off-by: Daniel Nephin --- Godeps/Godeps.json | 2 +- Godeps/_workspace/src/github.com/samalba/dockerclient/types.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 0451cb5f18..e8374cf632 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -104,7 +104,7 @@ }, { "ImportPath": "github.com/samalba/dockerclient", - "Rev": "cfb489c624b635251a93e74e1e90eb0959c5367f" + "Rev": "73edd1c3a9d280bcec6a22a12fc90c1a46558b4e" }, { "ImportPath": "github.com/samuel/go-zookeeper/zk", diff --git a/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go b/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go index fdbb907823..a1654b61a5 100644 --- a/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go +++ b/Godeps/_workspace/src/github.com/samalba/dockerclient/types.go @@ -272,7 +272,9 @@ type RespContainersCreate struct { type Image struct { Created int64 Id string + Labels map[string]string ParentId string + RepoDigests []string RepoTags []string Size int64 VirtualSize int64 From 8abf7d32e93e6a8062e5ff52bf94715b8e042acd Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Mon, 28 Sep 2015 16:43:37 -0400 Subject: [PATCH 2/2] Support filtering images by labels Signed-off-by: Daniel Nephin --- api/handlers.go | 9 +++--- cluster/cluster.go | 3 +- cluster/engine.go | 18 +++++++++-- cluster/engine_test.go | 30 +++++++++++++++++++ cluster/mesos/cluster.go | 5 ++-- cluster/swarm/cluster.go | 9 +++--- scheduler/node/node.go | 2 +- test/integration/api/images.bats | 13 ++++++++ .../testdata/imagelabel/Dockerfile | 2 ++ 9 files changed, 77 insertions(+), 14 deletions(-) create mode 100644 test/integration/testdata/imagelabel/Dockerfile diff --git a/api/handlers.go b/api/handlers.go index 6cd4e07fa2..6b5135b5ea 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -28,7 +28,7 @@ const APIVERSION = "1.16" func getInfo(c *context, w http.ResponseWriter, r *http.Request) { info := dockerclient.Info{ Containers: int64(len(c.cluster.Containers())), - Images: int64(len(c.cluster.Images(false))), + Images: int64(len(c.cluster.Images(false, nil))), DriverStatus: c.statusHandler.Status(), NEventsListener: int64(c.eventsHandler.Size()), Debug: c.debug, @@ -78,7 +78,7 @@ func getImages(c *context, w http.ResponseWriter, r *http.Request) { // Create a map of engine address to the list of images it holds. engineImages := make(map[string][]*cluster.Image) - for _, image := range c.cluster.Images(true) { + for _, image := range c.cluster.Images(true, nil) { engineImages[image.Engine.Addr] = append(engineImages[image.Engine.Addr], image) } @@ -122,11 +122,12 @@ func getImagesJSON(c *context, w http.ResponseWriter, r *http.Request) { return } + // TODO: apply node filter in engine? accepteds, _ := filters["node"] // this struct helps grouping images // but still keeps their Engine infos as an array. groupImages := make(map[string]dockerclient.Image) - for _, image := range c.cluster.Images(all) { + for _, image := range c.cluster.Images(all, filters) { if len(accepteds) != 0 { found := false for _, accepted := range accepteds { @@ -720,7 +721,7 @@ func proxyImage(c *context, w http.ResponseWriter, r *http.Request) { func proxyImageTagOptional(c *context, w http.ResponseWriter, r *http.Request) { name := mux.Vars(r)["name"] - for _, image := range c.cluster.Images(true) { + for _, image := range c.cluster.Images(true, nil) { if len(strings.SplitN(name, ":", 2)) == 2 && image.Match(name, true) || len(strings.SplitN(name, ":", 2)) == 1 && image.Match(name, false) { proxy(c.tlsConfig, image.Engine.Addr, w, r) diff --git a/cluster/cluster.go b/cluster/cluster.go index 8bc6f54653..f29db15dfd 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -3,6 +3,7 @@ package cluster import ( "io" + dockerfilters "github.com/docker/docker/pkg/parsers/filters" "github.com/samalba/dockerclient" ) @@ -15,7 +16,7 @@ type Cluster interface { RemoveContainer(container *Container, force, volumes bool) error // Return all images - Images(all bool) []*Image + Images(all bool, filters dockerfilters.Args) []*Image // Return one image matching `IDOrName` Image(IDOrName string) *Image diff --git a/cluster/engine.go b/cluster/engine.go index 304aba47d0..1bf89a1f85 100644 --- a/cluster/engine.go +++ b/cluster/engine.go @@ -12,6 +12,7 @@ import ( "time" log "github.com/Sirupsen/logrus" + dockerfilters "github.com/docker/docker/pkg/parsers/filters" "github.com/docker/docker/pkg/version" "github.com/samalba/dockerclient" "github.com/samalba/dockerclient/nopclient" @@ -558,15 +559,28 @@ func (e *Engine) Containers() Containers { } // Images returns all the images in the engine -func (e *Engine) Images(all bool) []*Image { +func (e *Engine) Images(all bool, filters dockerfilters.Args) []*Image { e.RLock() + includeAll := func(image *Image) bool { + // TODO: this is wrong if RepoTags == [] + return all || (len(image.RepoTags) != 0 && image.RepoTags[0] != ":") + } + + includeFilter := func(image *Image) bool { + if filters == nil { + return true + } + return filters.MatchKVList("label", image.Labels) + } + images := make([]*Image, 0, len(e.images)) for _, image := range e.images { - if all || (!all && len(image.RepoTags) != 0 && image.RepoTags[0] != ":") { + if includeAll(image) && includeFilter(image) { images = append(images, image) } } + // TODO: shouldn't this be defered immediately after locking? e.RUnlock() return images } diff --git a/cluster/engine_test.go b/cluster/engine_test.go index cf6d376204..80e15c1b34 100644 --- a/cluster/engine_test.go +++ b/cluster/engine_test.go @@ -6,6 +6,7 @@ import ( "math" "testing" + dockerfilters "github.com/docker/docker/pkg/parsers/filters" "github.com/samalba/dockerclient" "github.com/samalba/dockerclient/mockclient" "github.com/samalba/dockerclient/nopclient" @@ -217,6 +218,35 @@ func TestCreateContainer(t *testing.T) { assert.Len(t, engine.Containers(), 2) } +func TestImages(t *testing.T) { + engine := NewEngine("test", 0) + engine.images = []*Image{ + {dockerclient.Image{Id: "a"}, engine}, + {dockerclient.Image{Id: "b"}, engine}, + {dockerclient.Image{Id: "c"}, engine}, + } + + result := engine.Images(true, nil) + assert.Equal(t, len(result), 3) +} + +func TestImagesWithFilter(t *testing.T) { + engine := NewEngine("test", 0) + engine.images = []*Image{ + {dockerclient.Image{Id: "a"}, engine}, + {dockerclient.Image{ + Id: "b", + Labels: map[string]string{"com.example.project": "bar"}, + }, engine}, + {dockerclient.Image{Id: "c"}, engine}, + } + + filters := dockerfilters.Args{"label": {"com.example.project=bar"}} + result := engine.Images(true, filters) + assert.Equal(t, len(result), 1) + assert.Equal(t, result[0].Id, "b") +} + func TestTotalMemory(t *testing.T) { engine := NewEngine("test", 0.05) engine.Memory = 1024 diff --git a/cluster/mesos/cluster.go b/cluster/mesos/cluster.go index 34158120e2..783b736b29 100644 --- a/cluster/mesos/cluster.go +++ b/cluster/mesos/cluster.go @@ -12,6 +12,7 @@ import ( "time" log "github.com/Sirupsen/logrus" + dockerfilters "github.com/docker/docker/pkg/parsers/filters" "github.com/docker/swarm/cluster" "github.com/docker/swarm/cluster/mesos/queue" "github.com/docker/swarm/scheduler" @@ -190,13 +191,13 @@ func (c *Cluster) RemoveContainer(container *cluster.Container, force, volumes b } // Images returns all the images in the cluster. -func (c *Cluster) Images(all bool) []*cluster.Image { +func (c *Cluster) Images(all bool, filters dockerfilters.Args) []*cluster.Image { c.RLock() defer c.RUnlock() out := []*cluster.Image{} for _, s := range c.slaves { - out = append(out, s.engine.Images(all)...) + out = append(out, s.engine.Images(all, filters)...) } return out diff --git a/cluster/swarm/cluster.go b/cluster/swarm/cluster.go index cc9b26da8e..b41fb405df 100644 --- a/cluster/swarm/cluster.go +++ b/cluster/swarm/cluster.go @@ -10,6 +10,7 @@ import ( "sync" log "github.com/Sirupsen/logrus" + dockerfilters "github.com/docker/docker/pkg/parsers/filters" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/units" "github.com/docker/swarm/cluster" @@ -237,13 +238,13 @@ func (c *Cluster) monitorDiscovery(ch <-chan discovery.Entries, errCh <-chan err } // Images returns all the images in the cluster. -func (c *Cluster) Images(all bool) []*cluster.Image { +func (c *Cluster) Images(all bool, filters dockerfilters.Args) []*cluster.Image { c.RLock() defer c.RUnlock() out := []*cluster.Image{} for _, e := range c.engines { - out = append(out, e.Images(all)...) + out = append(out, e.Images(all, filters)...) } return out @@ -276,7 +277,7 @@ func (c *Cluster) RemoveImages(name string, force bool) ([]*dockerclient.ImageDe errs := []string{} var err error for _, e := range c.engines { - for _, image := range e.Images(true) { + for _, image := range e.Images(true, nil) { if image.Match(name, true) { content, err := image.Engine.RemoveImage(image, name, force) if err != nil { @@ -698,7 +699,7 @@ func (c *Cluster) TagImage(IDOrName string, repo string, tag string, force bool) var err error found := false for _, e := range c.engines { - for _, image := range e.Images(true) { + for _, image := range e.Images(true, nil) { if image.Match(IDOrName, true) { found = true err := image.Engine.TagImage(IDOrName, repo, tag, force) diff --git a/scheduler/node/node.go b/scheduler/node/node.go index 333d609aed..c33877a72d 100644 --- a/scheduler/node/node.go +++ b/scheduler/node/node.go @@ -34,7 +34,7 @@ func NewNode(e *cluster.Engine) *Node { Name: e.Name, Labels: e.Labels, Containers: e.Containers(), - Images: e.Images(true), + Images: e.Images(true, nil), UsedMemory: e.UsedMemory(), UsedCpus: e.UsedCpus(), TotalMemory: e.TotalMemory(), diff --git a/test/integration/api/images.bats b/test/integration/api/images.bats index be83d46162..eff04ac80d 100644 --- a/test/integration/api/images.bats +++ b/test/integration/api/images.bats @@ -43,3 +43,16 @@ function teardown() { run docker_swarm images -a [ "${#lines[@]}" -ge 3 ] } + +@test "docker images -f label" { + start_docker_with_busybox 2 + swarm_manage + + docker_swarm build -t image-with-labels $TESTDATA/imagelabel + + run docker_swarm images \ + --filter label=com.docker.swarm.test.integration.images=labeltest + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 2 ] + [[ "${lines[1]}" == *"image-with-labels"* ]] +} diff --git a/test/integration/testdata/imagelabel/Dockerfile b/test/integration/testdata/imagelabel/Dockerfile new file mode 100644 index 0000000000..65354259ee --- /dev/null +++ b/test/integration/testdata/imagelabel/Dockerfile @@ -0,0 +1,2 @@ +FROM scratch +LABEL com.docker.swarm.test.integration.images=labeltest