Skip to content

Commit 349571f

Browse files
committed
fix: make PORTS in nerdctl ps or nerdctl compose ps easier to view
Details are described in the following issue. - #4338 Signed-off-by: Hayato Kiwata <[email protected]>
1 parent 2165e30 commit 349571f

File tree

2 files changed

+153
-4
lines changed

2 files changed

+153
-4
lines changed

pkg/formatter/formatter.go

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"context"
2222
"encoding/json"
2323
"fmt"
24+
"sort"
2425
"strconv"
2526
"strings"
2627
"time"
@@ -110,15 +111,59 @@ func Ellipsis(str string, maxDisplayWidth int) string {
110111
return str[:maxDisplayWidth-1] + "…"
111112
}
112113

114+
func formatRange(startHost, endHost, startContainer, endContainer int32) string {
115+
if startHost == endHost && startContainer == endContainer {
116+
return fmt.Sprintf("%d->%d", startHost, startContainer)
117+
}
118+
return fmt.Sprintf("%d-%d->%d-%d", startHost, endHost, startContainer, endContainer)
119+
}
120+
113121
func FormatPorts(ports []cni.PortMapping) string {
114122
if len(ports) == 0 {
115123
return ""
116124
}
117-
strs := make([]string, len(ports))
118-
for i, p := range ports {
119-
strs[i] = fmt.Sprintf("%s:%d->%d/%s", p.HostIP, p.HostPort, p.ContainerPort, p.Protocol)
125+
126+
type key struct {
127+
HostIP string
128+
Protocol string
129+
}
130+
grouped := make(map[key][]cni.PortMapping)
131+
132+
for _, p := range ports {
133+
k := key{HostIP: p.HostIP, Protocol: p.Protocol}
134+
grouped[k] = append(grouped[k], p)
120135
}
121-
return strings.Join(strs, ", ")
136+
137+
var displayPorts []string
138+
for k, pms := range grouped {
139+
sort.Slice(pms, func(i, j int) bool {
140+
return pms[i].HostPort < pms[j].HostPort
141+
})
142+
143+
var i int
144+
var ranges []string
145+
for i = 0; i < len(pms); {
146+
start, end := pms[i], pms[i]
147+
for i+1 < len(pms) &&
148+
pms[i+1].HostPort == end.HostPort+1 &&
149+
pms[i+1].ContainerPort == end.ContainerPort+1 {
150+
i++
151+
end = pms[i]
152+
}
153+
154+
ranges = append(
155+
ranges,
156+
formatRange(start.HostPort, end.HostPort, start.ContainerPort, end.ContainerPort),
157+
)
158+
i++
159+
}
160+
displayPorts = append(
161+
displayPorts,
162+
fmt.Sprintf("%s:%s/%s", k.HostIP, strings.Join(ranges, ", "), k.Protocol),
163+
)
164+
}
165+
166+
return strings.Join(displayPorts, ", ")
122167
}
123168

124169
func TimeSinceInHuman(since time.Time) string {

pkg/formatter/formatter_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"testing"
2121
"time"
2222

23+
"github.com/containerd/go-cni"
2324
"gotest.tools/v3/assert"
2425
)
2526

@@ -87,3 +88,106 @@ func TestTimeSinceInHuman(t *testing.T) {
8788
})
8889
}
8990
}
91+
92+
func TestFormatPorts(t *testing.T) {
93+
t.Parallel()
94+
95+
tests := []struct {
96+
name string
97+
input []cni.PortMapping
98+
expected string
99+
}{
100+
{
101+
name: "a single tcp port on localhost",
102+
input: []cni.PortMapping{
103+
{
104+
HostPort: 3000,
105+
ContainerPort: 8080,
106+
Protocol: "tcp",
107+
HostIP: "127.0.0.1",
108+
},
109+
},
110+
expected: "127.0.0.1:3000->8080/tcp",
111+
},
112+
{
113+
name: "consecutive tcp ports on localhost",
114+
input: []cni.PortMapping{
115+
{
116+
HostPort: 3000,
117+
ContainerPort: 8080,
118+
Protocol: "tcp",
119+
HostIP: "127.0.0.1",
120+
},
121+
{
122+
HostPort: 3001,
123+
ContainerPort: 8081,
124+
Protocol: "tcp",
125+
HostIP: "127.0.0.1",
126+
},
127+
},
128+
expected: "127.0.0.1:3000-3001->8080-8081/tcp",
129+
},
130+
{
131+
name: "a single tcp port on anyhost",
132+
input: []cni.PortMapping{
133+
{
134+
HostPort: 3000,
135+
ContainerPort: 8080,
136+
Protocol: "tcp",
137+
HostIP: "0.0.0.0",
138+
},
139+
},
140+
expected: "0.0.0.0:3000->8080/tcp",
141+
},
142+
{
143+
name: "a single udp port on anyhost",
144+
input: []cni.PortMapping{
145+
{
146+
HostPort: 3000,
147+
ContainerPort: 8080,
148+
Protocol: "udp",
149+
HostIP: "0.0.0.0",
150+
},
151+
},
152+
expected: "0.0.0.0:3000->8080/udp",
153+
},
154+
{
155+
name: "mixed tcp and udp with consecutive ports on anyhost",
156+
input: []cni.PortMapping{
157+
{
158+
HostPort: 3000,
159+
ContainerPort: 8080,
160+
Protocol: "tcp",
161+
HostIP: "0.0.0.0",
162+
},
163+
{
164+
HostPort: 3001,
165+
ContainerPort: 8081,
166+
Protocol: "tcp",
167+
HostIP: "0.0.0.0",
168+
},
169+
{
170+
HostPort: 3002,
171+
ContainerPort: 8082,
172+
Protocol: "udp",
173+
HostIP: "0.0.0.0",
174+
},
175+
{
176+
HostPort: 3003,
177+
ContainerPort: 8083,
178+
Protocol: "udp",
179+
HostIP: "0.0.0.0",
180+
},
181+
},
182+
expected: "0.0.0.0:3000-3001->8080-8081/tcp, 0.0.0.0:3002-3003->8082-8083/udp",
183+
},
184+
}
185+
186+
for _, tt := range tests {
187+
t.Run(tt.name, func(t *testing.T) {
188+
t.Parallel()
189+
result := FormatPorts(tt.input)
190+
assert.Equal(t, tt.expected, result)
191+
})
192+
}
193+
}

0 commit comments

Comments
 (0)