mirror of https://github.com/docker/docs.git
docker ps: Support for filters and improve state management.
- `docker ps` now fully supports `--filter` flags - Generate `Status` in `docker ps` dynamically. "Up X seconds" is now real-time. - Misc cleanup. Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
This commit is contained in:
parent
8a00a5ff10
commit
c69f0db71f
|
@ -106,28 +106,104 @@ func getContainersJSON(c *context, w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse flags.
|
||||||
all := r.Form.Get("all") == "1"
|
all := r.Form.Get("all") == "1"
|
||||||
limit, _ := strconv.Atoi(r.Form.Get("limit"))
|
limit, _ := strconv.Atoi(r.Form.Get("limit"))
|
||||||
|
|
||||||
out := []*dockerclient.Container{}
|
// Parse filters.
|
||||||
|
filters, err := dockerfilters.FromParam(r.Form.Get("filters"))
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filtExited := []int{}
|
||||||
|
if i, ok := filters["exited"]; ok {
|
||||||
|
for _, value := range i {
|
||||||
|
code, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filtExited = append(filtExited, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i, ok := filters["status"]; ok {
|
||||||
|
for _, value := range i {
|
||||||
|
if value == "exited" {
|
||||||
|
all = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filtering: select the containers we want to return.
|
||||||
|
candidates := []*cluster.Container{}
|
||||||
for _, container := range c.cluster.Containers() {
|
for _, container := range c.cluster.Containers() {
|
||||||
tmp := (*container).Container
|
|
||||||
// Skip stopped containers unless -a was specified.
|
// Skip stopped containers unless -a was specified.
|
||||||
if !strings.Contains(tmp.Status, "Up") && !all && limit <= 0 {
|
if !container.Info.State.Running && !all && limit <= 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip swarm containers unless -a was specified.
|
// Skip swarm containers unless -a was specified.
|
||||||
if strings.Split(tmp.Image, ":")[0] == "swarm" && !all {
|
if strings.Split(container.Image, ":")[0] == "swarm" && !all {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply filters.
|
||||||
|
if !filters.Match("name", strings.TrimPrefix(container.Names[0], "/")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !filters.Match("id", container.Id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !filters.MatchKVList("label", container.Config.Labels) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !filters.Match("status", container.StateString()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filtExited) > 0 {
|
||||||
|
shouldSkip := true
|
||||||
|
for _, code := range filtExited {
|
||||||
|
if code == container.Info.State.ExitCode && !container.Info.State.Running {
|
||||||
|
shouldSkip = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if shouldSkip {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates = append(candidates, container)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the candidates and apply limits.
|
||||||
|
sort.Sort(sort.Reverse(ContainerSorter(candidates)))
|
||||||
|
if limit > 0 && limit < len(candidates) {
|
||||||
|
candidates = candidates[:limit]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert cluster.Container back into dockerclient.Container.
|
||||||
|
out := []*dockerclient.Container{}
|
||||||
|
for _, container := range candidates {
|
||||||
|
// Create a copy of the underlying dockerclient.Container so we can
|
||||||
|
// make changes without messing with cluster.Container.
|
||||||
|
tmp := (*container).Container
|
||||||
|
|
||||||
|
// Update the Status. The one we have is stale from the last `docker ps` the engine sent.
|
||||||
|
// `Status()` will generate a new one
|
||||||
|
tmp.Status = container.Status()
|
||||||
if !container.Engine.IsHealthy() {
|
if !container.Engine.IsHealthy() {
|
||||||
tmp.Status = "Pending"
|
tmp.Status = "Pending"
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove the Node Name in the name when we have a good solution
|
// TODO remove the Node Name in the name when we have a good solution
|
||||||
tmp.Names = make([]string, len(container.Names))
|
tmp.Names = make([]string, len(container.Names))
|
||||||
for i, name := range container.Names {
|
for i, name := range container.Names {
|
||||||
tmp.Names[i] = "/" + container.Engine.Name + name
|
tmp.Names[i] = "/" + container.Engine.Name + name
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert node IP
|
// insert node IP
|
||||||
tmp.Ports = make([]dockerclient.Port, len(container.Ports))
|
tmp.Ports = make([]dockerclient.Port, len(container.Ports))
|
||||||
for i, port := range container.Ports {
|
for i, port := range container.Ports {
|
||||||
|
@ -139,14 +215,9 @@ func getContainersJSON(c *context, w http.ResponseWriter, r *http.Request) {
|
||||||
out = append(out, &tmp)
|
out = append(out, &tmp)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(sort.Reverse(ContainerSorter(out)))
|
// Finally, send them back to the CLI.
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if limit > 0 && limit < len(out) {
|
json.NewEncoder(w).Encode(out)
|
||||||
json.NewEncoder(w).Encode(out[:limit])
|
|
||||||
} else {
|
|
||||||
json.NewEncoder(w).Encode(out)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /containers/{name:.*}/json
|
// GET /containers/{name:.*}/json
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import "github.com/docker/swarm/cluster"
|
||||||
"github.com/samalba/dockerclient"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ContainerSorter implements the Sort interface to sort Docker containers.
|
// ContainerSorter implements the Sort interface to sort Docker containers.
|
||||||
// It is not guaranteed to be a stable sort.
|
// It is not guaranteed to be a stable sort.
|
||||||
type ContainerSorter []*dockerclient.Container
|
type ContainerSorter []*cluster.Container
|
||||||
|
|
||||||
// Len returns the number of containers to be sorted.
|
// Len returns the number of containers to be sorted.
|
||||||
func (s ContainerSorter) Len() int {
|
func (s ContainerSorter) Len() int {
|
||||||
|
@ -21,5 +19,5 @@ func (s ContainerSorter) Swap(i, j int) {
|
||||||
// Less reports whether the container with index i should sort before the container with index j.
|
// Less reports whether the container with index i should sort before the container with index j.
|
||||||
// Containers are sorted chronologically by when they were created.
|
// Containers are sorted chronologically by when they were created.
|
||||||
func (s ContainerSorter) Less(i, j int) bool {
|
func (s ContainerSorter) Less(i, j int) bool {
|
||||||
return s[i].Created < s[j].Created
|
return s[i].Info.Created < s[j].Info.Created
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,3 +44,57 @@ function teardown() {
|
||||||
[ "${#lines[@]}" -eq 2 ]
|
[ "${#lines[@]}" -eq 2 ]
|
||||||
[[ "${lines[1]}" == *"false"* ]]
|
[[ "${lines[1]}" == *"false"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "docker ps --filter" {
|
||||||
|
start_docker_with_busybox 2
|
||||||
|
swarm_manage
|
||||||
|
|
||||||
|
# Running
|
||||||
|
firstID=$(docker_swarm run -d --name name1 --label "match=me" --label "second=tag" busybox sleep 10000)
|
||||||
|
# Exited - successfull
|
||||||
|
secondID=$(docker_swarm run -d --name name2 --label "match=me too" busybox true)
|
||||||
|
# Exited - error
|
||||||
|
thirdID=$(docker_swarm run -d --name name3 --label "nomatch=me" busybox false)
|
||||||
|
|
||||||
|
# status
|
||||||
|
run docker_swarm ps -q --no-trunc --filter=status=exited
|
||||||
|
[ "${#lines[@]}" -eq 2 ]
|
||||||
|
[[ "$output" != *"$firstID"* ]]
|
||||||
|
[[ "$output" == *"$secondID"* ]]
|
||||||
|
[[ "$output" == *"$thirdID"* ]]
|
||||||
|
run docker_swarm ps -q -a --no-trunc --filter=status=running
|
||||||
|
[[ "$output" == "$firstID" ]]
|
||||||
|
|
||||||
|
# id
|
||||||
|
run docker_swarm ps -a -q --no-trunc --filter=id="$secondID"
|
||||||
|
[[ "$output" == "$secondID" ]]
|
||||||
|
run docker_swarm ps -a -q --no-trunc --filter=id="bogusID"
|
||||||
|
[ "${#lines[@]}" -eq 0 ]
|
||||||
|
|
||||||
|
# name
|
||||||
|
run docker_swarm ps -a -q --no-trunc --filter=name=name3
|
||||||
|
[[ "$output" == "$thirdID" ]]
|
||||||
|
run docker_swarm ps -a -q --no-trunc --filter=name=badname
|
||||||
|
[ "${#lines[@]}" -eq 0 ]
|
||||||
|
|
||||||
|
# exit code
|
||||||
|
run docker_swarm ps -a -q --no-trunc --filter=exited=0
|
||||||
|
[[ "$output" == "$secondID" ]]
|
||||||
|
run docker_swarm ps -a -q --no-trunc --filter=exited=1
|
||||||
|
[[ "$output" == "$thirdID" ]]
|
||||||
|
run docker_swarm ps -a -q --no-trunc --filter=exited=99
|
||||||
|
[ "${#lines[@]}" -eq 0 ]
|
||||||
|
|
||||||
|
# labels
|
||||||
|
run docker_swarm ps -a -q --no-trunc --filter=label=match=me
|
||||||
|
[[ "$output" == "$firstID" ]]
|
||||||
|
run docker_swarm ps -a -q --no-trunc --filter=label=match=me --filter=label=second=tag
|
||||||
|
[[ "$output" == "$firstID" ]]
|
||||||
|
run docker_swarm ps -a -q --no-trunc --filter=label=match=me --filter=label=second=tag-no
|
||||||
|
[ "${#lines[@]}" -eq 0 ]
|
||||||
|
run docker_swarm ps -a -q --no-trunc --filter=label=match
|
||||||
|
[ "${#lines[@]}" -eq 2 ]
|
||||||
|
[[ "$output" == *"$firstID"* ]]
|
||||||
|
[[ "$output" == *"$secondID"* ]]
|
||||||
|
[[ "$output" != *"$thirdID"* ]]
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue