From f0435fd3f32dc34ceb67677a3a7ead50fab10977 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 1 Dec 2022 10:20:57 +0100 Subject: [PATCH 1/4] cli/command/container: runPort(): update godoc, and add todo We should consider showing all mappings for a given port if no specific proto was specified. Signed-off-by: Sebastiaan van Stijn --- cli/command/container/port.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cli/command/container/port.go b/cli/command/container/port.go index cee721390e..12d227707e 100644 --- a/cli/command/container/port.go +++ b/cli/command/container/port.go @@ -43,6 +43,12 @@ func NewPortCommand(dockerCli command.Cli) *cobra.Command { return cmd } +// runPort shows the port mapping for a given container. Optionally, it +// allows showing the mapping for a specific (container)port and proto. +// +// TODO(thaJeztah): currently this defaults to show the TCP port if no +// proto is specified. We should consider changing this to "any" protocol +// for the given private port. func runPort(dockerCli command.Cli, opts *portOptions) error { ctx := context.Background() From 58487e088a825d6c6cd739a9a769d0c613fba4bf Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 1 Dec 2022 10:33:07 +0100 Subject: [PATCH 2/4] cli/command/container: runPort(): slight refactor - use strings.Cut - don't use nat.NewPort as we don't accept port ranges - use an early return if there's no results Signed-off-by: Sebastiaan van Stijn --- cli/command/container/port.go | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/cli/command/container/port.go b/cli/command/container/port.go index 12d227707e..26ed40c890 100644 --- a/cli/command/container/port.go +++ b/cli/command/container/port.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net" + "strconv" "strings" "github.com/docker/cli/cli" @@ -58,31 +59,26 @@ func runPort(dockerCli command.Cli, opts *portOptions) error { } if opts.port != "" { - port := opts.port - proto := "tcp" - parts := strings.SplitN(port, "/", 2) - - if len(parts) == 2 && len(parts[1]) != 0 { - port = parts[0] - proto = parts[1] + port, proto, _ := strings.Cut(opts.port, "/") + if proto == "" { + proto = "tcp" } - natPort := port + "/" + proto - newP, err := nat.NewPort(proto, port) - if err != nil { - return err + if _, err = strconv.ParseUint(port, 10, 16); err != nil { + return errors.Wrapf(err, "Error: invalid port (%s)", port) } - if frontends, exists := c.NetworkSettings.Ports[newP]; exists && frontends != nil { - for _, frontend := range frontends { - fmt.Fprintln(dockerCli.Out(), net.JoinHostPort(frontend.HostIP, frontend.HostPort)) - } - return nil + frontends, exists := c.NetworkSettings.Ports[nat.Port(port+"/"+proto)] + if !exists || frontends == nil { + return errors.Errorf("Error: No public port '%s' published for %s", opts.port, opts.container) } - return errors.Errorf("Error: No public port '%s' published for %s", natPort, opts.container) + for _, frontend := range frontends { + _, _ = fmt.Fprintln(dockerCli.Out(), net.JoinHostPort(frontend.HostIP, frontend.HostPort)) + } + return nil } for from, frontends := range c.NetworkSettings.Ports { for _, frontend := range frontends { - fmt.Fprintf(dockerCli.Out(), "%s -> %s\n", from, net.JoinHostPort(frontend.HostIP, frontend.HostPort)) + _, _ = fmt.Fprintf(dockerCli.Out(), "%s -> %s\n", from, net.JoinHostPort(frontend.HostIP, frontend.HostPort)) } } From c5613ac03239fe4d038f0c088ae54e408c9a4848 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 1 Dec 2022 10:47:10 +0100 Subject: [PATCH 3/4] cli/command/container: TestNewPortCommandOutput improve test Make sure that the container has multiple port-mappings to illustrate that only the given port is matched. Signed-off-by: Sebastiaan van Stijn --- cli/command/container/port_test.go | 21 +++++++++++++++++-- ...ontainer-port-ipv6-and-ipv4-443-udp.golden | 2 ++ 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 cli/command/container/testdata/container-port-ipv6-and-ipv4-443-udp.golden diff --git a/cli/command/container/port_test.go b/cli/command/container/port_test.go index 8ae06a5fb7..60a7ebcca3 100644 --- a/cli/command/container/port_test.go +++ b/cli/command/container/port_test.go @@ -15,18 +15,27 @@ func TestNewPortCommandOutput(t *testing.T) { testCases := []struct { name string ips []string + port string }{ { name: "container-port-ipv4", ips: []string{"0.0.0.0"}, + port: "80", }, { name: "container-port-ipv6", ips: []string{"::"}, + port: "80", }, { name: "container-port-ipv6-and-ipv4", ips: []string{"::", "0.0.0.0"}, + port: "80", + }, + { + name: "container-port-ipv6-and-ipv4-443-udp", + ips: []string{"::", "0.0.0.0"}, + port: "443/udp", }, } for _, tc := range testCases { @@ -36,19 +45,27 @@ func TestNewPortCommandOutput(t *testing.T) { inspectFunc: func(string) (types.ContainerJSON, error) { ci := types.ContainerJSON{NetworkSettings: &types.NetworkSettings{}} ci.NetworkSettings.Ports = nat.PortMap{ - "80/tcp": make([]nat.PortBinding, len(tc.ips)), + "80/tcp": make([]nat.PortBinding, len(tc.ips)), + "443/tcp": make([]nat.PortBinding, len(tc.ips)), + "443/udp": make([]nat.PortBinding, len(tc.ips)), } for i, ip := range tc.ips { ci.NetworkSettings.Ports["80/tcp"][i] = nat.PortBinding{ HostIP: ip, HostPort: "3456", } + ci.NetworkSettings.Ports["443/tcp"][i] = nat.PortBinding{ + HostIP: ip, HostPort: "4567", + } + ci.NetworkSettings.Ports["443/udp"][i] = nat.PortBinding{ + HostIP: ip, HostPort: "5678", + } } return ci, nil }, }, test.EnableContentTrust) cmd := NewPortCommand(cli) cmd.SetErr(io.Discard) - cmd.SetArgs([]string{"some_container", "80"}) + cmd.SetArgs([]string{"some_container", tc.port}) err := cmd.Execute() assert.NilError(t, err) golden.Assert(t, cli.OutBuffer().String(), tc.name+".golden") diff --git a/cli/command/container/testdata/container-port-ipv6-and-ipv4-443-udp.golden b/cli/command/container/testdata/container-port-ipv6-and-ipv4-443-udp.golden new file mode 100644 index 0000000000..fae92a6034 --- /dev/null +++ b/cli/command/container/testdata/container-port-ipv6-and-ipv4-443-udp.golden @@ -0,0 +1,2 @@ +[::]:5678 +0.0.0.0:5678 From 1768240bcd328db7744eed62163450c767ae5770 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Thu, 1 Dec 2022 10:58:40 +0100 Subject: [PATCH 4/4] cli/command/container: runPort: sort ports before printing Signed-off-by: Sebastiaan van Stijn --- cli/command/container/port.go | 21 +++++++++++++------ cli/command/container/port_test.go | 4 ++++ .../testdata/container-port-all-ports.golden | 6 ++++++ ...ontainer-port-ipv6-and-ipv4-443-udp.golden | 2 +- .../container-port-ipv6-and-ipv4.golden | 2 +- 5 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 cli/command/container/testdata/container-port-all-ports.golden diff --git a/cli/command/container/port.go b/cli/command/container/port.go index 26ed40c890..e8e39efa21 100644 --- a/cli/command/container/port.go +++ b/cli/command/container/port.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net" + "sort" "strconv" "strings" @@ -11,6 +12,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" "github.com/docker/go-connections/nat" + "github.com/fvbommel/sortorder" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -58,6 +60,7 @@ func runPort(dockerCli command.Cli, opts *portOptions) error { return err } + var out []string if opts.port != "" { port, proto, _ := strings.Cut(opts.port, "/") if proto == "" { @@ -71,15 +74,21 @@ func runPort(dockerCli command.Cli, opts *portOptions) error { return errors.Errorf("Error: No public port '%s' published for %s", opts.port, opts.container) } for _, frontend := range frontends { - _, _ = fmt.Fprintln(dockerCli.Out(), net.JoinHostPort(frontend.HostIP, frontend.HostPort)) + out = append(out, net.JoinHostPort(frontend.HostIP, frontend.HostPort)) + } + } else { + for from, frontends := range c.NetworkSettings.Ports { + for _, frontend := range frontends { + out = append(out, fmt.Sprintf("%s -> %s", from, net.JoinHostPort(frontend.HostIP, frontend.HostPort))) + } } - return nil } - for from, frontends := range c.NetworkSettings.Ports { - for _, frontend := range frontends { - _, _ = fmt.Fprintf(dockerCli.Out(), "%s -> %s\n", from, net.JoinHostPort(frontend.HostIP, frontend.HostPort)) - } + if len(out) > 0 { + sort.Slice(out, func(i, j int) bool { + return sortorder.NaturalLess(out[i], out[j]) + }) + _, _ = fmt.Fprintln(dockerCli.Out(), strings.Join(out, "\n")) } return nil diff --git a/cli/command/container/port_test.go b/cli/command/container/port_test.go index 60a7ebcca3..66e601c543 100644 --- a/cli/command/container/port_test.go +++ b/cli/command/container/port_test.go @@ -37,6 +37,10 @@ func TestNewPortCommandOutput(t *testing.T) { ips: []string{"::", "0.0.0.0"}, port: "443/udp", }, + { + name: "container-port-all-ports", + ips: []string{"::", "0.0.0.0"}, + }, } for _, tc := range testCases { tc := tc diff --git a/cli/command/container/testdata/container-port-all-ports.golden b/cli/command/container/testdata/container-port-all-ports.golden new file mode 100644 index 0000000000..7342bbe963 --- /dev/null +++ b/cli/command/container/testdata/container-port-all-ports.golden @@ -0,0 +1,6 @@ +80/tcp -> 0.0.0.0:3456 +80/tcp -> [::]:3456 +443/tcp -> 0.0.0.0:4567 +443/tcp -> [::]:4567 +443/udp -> 0.0.0.0:5678 +443/udp -> [::]:5678 diff --git a/cli/command/container/testdata/container-port-ipv6-and-ipv4-443-udp.golden b/cli/command/container/testdata/container-port-ipv6-and-ipv4-443-udp.golden index fae92a6034..5518923f85 100644 --- a/cli/command/container/testdata/container-port-ipv6-and-ipv4-443-udp.golden +++ b/cli/command/container/testdata/container-port-ipv6-and-ipv4-443-udp.golden @@ -1,2 +1,2 @@ -[::]:5678 0.0.0.0:5678 +[::]:5678 diff --git a/cli/command/container/testdata/container-port-ipv6-and-ipv4.golden b/cli/command/container/testdata/container-port-ipv6-and-ipv4.golden index fdb6c56bb4..27b2f79729 100644 --- a/cli/command/container/testdata/container-port-ipv6-and-ipv4.golden +++ b/cli/command/container/testdata/container-port-ipv6-and-ipv4.golden @@ -1,2 +1,2 @@ -[::]:3456 0.0.0.0:3456 +[::]:3456