diff --git a/api/common.go b/api/common.go index 4d8296df8..9e85c5ec3 100644 --- a/api/common.go +++ b/api/common.go @@ -29,21 +29,78 @@ func ValidateHost(val string) (string, error) { return host, nil } -//TODO remove, used on < 1.5 in getContainersJSON +// TODO remove, used on < 1.5 in getContainersJSON func DisplayablePorts(ports *engine.Table) string { - result := []string{} - ports.SetKey("PublicPort") + var ( + result = []string{} + hostMappings = []string{} + firstInGroupMap map[string]int + lastInGroupMap map[string]int + ) + firstInGroupMap = make(map[string]int) + lastInGroupMap = make(map[string]int) + ports.SetKey("PrivatePort") ports.Sort() for _, port := range ports.Data { - if port.Get("IP") == "" { - result = append(result, fmt.Sprintf("%d/%s", port.GetInt("PrivatePort"), port.Get("Type"))) - } else { - result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type"))) + var ( + current = port.GetInt("PrivatePort") + portKey = port.Get("Type") + firstInGroup int + lastInGroup int + ) + if port.Get("IP") != "" { + if port.GetInt("PublicPort") != current { + hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type"))) + continue + } + portKey = fmt.Sprintf("%s/%s", port.Get("IP"), port.Get("Type")) } + firstInGroup = firstInGroupMap[portKey] + lastInGroup = lastInGroupMap[portKey] + + if firstInGroup == 0 { + firstInGroupMap[portKey] = current + lastInGroupMap[portKey] = current + continue + } + + if current == (lastInGroup + 1) { + lastInGroupMap[portKey] = current + continue + } + result = append(result, FormGroup(portKey, firstInGroup, lastInGroup)) + firstInGroupMap[portKey] = current + lastInGroupMap[portKey] = current } + for portKey, firstInGroup := range firstInGroupMap { + result = append(result, FormGroup(portKey, firstInGroup, lastInGroupMap[portKey])) + } + result = append(result, hostMappings...) return strings.Join(result, ", ") } +func FormGroup(key string, start, last int) string { + var ( + group string + parts = strings.Split(key, "/") + groupType = parts[0] + ip = "" + ) + if len(parts) > 1 { + ip = parts[0] + groupType = parts[1] + } + if start == last { + group = fmt.Sprintf("%d", start) + } else { + group = fmt.Sprintf("%d-%d", start, last) + } + if ip != "" { + group = fmt.Sprintf("%s:%s->%s", ip, group, group) + } + return fmt.Sprintf("%s/%s", group, groupType) +} + func MatchesContentType(contentType, expectedType string) bool { mimetype, _, err := mime.ParseMediaType(contentType) if err != nil { diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 093479644..6da9f2f1b 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1442,13 +1442,15 @@ The `docker rename` command allows the container to be renamed to a different na Running `docker ps --no-trunc` showing 2 linked containers. $ sudo docker ps - CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES - f7ee772232194fcc088c6bdec6ea09f7b3f6c54d53934658164b8602d7cd4744 ubuntu:12.04 bash 17 seconds ago Up 16 seconds webapp - d0963715a061c7c7b7cc80b2646da913a959fbf13e80a971d4a60f6997a2f595 crosbymichael/redis:latest /redis-server --dir 33 minutes ago Up 33 minutes 6379/tcp redis,webapp/db + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + 4c01db0b339c ubuntu:12.04 bash 17 seconds ago Up 16 seconds 3300-3310/tcp webapp + d7886598dbe2 crosbymichael/redis:latest /redis-server --dir 33 minutes ago Up 33 minutes 6379/tcp redis,webapp/db `docker ps` will show only running containers by default. To see all containers: `docker ps -a` +`docker ps` will group exposed ports into a single range if possible. E.g., a container that exposes TCP ports `100, 101, 102` will display `100-102/tcp` in the `PORTS` column. + #### Filtering The filtering flag (`-f` or `--filter)` format is a `key=value` pair. If there is more diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index b92c80416..e171ac003 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -566,3 +566,25 @@ func TestPsLinkedWithNoTrunc(t *testing.T) { t.Fatalf("Expected array: %v, got: %v", expected, names) } } + +func TestPsGroupPortRange(t *testing.T) { + defer deleteAllContainers() + + portRange := "3300-3900" + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "porttest", "-p", portRange+":"+portRange, "busybox", "top")) + if err != nil { + t.Fatal(out, err) + } + + out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "ps")) + if err != nil { + t.Fatal(out, err) + } + + // check that the port range is in the output + if !strings.Contains(string(out), portRange) { + t.Fatalf("docker ps output should have had the port range %q: %s", portRange, string(out)) + } + + logDone("ps - port range") +}