diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 2c2a95fe04..23f56fcc14 100644 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -2181,6 +2181,10 @@ _docker_ps() { __docker_complete_container_ids return ;; + is-task) + COMPREPLY=( $( compgen -W "true false" -- "${cur##*=}" ) ) + return + ;; name) cur="${cur##*=}" __docker_complete_container_names diff --git a/contrib/completion/zsh/_docker b/contrib/completion/zsh/_docker index 6f1c660bc1..535f1f1ec2 100644 --- a/contrib/completion/zsh/_docker +++ b/contrib/completion/zsh/_docker @@ -333,6 +333,9 @@ __docker_complete_ps_filters() { (id) __docker_containers_ids && ret=0 ;; + (is-task) + _describe -t boolean-filter-opts "filter options" boolean_opts && ret=0 + ;; (name) __docker_containers_names && ret=0 ;; diff --git a/daemon/list.go b/daemon/list.go index cb634333c3..7f898591cc 100644 --- a/daemon/list.go +++ b/daemon/list.go @@ -36,6 +36,7 @@ var acceptedPsFilterTags = map[string]bool{ "since": true, "volume": true, "network": true, + "is-task": true, } // iterationAction represents possible outcomes happening during the container iteration. @@ -79,11 +80,14 @@ type listContext struct { exitAllowed []int // beforeFilter is a filter to ignore containers that appear before the one given - // this is used for --filter=before= and --before=, the latter is deprecated. beforeFilter *container.Container // sinceFilter is a filter to stop the filtering when the iterator arrive to the given container - // this is used for --filter=since= and --since=, the latter is deprecated. sinceFilter *container.Container + + // taskFilter tells if we should filter based on wether a container is part of a task + taskFilter bool + // isTask tells us if the we should filter container that are a task (true) or not (false) + isTask bool // ContainerListOptions is the filters set by the user *types.ContainerListOptions } @@ -241,6 +245,19 @@ func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listConte return nil, err } + var taskFilter, isTask bool + if psFilters.Include("is-task") { + if psFilters.ExactMatch("is-task", "true") { + taskFilter = true + isTask = true + } else if psFilters.ExactMatch("is-task", "false") { + taskFilter = true + isTask = false + } else { + return nil, fmt.Errorf("Invalid filter 'is-task=%s'", psFilters.Get("is-task")) + } + } + var beforeContFilter, sinceContFilter *container.Container err = psFilters.WalkValues("before", func(value string) error { @@ -286,6 +303,8 @@ func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listConte exitAllowed: filtExited, beforeFilter: beforeContFilter, sinceFilter: sinceContFilter, + taskFilter: taskFilter, + isTask: isTask, ContainerListOptions: config, names: daemon.nameIndex.GetAll(), }, nil @@ -325,6 +344,12 @@ func includeContainerInList(container *container.Container, ctx *listContext) it return excludeContainer } + if ctx.taskFilter { + if ctx.isTask != container.Managed { + return excludeContainer + } + } + // Do not include container if any of the labels don't match if !ctx.filters.MatchKVList("label", container.Config.Labels) { return excludeContainer diff --git a/docs/reference/api/docker_remote_api.md b/docs/reference/api/docker_remote_api.md index 205402da9d..bb67613da0 100644 --- a/docs/reference/api/docker_remote_api.md +++ b/docs/reference/api/docker_remote_api.md @@ -130,6 +130,8 @@ This section lists each version from latest to oldest. Each listing includes a instead of the default network if a trailing slash is provided, but no `name` or `id`. * `DELETE /containers/(name)` endpoint now returns an error of `removal of container name is already in progress` with status code of 400, when container name is in a state of removal in progress. +* `GET /containers/json` now supports a `is-task` filter to filter + containers that are tasks (part of a service in swarm mode). ### v1.24 API changes diff --git a/docs/reference/api/docker_remote_api_v1.25.md b/docs/reference/api/docker_remote_api_v1.25.md index 486e70b178..96b3e3801e 100644 --- a/docs/reference/api/docker_remote_api_v1.25.md +++ b/docs/reference/api/docker_remote_api_v1.25.md @@ -229,6 +229,9 @@ List containers - `status=`(`created`|`restarting`|`running`|`removing`|`paused`|`exited`|`dead`) - `label=key` or `label="key=value"` of a container label - `isolation=`(`default`|`process`|`hyperv`) (Windows daemon only) + `id=` a container's ID + `name=` a container's name + `is-task=`(`true`|`false`) - `ancestor`=(`[:]`, `` or ``) - `before`=(`` or ``) - `since`=(`` or ``) diff --git a/docs/reference/commandline/ps.md b/docs/reference/commandline/ps.md index 1f3ceb3799..541979e819 100644 --- a/docs/reference/commandline/ps.md +++ b/docs/reference/commandline/ps.md @@ -27,6 +27,7 @@ Options: - since=(|) - ancestor=([:tag]||) containers created from an image or a descendant. + - is-task=(true|false) --format string Pretty-print containers using a Go template --help Print usage -n, --last int Show n last created containers (includes all states) (default -1) diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index a8e250d8fb..9cbe622a5a 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -136,6 +136,7 @@ func assertContainerList(out string, expected []string) bool { return true } +// FIXME(vdemeester) Move this into a unit test in daemon package func (s *DockerSuite) TestPsListContainersInvalidFilterName(c *check.C) { out, _, err := dockerCmdWithError("ps", "-f", "invalidFilter=test") c.Assert(err, checker.NotNil) diff --git a/integration-cli/docker_cli_swarm_test.go b/integration-cli/docker_cli_swarm_test.go index e2cee8689a..3fbf3099ef 100644 --- a/integration-cli/docker_cli_swarm_test.go +++ b/integration-cli/docker_cli_swarm_test.go @@ -323,3 +323,33 @@ func (s *DockerSwarmSuite) TestSwarmTaskListFilter(c *check.C) { c.Assert(err, checker.IsNil) c.Assert(out, checker.Not(checker.Contains), name) } + +func (s *DockerSwarmSuite) TestPsListContainersFilterIsTask(c *check.C) { + d := s.AddDaemon(c, true, true) + + // Create a bare container + out, err := d.Cmd("run", "-d", "--name=bare-container", "busybox", "top") + c.Assert(err, checker.IsNil) + bareID := strings.TrimSpace(out)[:12] + // Create a service + name := "busybox-top" + out, err = d.Cmd("service", "create", "--name", name, "busybox", "top") + c.Assert(err, checker.IsNil) + c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") + + // make sure task has been deployed. + waitAndAssert(c, defaultReconciliationTimeout, d.checkServiceRunningTasks(c, name), checker.Equals, 1) + + // Filter non-tasks + out, err = d.Cmd("ps", "-a", "-q", "--filter=is-task=false") + c.Assert(err, checker.IsNil) + psOut := strings.TrimSpace(out) + c.Assert(psOut, checker.Equals, bareID, check.Commentf("Expected id %s, got %s for is-task label, output %q", bareID, psOut, out)) + + // Filter tasks + out, err = d.Cmd("ps", "-a", "-q", "--filter=is-task=true") + c.Assert(err, checker.IsNil) + lines := strings.Split(strings.Trim(out, "\n "), "\n") + c.Assert(lines, checker.HasLen, 1) + c.Assert(lines[0], checker.Not(checker.Equals), bareID, check.Commentf("Expected not %s, but got it for is-task label, output %q", bareID, out)) +} diff --git a/man/docker-ps.1.md b/man/docker-ps.1.md index 14c770121f..fd6c4e78fa 100644 --- a/man/docker-ps.1.md +++ b/man/docker-ps.1.md @@ -32,6 +32,7 @@ the running containers. - status=(created|restarting|running|paused|exited|dead) - name= a container's name - id= a container's ID + - is-task=(true|false) - containers that are a task (part of a service managed by swarm) - before=(|) - since=(|) - ancestor=([:tag]||) - containers created from an image or a descendant.