Browse Source

Merge pull request #27557 from yongtang/27178-ps-filter-publish-expose

Add `publish` and `expose` filter for `docker ps --filter`
Vincent Demeester 8 years ago
parent
commit
1b6a15eedc
3 changed files with 160 additions and 0 deletions
  1. 84 0
      daemon/list.go
  2. 42 0
      docs/reference/commandline/ps.md
  3. 34 0
      integration-cli/docker_cli_ps_test.go

+ 84 - 0
daemon/list.go

@@ -38,6 +38,8 @@ var acceptedPsFilterTags = map[string]bool{
 	"volume":    true,
 	"network":   true,
 	"is-task":   true,
+	"publish":   true,
+	"expose":    true,
 }
 
 // iterationAction represents possible outcomes happening during the container iteration.
@@ -89,6 +91,12 @@ type listContext struct {
 	taskFilter bool
 	// isTask tells us if the we should filter container that are a task (true) or not (false)
 	isTask bool
+
+	// publish is a list of published ports to filter with
+	publish map[nat.Port]bool
+	// expose is a list of exposed ports to filter with
+	expose map[nat.Port]bool
+
 	// ContainerListOptions is the filters set by the user
 	*types.ContainerListOptions
 }
@@ -311,6 +319,54 @@ func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listConte
 		})
 	}
 
+	publishFilter := map[nat.Port]bool{}
+	err = psFilters.WalkValues("publish", func(value string) error {
+		if strings.Contains(value, ":") {
+			return fmt.Errorf("filter for 'publish' should not contain ':': %v", value)
+		}
+		//support two formats, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
+		proto, port := nat.SplitProtoPort(value)
+		start, end, err := nat.ParsePortRange(port)
+		if err != nil {
+			return fmt.Errorf("error while looking up for publish %v: %s", value, err)
+		}
+		for i := start; i <= end; i++ {
+			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
+			if err != nil {
+				return fmt.Errorf("error while looking up for publish %v: %s", value, err)
+			}
+			publishFilter[p] = true
+		}
+		return nil
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	exposeFilter := map[nat.Port]bool{}
+	err = psFilters.WalkValues("expose", func(value string) error {
+		if strings.Contains(value, ":") {
+			return fmt.Errorf("filter for 'expose' should not contain ':': %v", value)
+		}
+		//support two formats, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
+		proto, port := nat.SplitProtoPort(value)
+		start, end, err := nat.ParsePortRange(port)
+		if err != nil {
+			return fmt.Errorf("error while looking up for 'expose' %v: %s", value, err)
+		}
+		for i := start; i <= end; i++ {
+			p, err := nat.NewPort(proto, strconv.FormatUint(i, 10))
+			if err != nil {
+				return fmt.Errorf("error while looking up for 'expose' %v: %s", value, err)
+			}
+			exposeFilter[p] = true
+		}
+		return nil
+	})
+	if err != nil {
+		return nil, err
+	}
+
 	return &listContext{
 		filters:              psFilters,
 		ancestorFilter:       ancestorFilter,
@@ -320,6 +376,8 @@ func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listConte
 		sinceFilter:          sinceContFilter,
 		taskFilter:           taskFilter,
 		isTask:               isTask,
+		publish:              publishFilter,
+		expose:               exposeFilter,
 		ContainerListOptions: config,
 		names:                daemon.nameIndex.GetAll(),
 	}, nil
@@ -459,6 +517,32 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
 		}
 	}
 
+	if len(ctx.publish) > 0 {
+		shouldSkip := true
+		for port := range ctx.publish {
+			if _, ok := container.HostConfig.PortBindings[port]; ok {
+				shouldSkip = false
+				break
+			}
+		}
+		if shouldSkip {
+			return excludeContainer
+		}
+	}
+
+	if len(ctx.expose) > 0 {
+		shouldSkip := true
+		for port := range ctx.expose {
+			if _, ok := container.Config.ExposedPorts[port]; ok {
+				shouldSkip = false
+				break
+			}
+		}
+		if shouldSkip {
+			return excludeContainer
+		}
+	}
+
 	return includeContainer
 }
 

+ 42 - 0
docs/reference/commandline/ps.md

@@ -32,6 +32,8 @@ Options:
                         - since=(<container-name>|<container-id>)
                         - ancestor=(<image-name>[:tag]|<image-id>|<image@digest>)
                           containers created from an image or a descendant.
