From 743943f6364d33425cfbcadad43507fd16305f1a Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sat, 15 Oct 2016 15:53:06 -0700 Subject: [PATCH] Add `publish` and `expose` filter for `docker ps --filter` This fix tries to address the enhancement proposal raised in 27178 for filtering based on published or exposed ports of `docker ps --filter`. In this fix, two filter options, `publish` and `expose` have been added to take either `[/]` or `-[/]` and filtering on containers. An integration test has been added to cover the changes. This fix fixes 27178. Signed-off-by: Yong Tang --- daemon/list.go | 84 +++++++++++++++++++++++++++ docs/reference/commandline/ps.md | 42 ++++++++++++++ integration-cli/docker_cli_ps_test.go | 34 +++++++++++ 3 files changed, 160 insertions(+) diff --git a/daemon/list.go b/daemon/list.go index 615394f3df..4de5d0ac6f 100644 --- a/daemon/list.go +++ b/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 /[] or /[] + 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 /[] or /[] + 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 } diff --git a/docs/reference/commandline/ps.md b/docs/reference/commandline/ps.md index 1d5f31da88..d162532d31 100644 --- a/docs/reference/commandline/ps.md +++ b/docs/reference/commandline/ps.md @@ -32,6 +32,8 @@ Options: - since=(|) - ancestor=([:tag]||) containers created from an image or a descendant. + - publish=([/]|/[]) + - expose=([/]|/[]) - 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 diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index c0e4034638..dc2ac17506 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -924,3 +924,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) +}