Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions cmd/compose/port.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package compose
import (
"context"
"fmt"
"net"
"sort"
"strconv"
"strings"

Expand All @@ -41,16 +43,20 @@ func portCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backe
ProjectOptions: p,
}
cmd := &cobra.Command{
Use: "port [OPTIONS] SERVICE PRIVATE_PORT",
Short: "Print the public port for a port binding",
Args: cobra.MinimumNArgs(2),
Use: "port [OPTIONS] SERVICE [PRIVATE_PORT]",
Short: "List port mappings or print the public port of a specific mapping for the service",
Args: cobra.RangeArgs(1, 2),
PreRunE: Adapt(func(ctx context.Context, args []string) error {
port, err := strconv.ParseUint(args[1], 10, 16)
if err != nil {
return err
}
opts.port = uint16(port)
opts.protocol = strings.ToLower(opts.protocol)
if len(args) > 1 {
port, err := strconv.ParseUint(args[1], 10, 16)
if err != nil {
return err
}
opts.port = uint16(port)
} else {
opts.protocol = ""
}
return nil
}),
RunE: Adapt(func(ctx context.Context, args []string) error {
Expand All @@ -73,14 +79,22 @@ func runPort(ctx context.Context, dockerCli command.Cli, backendOptions *Backend
if err != nil {
return err
}
ip, port, err := backend.Port(ctx, projectName, service, opts.port, api.PortOptions{
publishers, err := backend.Ports(ctx, projectName, service, opts.port, api.PortOptions{
Protocol: opts.protocol,
Index: opts.index,
})
if err != nil {
return err
}

_, _ = fmt.Fprintf(dockerCli.Out(), "%s:%d\n", ip, port)
if opts.port != 0 && len(publishers) > 0 {
p := publishers[0]
_, _ = fmt.Fprintf(dockerCli.Out(), "%s\n", net.JoinHostPort(p.URL, strconv.Itoa(p.PublishedPort)))
return nil
}
sort.Sort(publishers)
for _, p := range publishers {
_, _ = fmt.Fprintln(dockerCli.Out(), p.String())
}
return nil
}
2 changes: 1 addition & 1 deletion docs/reference/compose.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Define and run multi-container applications with Docker
| [`logs`](compose_logs.md) | View output from containers |
| [`ls`](compose_ls.md) | List running compose projects |
| [`pause`](compose_pause.md) | Pause services |
| [`port`](compose_port.md) | Print the public port for a port binding |
| [`port`](compose_port.md) | List port mappings or print the public port of a specific mapping for the service |
| [`ps`](compose_ps.md) | List containers |
| [`publish`](compose_publish.md) | Publish compose application |
| [`pull`](compose_pull.md) | Pull service images |
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/compose_port.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# docker compose port

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

### Options

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

## Description

Prints the public port for a port binding
List port mappings or print the public port of a specific mapping for the service
8 changes: 5 additions & 3 deletions docs/reference/docker_compose_port.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
command: docker compose port
short: Print the public port for a port binding
long: Prints the public port for a port binding
usage: docker compose port [OPTIONS] SERVICE PRIVATE_PORT
short: |
List port mappings or print the public port of a specific mapping for the service
long: |
List port mappings or print the public port of a specific mapping for the service
usage: docker compose port [OPTIONS] SERVICE [PRIVATE_PORT]
pname: docker compose
plink: docker_compose.yaml
options:
Expand Down
10 changes: 8 additions & 2 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import (
"context"
"fmt"
"io"
"net"
"slices"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -124,8 +126,8 @@ type Compose interface {
Top(ctx context.Context, projectName string, services []string) ([]ContainerProcSummary, error)
// Events executes the equivalent to a `compose events`
Events(ctx context.Context, projectName string, options EventsOptions) error
// Port executes the equivalent to a `compose port`
Port(ctx context.Context, projectName string, service string, port uint16, options PortOptions) (string, int, error)
// Ports executes the equivalent to a `compose port`
Ports(ctx context.Context, projectName string, service string, port uint16, options PortOptions) (PortPublishers, error)
// Publish executes the equivalent to a `compose publish`
Publish(ctx context.Context, project *types.Project, repository string, options PublishOptions) error
// Images executes the equivalent of a `compose images`
Expand Down Expand Up @@ -534,6 +536,10 @@ type PortPublisher struct {
Protocol string
}

func (p PortPublisher) String() string {
return fmt.Sprintf("%d/%s -> %s", p.TargetPort, p.Protocol, net.JoinHostPort(p.URL, strconv.Itoa(p.PublishedPort)))
}

// ContainerSummary hold high-level description of a container
type ContainerSummary struct {
ID string
Expand Down
16 changes: 16 additions & 0 deletions pkg/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ import (
"gotest.tools/v3/assert"
)

func TestPortPublisherString(t *testing.T) {
tests := []struct {
name string
pub PortPublisher
want string
}{
{"ipv4", PortPublisher{URL: "0.0.0.0", TargetPort: 80, PublishedPort: 8080, Protocol: "tcp"}, "80/tcp -> 0.0.0.0:8080"},
{"ipv6", PortPublisher{URL: "::", TargetPort: 5060, PublishedPort: 32769, Protocol: "udp"}, "5060/udp -> [::]:32769"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.pub.String(), tt.want)
})
}
}

func TestRunOptionsEnvironmentMap(t *testing.T) {
opts := RunOptions{
Environment: []string{
Expand Down
32 changes: 27 additions & 5 deletions pkg/compose/port.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,40 @@ import (
"github.com/docker/compose/v5/pkg/api"
)

func (s *composeService) Port(ctx context.Context, projectName string, service string, port uint16, options api.PortOptions) (string, int, error) {
func (s *composeService) Ports(ctx context.Context, projectName string, service string, port uint16, options api.PortOptions) (api.PortPublishers, error) {
projectName = strings.ToLower(projectName)
ctr, err := s.getSpecifiedContainer(ctx, projectName, oneOffInclude, false, service, options.Index)
if err != nil {
return "", 0, err
return nil, err
}

if port != 0 {
for _, p := range ctr.Ports {
if p.PrivatePort == port && p.Type == options.Protocol {
return api.PortPublishers{{
URL: p.IP,
TargetPort: int(p.PrivatePort),
PublishedPort: int(p.PublicPort),
Protocol: p.Type,
}}, nil
}
}
return nil, portNotFoundError(options.Protocol, port, ctr)
}

var publishers api.PortPublishers
for _, p := range ctr.Ports {
if p.PrivatePort == port && p.Type == options.Protocol {
return p.IP, int(p.PublicPort), nil
if options.Protocol != "" && p.Type != options.Protocol {
continue
}
publishers = append(publishers, api.PortPublisher{
URL: p.IP,
TargetPort: int(p.PrivatePort),
PublishedPort: int(p.PublicPort),
Protocol: p.Type,
})
}
return "", 0, portNotFoundError(options.Protocol, port, ctr)
return publishers, nil
}

func portNotFoundError(protocol string, port uint16, ctr container.Summary) error {
Expand Down
19 changes: 9 additions & 10 deletions pkg/mocks/mock_docker_compose_api.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.