+                        - publish=(<port>[/<proto>]|<startport-endport>/[<proto>])
+                        - expose=(<port>[/<proto>]|<startport-endport>/[<proto>])
                         - is-task=(true|false)
                         - health=(starting|healthy|unhealthy|none)
       --format string   Pretty-print containers using a Go template
@@ -83,6 +85,8 @@ The currently supported filters are:
 * volume (volume name or mount point) - filters containers that mount volumes.
 * network (network id or name) - filters containers connected to the provided network
 * health (starting|healthy|unhealthy|none) - filters containers based on healthcheck status
+* publish=(container's published port) - filters published ports by containers
+* expose=(container's exposed port) - filters exposed ports by containers
 
 #### Label
 
@@ -328,6 +332,44 @@ CONTAINER ID        IMAGE       COMMAND       CREATED             STATUS
 9d4893ed80fe        ubuntu      "top"         10 minutes ago      Up 10 minutes                           test1
 ```
 
+#### Publish and Expose
+
+The `publish` and `expose` filters show only containers that have published or exposed port with a given port
+number, port range, and/or protocol. The default protocol is `tcp` when not specified.
+
+The following filter matches all containers that have published port of 80:
+
+```bash
+$ docker run -d --publish=80 busybox top
+$ docker run -d --expose=8080 busybox top
+
+$ docker ps -a
+
+CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                   NAMES
+9833437217a5        busybox             "top"               5 seconds ago       Up 4 seconds        8080/tcp                dreamy_mccarthy
+fc7e477723b7        busybox             "top"               50 seconds ago      Up 50 seconds       0.0.0.0:32768->80/tcp   admiring_roentgen
+
+$ docker ps --filter publish=80
+
+CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS              PORTS                   NAMES
+fc7e477723b7        busybox             "top"               About a minute ago   Up About a minute   0.0.0.0:32768->80/tcp   admiring_roentgen
+```
+
+The following filter matches all containers that have exposed TCP port in the range of `8000-8080`:
+```bash
+$ docker ps --filter expose=8000-8080/tcp
+
+CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
+9833437217a5        busybox             "top"               21 seconds ago      Up 19 seconds       8080/tcp            dreamy_mccarthy
+```
+
+The following filter matches all containers that have exposed UDP port `80`:
+```bash
+$ docker ps --filter publish=80/udp
+
+CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
+```
+
 ## Formatting
 
 The formatting option (`--format`) pretty-prints container output using a Go

+ 34 - 0
integration-cli/docker_cli_ps_test.go

@@ -922,3 +922,37 @@ func (s *DockerSuite) TestPsFormatTemplateWithArg(c *check.C) {
 	out, _ := dockerCmd(c, "ps", "--format", `{{.Names}} {{.Label "some.label"}}`)
 	c.Assert(strings.TrimSpace(out), checker.Equals, "top label.foo-bar")
 }
+
+func (s *DockerSuite) TestPsListContainersFilterPorts(c *check.C) {
+	testRequires(c, DaemonIsLinux)
+
+	out, _ := dockerCmd(c, "run", "-d", "--publish=80", "busybox", "top")
+	id1 := strings.TrimSpace(out)
+
+	out, _ = dockerCmd(c, "run", "-d", "--expose=8080", "busybox", "top")
+	id2 := strings.TrimSpace(out)
+
+	out, _ = dockerCmd(c, "ps", "--no-trunc", "-q")
+	c.Assert(strings.TrimSpace(out), checker.Contains, id1)
+	c.Assert(strings.TrimSpace(out), checker.Contains, id2)
+
+	out, _ = dockerCmd(c, "ps", "--no-trunc", "-q", "--filter", "publish=80-8080/udp")
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), id1)
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), id2)
+
+	out, _ = dockerCmd(c, "ps", "--no-trunc", "-q", "--filter", "expose=8081")
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), id1)
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), id2)
+
+	out, _ = dockerCmd(c, "ps", "--no-trunc", "-q", "--filter", "publish=80-81")
+	c.Assert(strings.TrimSpace(out), checker.Equals, id1)
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), id2)
+
+	out, _ = dockerCmd(c, "ps", "--no-trunc", "-q", "--filter", "expose=80/tcp")
+	c.Assert(strings.TrimSpace(out), checker.Equals, id1)
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), id2)
+
+	out, _ = dockerCmd(c, "ps", "--no-trunc", "-q", "--filter", "expose=8080/tcp")
+	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), id1)
+	c.Assert(strings.TrimSpace(out), checker.Equals, id2)
+}