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:
Andrea Luzzardi 2015-05-06 22:54:34 -07:00
parent 8a00a5ff10
commit c69f0db71f
3 changed files with 139 additions and 16 deletions

View File

@ -106,28 +106,104 @@ func getContainersJSON(c *context, w http.ResponseWriter, r *http.Request) {
return
}
// Parse flags.
all := r.Form.Get("all") == "1"
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() {
tmp := (*container).Container
// 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
}
// Skip swarm containers unless -a was specified.
if strings.Split(tmp.Image, ":")[0] == "swarm" && !all {
if strings.Split(container.Image, ":")[0] == "swarm" && !all {
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() {
tmp.Status = "Pending"
}
// TODO remove the Node Name in the name when we have a good solution
tmp.Names = make([]string, len(container.Names))
for i, name := range container.Names {
tmp.Names[i] = "/" + container.Engine.Name + name
}
// insert node IP
tmp.Ports = make([]dockerclient.Port, len(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)
}
sort.Sort(sort.Reverse(ContainerSorter(out)))
// Finally, send them back to the CLI.
w.Header().Set("Content-Type", "application/json")
if limit > 0 && limit < len(out) {
json.NewEncoder(w).Encode(out[:limit])
} else {
json.NewEncoder(w).Encode(out)
}
json.NewEncoder(w).Encode(out)
}
// GET /containers/{name:.*}/json

View File

@ -1,12 +1,10 @@
package api
import (
"github.com/samalba/dockerclient"
)
import "github.com/docker/swarm/cluster"
// ContainerSorter implements the Sort interface to sort Docker containers.
// 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.
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.
// Containers are sorted chronologically by when they were created.
func (s ContainerSorter) Less(i, j int) bool {
return s[i].Created < s[j].Created
return s[i].Info.Created < s[j].Info.Created
}

View File

@ -44,3 +44,57 @@ function teardown() {
[ "${#lines[@]}" -eq 2 ]
[[ "${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"* ]]
}