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 `<port>[/<protocol>]` or `<from>-<to>[/<protocol>]`
and filtering on containers.
An integration test has been added to cover the changes.
This fix fixes 27178.
Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
| ... | ... |
@@ -38,6 +38,8 @@ var acceptedPsFilterTags = map[string]bool{
|
| 38 | 38 |
"volume": true, |
| 39 | 39 |
"network": true, |
| 40 | 40 |
"is-task": true, |
| 41 |
+ "publish": true, |
|
| 42 |
+ "expose": true, |
|
| 41 | 43 |
} |
| 42 | 44 |
|
| 43 | 45 |
// iterationAction represents possible outcomes happening during the container iteration. |
| ... | ... |
@@ -89,6 +91,12 @@ type listContext struct {
|
| 89 | 89 |
taskFilter bool |
| 90 | 90 |
// isTask tells us if the we should filter container that are a task (true) or not (false) |
| 91 | 91 |
isTask bool |
| 92 |
+ |
|
| 93 |
+ // publish is a list of published ports to filter with |
|
| 94 |
+ publish map[nat.Port]bool |
|
| 95 |
+ // expose is a list of exposed ports to filter with |
|
| 96 |
+ expose map[nat.Port]bool |
|
| 97 |
+ |
|
| 92 | 98 |
// ContainerListOptions is the filters set by the user |
| 93 | 99 |
*types.ContainerListOptions |
| 94 | 100 |
} |
| ... | ... |
@@ -311,6 +319,54 @@ func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listConte |
| 311 | 311 |
}) |
| 312 | 312 |
} |
| 313 | 313 |
|
| 314 |
+ publishFilter := map[nat.Port]bool{}
|
|
| 315 |
+ err = psFilters.WalkValues("publish", func(value string) error {
|
|
| 316 |
+ if strings.Contains(value, ":") {
|
|
| 317 |
+ return fmt.Errorf("filter for 'publish' should not contain ':': %v", value)
|
|
| 318 |
+ } |
|
| 319 |
+ //support two formats, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] |
|
| 320 |
+ proto, port := nat.SplitProtoPort(value) |
|
| 321 |
+ start, end, err := nat.ParsePortRange(port) |
|
| 322 |
+ if err != nil {
|
|
| 323 |
+ return fmt.Errorf("error while looking up for publish %v: %s", value, err)
|
|
| 324 |
+ } |
|
| 325 |
+ for i := start; i <= end; i++ {
|
|
| 326 |
+ p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) |
|
| 327 |
+ if err != nil {
|
|
| 328 |
+ return fmt.Errorf("error while looking up for publish %v: %s", value, err)
|
|
| 329 |
+ } |
|
| 330 |
+ publishFilter[p] = true |
|
| 331 |
+ } |
|
| 332 |
+ return nil |
|
| 333 |
+ }) |
|
| 334 |
+ if err != nil {
|
|
| 335 |
+ return nil, err |
|
| 336 |
+ } |
|
| 337 |
+ |
|
| 338 |
+ exposeFilter := map[nat.Port]bool{}
|
|
| 339 |
+ err = psFilters.WalkValues("expose", func(value string) error {
|
|
| 340 |
+ if strings.Contains(value, ":") {
|
|
| 341 |
+ return fmt.Errorf("filter for 'expose' should not contain ':': %v", value)
|
|
| 342 |
+ } |
|
| 343 |
+ //support two formats, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] |
|
| 344 |
+ proto, port := nat.SplitProtoPort(value) |
|
| 345 |
+ start, end, err := nat.ParsePortRange(port) |
|
| 346 |
+ if err != nil {
|
|
| 347 |
+ return fmt.Errorf("error while looking up for 'expose' %v: %s", value, err)
|
|
| 348 |
+ } |
|
| 349 |
+ for i := start; i <= end; i++ {
|
|
| 350 |
+ p, err := nat.NewPort(proto, strconv.FormatUint(i, 10)) |
|
| 351 |
+ if err != nil {
|
|
| 352 |
+ return fmt.Errorf("error while looking up for 'expose' %v: %s", value, err)
|
|
| 353 |
+ } |
|
| 354 |
+ exposeFilter[p] = true |
|
| 355 |
+ } |
|
| 356 |
+ return nil |
|
| 357 |
+ }) |
|
| 358 |
+ if err != nil {
|
|
| 359 |
+ return nil, err |
|
| 360 |
+ } |
|
| 361 |
+ |
|
| 314 | 362 |
return &listContext{
|
| 315 | 363 |
filters: psFilters, |
| 316 | 364 |
ancestorFilter: ancestorFilter, |
| ... | ... |
@@ -320,6 +376,8 @@ func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listConte |
| 320 | 320 |
sinceFilter: sinceContFilter, |
| 321 | 321 |
taskFilter: taskFilter, |
| 322 | 322 |
isTask: isTask, |
| 323 |
+ publish: publishFilter, |
|
| 324 |
+ expose: exposeFilter, |
|
| 323 | 325 |
ContainerListOptions: config, |
| 324 | 326 |
names: daemon.nameIndex.GetAll(), |
| 325 | 327 |
}, nil |
| ... | ... |
@@ -459,6 +517,32 @@ func includeContainerInList(container *container.Container, ctx *listContext) it |
| 459 | 459 |
} |
| 460 | 460 |
} |
| 461 | 461 |
|
| 462 |
+ if len(ctx.publish) > 0 {
|
|
| 463 |
+ shouldSkip := true |
|
| 464 |
+ for port := range ctx.publish {
|
|
| 465 |
+ if _, ok := container.HostConfig.PortBindings[port]; ok {
|
|
| 466 |
+ shouldSkip = false |
|
| 467 |
+ break |
|
| 468 |
+ } |
|
| 469 |
+ } |
|
| 470 |
+ if shouldSkip {
|
|
| 471 |
+ return excludeContainer |
|
| 472 |
+ } |
|
| 473 |
+ } |
|
| 474 |
+ |
|
| 475 |
+ if len(ctx.expose) > 0 {
|
|
| 476 |
+ shouldSkip := true |
|
| 477 |
+ for port := range ctx.expose {
|
|
| 478 |
+ if _, ok := container.Config.ExposedPorts[port]; ok {
|
|
| 479 |
+ shouldSkip = false |
|
| 480 |
+ break |
|
| 481 |
+ } |
|
| 482 |
+ } |
|
| 483 |
+ if shouldSkip {
|
|
| 484 |
+ return excludeContainer |
|
| 485 |
+ } |
|
| 486 |
+ } |
|
| 487 |
+ |
|
| 462 | 488 |
return includeContainer |
| 463 | 489 |
} |
| 464 | 490 |
|
| ... | ... |
@@ -32,6 +32,8 @@ Options: |
| 32 | 32 |
- since=(<container-name>|<container-id>) |
| 33 | 33 |
- ancestor=(<image-name>[:tag]|<image-id>|<image@digest>) |
| 34 | 34 |
containers created from an image or a descendant. |
| 35 |
+ - publish=(<port>[/<proto>]|<startport-endport>/[<proto>]) |
|
| 36 |
+ - expose=(<port>[/<proto>]|<startport-endport>/[<proto>]) |
|
| 35 | 37 |
- is-task=(true|false) |
| 36 | 38 |
- health=(starting|healthy|unhealthy|none) |
| 37 | 39 |
--format string Pretty-print containers using a Go template |
| ... | ... |
@@ -83,6 +85,8 @@ The currently supported filters are: |
| 83 | 83 |
* volume (volume name or mount point) - filters containers that mount volumes. |
| 84 | 84 |
* network (network id or name) - filters containers connected to the provided network |
| 85 | 85 |
* health (starting|healthy|unhealthy|none) - filters containers based on healthcheck status |
| 86 |
+* publish=(container's published port) - filters published ports by containers |
|
| 87 |
+* expose=(container's exposed port) - filters exposed ports by containers |
|
| 86 | 88 |
|
| 87 | 89 |
#### Label |
| 88 | 90 |
|
| ... | ... |
@@ -328,6 +332,44 @@ CONTAINER ID IMAGE COMMAND CREATED STATUS |
| 328 | 328 |
9d4893ed80fe ubuntu "top" 10 minutes ago Up 10 minutes test1 |
| 329 | 329 |
``` |
| 330 | 330 |
|
| 331 |
+#### Publish and Expose |
|
| 332 |
+ |
|
| 333 |
+The `publish` and `expose` filters show only containers that have published or exposed port with a given port |
|
| 334 |
+number, port range, and/or protocol. The default protocol is `tcp` when not specified. |
|
| 335 |
+ |
|
| 336 |
+The following filter matches all containers that have published port of 80: |
|
| 337 |
+ |
|
| 338 |
+```bash |
|
| 339 |
+$ docker run -d --publish=80 busybox top |
|
| 340 |
+$ docker run -d --expose=8080 busybox top |
|
| 341 |
+ |
|
| 342 |
+$ docker ps -a |
|
| 343 |
+ |
|
| 344 |
+CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
|
| 345 |
+9833437217a5 busybox "top" 5 seconds ago Up 4 seconds 8080/tcp dreamy_mccarthy |
|
| 346 |
+fc7e477723b7 busybox "top" 50 seconds ago Up 50 seconds 0.0.0.0:32768->80/tcp admiring_roentgen |
|
| 347 |
+ |
|
| 348 |
+$ docker ps --filter publish=80 |
|
| 349 |
+ |
|
| 350 |
+CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
|
| 351 |
+fc7e477723b7 busybox "top" About a minute ago Up About a minute 0.0.0.0:32768->80/tcp admiring_roentgen |
|
| 352 |
+``` |
|
| 353 |
+ |
|
| 354 |
+The following filter matches all containers that have exposed TCP port in the range of `8000-8080`: |
|
| 355 |
+```bash |
|
| 356 |
+$ docker ps --filter expose=8000-8080/tcp |
|
| 357 |
+ |
|
| 358 |
+CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
|
| 359 |
+9833437217a5 busybox "top" 21 seconds ago Up 19 seconds 8080/tcp dreamy_mccarthy |
|
| 360 |
+``` |
|
| 361 |
+ |
|
| 362 |
+The following filter matches all containers that have exposed UDP port `80`: |
|
| 363 |
+```bash |
|
| 364 |
+$ docker ps --filter publish=80/udp |
|
| 365 |
+ |
|
| 366 |
+CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
|
| 367 |
+``` |
|
| 368 |
+ |
|
| 331 | 369 |
## Formatting |
| 332 | 370 |
|
| 333 | 371 |
The formatting option (`--format`) pretty-prints container output using a Go |
| ... | ... |
@@ -924,3 +924,37 @@ func (s *DockerSuite) TestPsFormatTemplateWithArg(c *check.C) {
|
| 924 | 924 |
out, _ := dockerCmd(c, "ps", "--format", `{{.Names}} {{.Label "some.label"}}`)
|
| 925 | 925 |
c.Assert(strings.TrimSpace(out), checker.Equals, "top label.foo-bar") |
| 926 | 926 |
} |
| 927 |
+ |
|
| 928 |
+func (s *DockerSuite) TestPsListContainersFilterPorts(c *check.C) {
|
|
| 929 |
+ testRequires(c, DaemonIsLinux) |
|
| 930 |
+ |
|
| 931 |
+ out, _ := dockerCmd(c, "run", "-d", "--publish=80", "busybox", "top") |
|
| 932 |
+ id1 := strings.TrimSpace(out) |
|
| 933 |
+ |
|
| 934 |
+ out, _ = dockerCmd(c, "run", "-d", "--expose=8080", "busybox", "top") |
|
| 935 |
+ id2 := strings.TrimSpace(out) |
|
| 936 |
+ |
|
| 937 |
+ out, _ = dockerCmd(c, "ps", "--no-trunc", "-q") |
|
| 938 |
+ c.Assert(strings.TrimSpace(out), checker.Contains, id1) |
|
| 939 |
+ c.Assert(strings.TrimSpace(out), checker.Contains, id2) |
|
| 940 |
+ |
|
| 941 |
+ out, _ = dockerCmd(c, "ps", "--no-trunc", "-q", "--filter", "publish=80-8080/udp") |
|
| 942 |
+ c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), id1) |
|
| 943 |
+ c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), id2) |
|
| 944 |
+ |
|
| 945 |
+ out, _ = dockerCmd(c, "ps", "--no-trunc", "-q", "--filter", "expose=8081") |
|
| 946 |
+ c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), id1) |
|
| 947 |
+ c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), id2) |
|
| 948 |
+ |
|
| 949 |
+ out, _ = dockerCmd(c, "ps", "--no-trunc", "-q", "--filter", "publish=80-81") |
|
| 950 |
+ c.Assert(strings.TrimSpace(out), checker.Equals, id1) |
|
| 951 |
+ c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), id2) |
|
| 952 |
+ |
|
| 953 |
+ out, _ = dockerCmd(c, "ps", "--no-trunc", "-q", "--filter", "expose=80/tcp") |
|
| 954 |
+ c.Assert(strings.TrimSpace(out), checker.Equals, id1) |
|
| 955 |
+ c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), id2) |
|
| 956 |
+ |
|
| 957 |
+ out, _ = dockerCmd(c, "ps", "--no-trunc", "-q", "--filter", "expose=8080/tcp") |
|
| 958 |
+ c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), id1) |
|
| 959 |
+ c.Assert(strings.TrimSpace(out), checker.Equals, id2) |
|
| 960 |
+} |