group ports to render ranges as ... ranges

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2021-09-21 15:21:28 +02:00 committed by Nicolas De loof
parent e39ea13002
commit 97a0efd7c3
4 changed files with 104 additions and 17 deletions

View File

@ -147,14 +147,7 @@ SERVICES:
func writter(containers []api.ContainerSummary) func(w io.Writer) {
return func(w io.Writer) {
for _, container := range containers {
var ports []string
for _, p := range container.Publishers {
if p.URL == "" {
ports = append(ports, fmt.Sprintf("%d/%s", p.TargetPort, p.Protocol))
} else {
ports = append(ports, fmt.Sprintf("%s->%d/%s", p.URL, p.TargetPort, p.Protocol))
}
}
ports := DisplayablePorts(container)
status := container.State
if status == "running" && container.Health != "" {
status = fmt.Sprintf("%s (%s)", container.State, container.Health)
@ -162,7 +155,7 @@ func writter(containers []api.ContainerSummary) func(w io.Writer) {
status = fmt.Sprintf("%s (%d)", container.State, container.ExitCode)
}
command := formatter2.Ellipsis(container.Command, 20)
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", container.Name, strconv.Quote(command), container.Service, status, strings.Join(ports, ", "))
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", container.Name, strconv.Quote(command), container.Service, status, ports)
}
}
}
@ -182,3 +175,73 @@ func filterByStatus(containers []api.ContainerSummary, status string) []api.Cont
}
return filtered
}
type portRange struct {
pStart int
pEnd int
tStart int
tEnd int
IP string
protocol string
}
func (pr portRange) String() string {
var (
pub string
tgt string
)
if pr.pEnd > pr.pStart {
pub = fmt.Sprintf("%s:%d-%d->", pr.IP, pr.pStart, pr.pEnd)
} else if pr.pStart > 0 {
pub = fmt.Sprintf("%s:%d->", pr.IP, pr.pStart)
}
if pr.tEnd > pr.tStart {
tgt = fmt.Sprintf("%d-%d", pr.tStart, pr.tEnd)
} else {
tgt = fmt.Sprintf("%d", pr.tStart)
}
return fmt.Sprintf("%s%s/%s", pub, tgt, pr.protocol)
}
// DisplayablePorts is copy pasted from https://github.com/docker/cli/pull/581/files
func DisplayablePorts(c api.ContainerSummary) string {
if c.Publishers == nil {
return ""
}
sort.Sort(c.Publishers)
pr := portRange{}
ports := []string{}
for _, p := range c.Publishers {
prIsRange := pr.tEnd != pr.tStart
tOverlaps := p.TargetPort <= pr.tEnd
// Start a new port-range if:
// - the protocol is different from the current port-range
// - published or target port are not consecutive to the current port-range
// - the current port-range is a _range_, and the target port overlaps with the current range's target-ports
if p.Protocol != pr.protocol || p.URL != pr.IP || p.PublishedPort-pr.pEnd > 1 || p.TargetPort-pr.tEnd > 1 || prIsRange && tOverlaps {
// start a new port-range, and print the previous port-range (if any)
if pr.pStart > 0 {
ports = append(ports, pr.String())
}
pr = portRange{
pStart: p.PublishedPort,
pEnd: p.PublishedPort,
tStart: p.TargetPort,
tEnd: p.TargetPort,
protocol: p.Protocol,
IP: p.URL,
}
continue
}
pr.pEnd = p.PublishedPort
pr.tEnd = p.TargetPort
}
if pr.tStart > 0 {
ports = append(ports, pr.String())
}
return strings.Join(ports, ", ")
}

View File

@ -297,7 +297,36 @@ type ContainerSummary struct {
State string
Health string
ExitCode int
Publishers []PortPublisher
Publishers PortPublishers
}
// PortPublishers is a slice of PortPublisher
type PortPublishers []PortPublisher
// Len implements sort.Interface
func (p PortPublishers) Len() int {
return len(p)
}
// Less implements sort.Interface
func (p PortPublishers) Less(i, j int) bool {
left := p[i]
right := p[j]
if left.URL != right.URL {
return left.URL < right.URL
}
if left.TargetPort != right.TargetPort {
return left.TargetPort < right.TargetPort
}
if left.PublishedPort != right.PublishedPort {
return left.PublishedPort < right.PublishedPort
}
return left.Protocol < right.Protocol
}
// Swap implements sort.Interface
func (p PortPublishers) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
// ContainerProcSummary holds container processes top data

View File

@ -18,7 +18,6 @@ package compose
import (
"context"
"fmt"
"sort"
"golang.org/x/sync/errgroup"
@ -46,12 +45,8 @@ func (s *composeService) Ps(ctx context.Context, projectName string, options api
return container.Ports[i].PrivatePort < container.Ports[j].PrivatePort
})
for _, p := range container.Ports {
var url string
if p.PublicPort != 0 {
url = fmt.Sprintf("%s:%d", p.IP, p.PublicPort)
}
publishers = append(publishers, api.PortPublisher{
URL: url,
URL: p.IP,
TargetPort: int(p.PrivatePort),
PublishedPort: int(p.PublicPort),
Protocol: p.Type,

View File

@ -54,7 +54,7 @@ func TestPs(t *testing.T) {
expected := []compose.ContainerSummary{
{ID: "123", Name: "123", Project: strings.ToLower(testProject), Service: "service1", State: "running", Health: "healthy", Publishers: nil},
{ID: "456", Name: "456", Project: strings.ToLower(testProject), Service: "service1", State: "running", Health: "", Publishers: []compose.PortPublisher{{URL: "localhost:80", TargetPort: 90,
{ID: "456", Name: "456", Project: strings.ToLower(testProject), Service: "service1", State: "running", Health: "", Publishers: []compose.PortPublisher{{URL: "localhost", TargetPort: 90,
PublishedPort: 80}}},
{ID: "789", Name: "789", Project: strings.ToLower(testProject), Service: "service2", State: "exited", Health: "", ExitCode: 130, Publishers: nil},
}