Skip to content

Commit c350344

Browse files
committed
PRIVATE_PORT optional for docker compose port
Signed-off-by: Max Proske <max@mproske.com>
1 parent f9828df commit c350344

File tree

8 files changed

+92
-33
lines changed

8 files changed

+92
-33
lines changed

cmd/compose/port.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package compose
1919
import (
2020
"context"
2121
"fmt"
22+
"net"
23+
"sort"
2224
"strconv"
2325
"strings"
2426

@@ -41,16 +43,20 @@ func portCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
4143
ProjectOptions: p,
4244
}
4345
cmd := &cobra.Command{
44-
Use: "port [OPTIONS] SERVICE PRIVATE_PORT",
45-
Short: "Print the public port for a port binding",
46-
Args: cobra.MinimumNArgs(2),
46+
Use: "port [OPTIONS] SERVICE [PRIVATE_PORT]",
47+
Short: "List port mappings or print the public port of a specific mapping for the service",
48+
Args: cobra.RangeArgs(1, 2),
4749
PreRunE: Adapt(func(ctx context.Context, args []string) error {
48-
port, err := strconv.ParseUint(args[1], 10, 16)
49-
if err != nil {
50-
return err
51-
}
52-
opts.port = uint16(port)
5350
opts.protocol = strings.ToLower(opts.protocol)
51+
if len(args) > 1 {
52+
port, err := strconv.ParseUint(args[1], 10, 16)
53+
if err != nil {
54+
return err
55+
}
56+
opts.port = uint16(port)
57+
} else {
58+
opts.protocol = ""
59+
}
5460
return nil
5561
}),
5662
RunE: Adapt(func(ctx context.Context, args []string) error {
@@ -73,14 +79,22 @@ func runPort(ctx context.Context, dockerCli command.Cli, backendOptions *Backend
7379
if err != nil {
7480
return err
7581
}
76-
ip, port, err := backend.Port(ctx, projectName, service, opts.port, api.PortOptions{
82+
publishers, err := backend.Ports(ctx, projectName, service, opts.port, api.PortOptions{
7783
Protocol: opts.protocol,
7884
Index: opts.index,
7985
})
8086
if err != nil {
8187
return err
8288
}
8389

84-
_, _ = fmt.Fprintf(dockerCli.Out(), "%s:%d\n", ip, port)
90+
if opts.port != 0 && len(publishers) > 0 {
91+
p := publishers[0]
92+
_, _ = fmt.Fprintf(dockerCli.Out(), "%s\n", net.JoinHostPort(p.URL, strconv.Itoa(p.PublishedPort)))
93+
return nil
94+
}
95+
sort.Sort(publishers)
96+
for _, p := range publishers {
97+
_, _ = fmt.Fprintln(dockerCli.Out(), p.String())
98+
}
8599
return nil
86100
}

docs/reference/compose.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Define and run multi-container applications with Docker
2828
| [`logs`](compose_logs.md) | View output from containers |
2929
| [`ls`](compose_ls.md) | List running compose projects |
3030
| [`pause`](compose_pause.md) | Pause services |
31-
| [`port`](compose_port.md) | Print the public port for a port binding |
31+
| [`port`](compose_port.md) | List port mappings or print the public port of a specific mapping for the service |
3232
| [`ps`](compose_ps.md) | List containers |
3333
| [`publish`](compose_publish.md) | Publish compose application |
3434
| [`pull`](compose_pull.md) | Pull service images |

docs/reference/compose_port.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# docker compose port
22

33
<!---MARKER_GEN_START-->
4-
Prints the public port for a port binding
4+
List port mappings or print the public port of a specific mapping for the service
55

66
### Options
77

@@ -16,4 +16,4 @@ Prints the public port for a port binding
1616

1717
## Description
1818

19-
Prints the public port for a port binding
19+
List port mappings or print the public port of a specific mapping for the service

docs/reference/docker_compose_port.yaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
command: docker compose port
2-
short: Print the public port for a port binding
3-
long: Prints the public port for a port binding
4-
usage: docker compose port [OPTIONS] SERVICE PRIVATE_PORT
2+
short: |
3+
List port mappings or print the public port of a specific mapping for the service
4+
long: |
5+
List port mappings or print the public port of a specific mapping for the service
6+
usage: docker compose port [OPTIONS] SERVICE [PRIVATE_PORT]
57
pname: docker compose
68
plink: docker_compose.yaml
79
options:

pkg/api/api.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import (
2020
"context"
2121
"fmt"
2222
"io"
23+
"net"
2324
"slices"
25+
"strconv"
2426
"strings"
2527
"time"
2628

@@ -125,8 +127,8 @@ type Compose interface {
125127
Top(ctx context.Context, projectName string, services []string) ([]ContainerProcSummary, error)
126128
// Events executes the equivalent to a `compose events`
127129
Events(ctx context.Context, projectName string, options EventsOptions) error
128-
// Port executes the equivalent to a `compose port`
129-
Port(ctx context.Context, projectName string, service string, port uint16, options PortOptions) (string, int, error)
130+
// Ports executes the equivalent to a `compose port`
131+
Ports(ctx context.Context, projectName string, service string, port uint16, options PortOptions) (PortPublishers, error)
130132
// Publish executes the equivalent to a `compose publish`
131133
Publish(ctx context.Context, project *types.Project, repository string, options PublishOptions) error
132134
// Images executes the equivalent of a `compose images`
@@ -535,6 +537,10 @@ type PortPublisher struct {
535537
Protocol string
536538
}
537539

540+
func (p PortPublisher) String() string {
541+
return fmt.Sprintf("%d/%s -> %s", p.TargetPort, p.Protocol, net.JoinHostPort(p.URL, strconv.Itoa(p.PublishedPort)))
542+
}
543+
538544
// ContainerSummary hold high-level description of a container
539545
type ContainerSummary struct {
540546
ID string

pkg/api/api_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,22 @@ import (
2323
"gotest.tools/v3/assert"
2424
)
2525

26+
func TestPortPublisherString(t *testing.T) {
27+
tests := []struct {
28+
name string
29+
pub PortPublisher
30+
want string
31+
}{
32+
{"ipv4", PortPublisher{URL: "0.0.0.0", TargetPort: 80, PublishedPort: 8080, Protocol: "tcp"}, "80/tcp -> 0.0.0.0:8080"},
33+
{"ipv6", PortPublisher{URL: "::", TargetPort: 5060, PublishedPort: 32769, Protocol: "udp"}, "5060/udp -> [::]:32769"},
34+
}
35+
for _, tt := range tests {
36+
t.Run(tt.name, func(t *testing.T) {
37+
assert.Equal(t, tt.pub.String(), tt.want)
38+
})
39+
}
40+
}
41+
2642
func TestRunOptionsEnvironmentMap(t *testing.T) {
2743
opts := RunOptions{
2844
Environment: []string{

pkg/compose/port.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,40 @@ import (
2626
"github.com/docker/compose/v5/pkg/api"
2727
)
2828

29-
func (s *composeService) Port(ctx context.Context, projectName string, service string, port uint16, options api.PortOptions) (string, int, error) {
29+
func (s *composeService) Ports(ctx context.Context, projectName string, service string, port uint16, options api.PortOptions) (api.PortPublishers, error) {
3030
projectName = strings.ToLower(projectName)
3131
ctr, err := s.getSpecifiedContainer(ctx, projectName, oneOffInclude, false, service, options.Index)
3232
if err != nil {
33-
return "", 0, err
33+
return nil, err
3434
}
35+
36+
if port != 0 {
37+
for _, p := range ctr.Ports {
38+
if p.PrivatePort == port && p.Type == options.Protocol {
39+
return api.PortPublishers{{
40+
URL: p.IP.String(),
41+
TargetPort: int(p.PrivatePort),
42+
PublishedPort: int(p.PublicPort),
43+
Protocol: p.Type,
44+
}}, nil
45+
}
46+
}
47+
return nil, portNotFoundError(options.Protocol, port, ctr)
48+
}
49+
50+
var publishers api.PortPublishers
3551
for _, p := range ctr.Ports {
36-
if p.PrivatePort == port && p.Type == options.Protocol {
37-
return p.IP.String(), int(p.PublicPort), nil
52+
if options.Protocol != "" && p.Type != options.Protocol {
53+
continue
3854
}
55+
publishers = append(publishers, api.PortPublisher{
56+
URL: p.IP.String(),
57+
TargetPort: int(p.PrivatePort),
58+
PublishedPort: int(p.PublicPort),
59+
Protocol: p.Type,
60+
})
3961
}
40-
return "", 0, portNotFoundError(options.Protocol, port, ctr)
62+
return publishers, nil
4163
}
4264

4365
func portNotFoundError(protocol string, port uint16, ctr container.Summary) error {

pkg/mocks/mock_docker_compose_api.go

Lines changed: 9 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)