From 4ee2010111af602988bf9bedf66d1a0c36419b09 Mon Sep 17 00:00:00 2001 From: Mikalai Radchuk Date: Mon, 12 Nov 2018 00:06:35 +0000 Subject: [PATCH] Fixes address parsing in port-forward The rules for address parsing are: * Explicitly specified addresses must bind successfully * `localhost` is pinned to `127.0.0.1` and `::1` and at least one of those must bind successfully This change also makes output of the command consistent between runs with the same arguments. Previously the command was using the range via map of addresses which sometimes was producing different output because the order of values is not guaranteed in Go. --- pkg/kubectl/cmd/portforward/portforward.go | 2 +- .../tools/portforward/portforward.go | 16 ++++-- .../tools/portforward/portforward_test.go | 56 +++++++++++++++++++ 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/pkg/kubectl/cmd/portforward/portforward.go b/pkg/kubectl/cmd/portforward/portforward.go index ccd925207a7..d31a0193488 100644 --- a/pkg/kubectl/cmd/portforward/portforward.go +++ b/pkg/kubectl/cmd/portforward/portforward.go @@ -120,7 +120,7 @@ func NewCmdPortForward(f cmdutil.Factory, streams genericclioptions.IOStreams) * }, } cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodPortForwardWaitTimeout) - cmd.Flags().StringSliceVar(&opts.Address, "address", []string{"localhost"}, "Addresses to listen on (comma separated)") + cmd.Flags().StringSliceVar(&opts.Address, "address", []string{"localhost"}, "Addresses to listen on (comma separated). Only accepts IP addresses or localhost as a value. When localhost is supplied, kubectl will try to bind on both 127.0.0.1 and ::1 and will fail if neither of these addresses are available to bind.") // TODO support UID return cmd } diff --git a/staging/src/k8s.io/client-go/tools/portforward/portforward.go b/staging/src/k8s.io/client-go/tools/portforward/portforward.go index 0e9b369a983..357680ede04 100644 --- a/staging/src/k8s.io/client-go/tools/portforward/portforward.go +++ b/staging/src/k8s.io/client-go/tools/portforward/portforward.go @@ -23,6 +23,7 @@ import ( "io/ioutil" "net" "net/http" + "sort" "strconv" "strings" "sync" @@ -122,10 +123,14 @@ func parseAddresses(addressesToParse []string) ([]listenAddress, error) { parsed := make(map[string]listenAddress) for _, address := range addressesToParse { if address == "localhost" { - ip := listenAddress{address: "127.0.0.1", protocol: "tcp4", failureMode: "all"} - parsed[ip.address] = ip - ip = listenAddress{address: "::1", protocol: "tcp6", failureMode: "all"} - parsed[ip.address] = ip + if _, exists := parsed["127.0.0.1"]; !exists { + ip := listenAddress{address: "127.0.0.1", protocol: "tcp4", failureMode: "all"} + parsed[ip.address] = ip + } + if _, exists := parsed["::1"]; !exists { + ip := listenAddress{address: "::1", protocol: "tcp6", failureMode: "all"} + parsed[ip.address] = ip + } } else if net.ParseIP(address).To4() != nil { parsed[address] = listenAddress{address: address, protocol: "tcp4", failureMode: "any"} } else if net.ParseIP(address) != nil { @@ -140,6 +145,9 @@ func parseAddresses(addressesToParse []string) ([]listenAddress, error) { addresses[id] = v id++ } + // Sort addresses before returning to get a stable order + sort.Slice(addresses, func(i, j int) bool { return addresses[i].address < addresses[j].address }) + return addresses, nil } diff --git a/staging/src/k8s.io/client-go/tools/portforward/portforward_test.go b/staging/src/k8s.io/client-go/tools/portforward/portforward_test.go index ff2401e76bf..dd8d4fd5e5b 100644 --- a/staging/src/k8s.io/client-go/tools/portforward/portforward_test.go +++ b/staging/src/k8s.io/client-go/tools/portforward/portforward_test.go @@ -83,6 +83,62 @@ func TestParsePortsAndNew(t *testing.T) { {protocol: "tcp6", address: "::1", failureMode: "all"}, }, }, + { + input: []string{"5000:5000"}, + addresses: []string{"localhost", "::1"}, + expectedPorts: []ForwardedPort{ + {5000, 5000}, + }, + expectedAddresses: []listenAddress{ + {protocol: "tcp4", address: "127.0.0.1", failureMode: "all"}, + {protocol: "tcp6", address: "::1", failureMode: "any"}, + }, + }, + { + input: []string{"5000:5000"}, + addresses: []string{"localhost", "127.0.0.1", "::1"}, + expectedPorts: []ForwardedPort{ + {5000, 5000}, + }, + expectedAddresses: []listenAddress{ + {protocol: "tcp4", address: "127.0.0.1", failureMode: "any"}, + {protocol: "tcp6", address: "::1", failureMode: "any"}, + }, + }, + { + input: []string{"5000:5000"}, + addresses: []string{"localhost", "127.0.0.1", "10.10.10.1"}, + expectedPorts: []ForwardedPort{ + {5000, 5000}, + }, + expectedAddresses: []listenAddress{ + {protocol: "tcp4", address: "127.0.0.1", failureMode: "any"}, + {protocol: "tcp6", address: "::1", failureMode: "all"}, + {protocol: "tcp4", address: "10.10.10.1", failureMode: "any"}, + }, + }, + { + input: []string{"5000:5000"}, + addresses: []string{"127.0.0.1", "::1", "localhost"}, + expectedPorts: []ForwardedPort{ + {5000, 5000}, + }, + expectedAddresses: []listenAddress{ + {protocol: "tcp4", address: "127.0.0.1", failureMode: "any"}, + {protocol: "tcp6", address: "::1", failureMode: "any"}, + }, + }, + { + input: []string{"5000:5000"}, + addresses: []string{"10.0.0.1", "127.0.0.1"}, + expectedPorts: []ForwardedPort{ + {5000, 5000}, + }, + expectedAddresses: []listenAddress{ + {protocol: "tcp4", address: "10.0.0.1", failureMode: "any"}, + {protocol: "tcp4", address: "127.0.0.1", failureMode: "any"}, + }, + }, { input: []string{"5000", "5000:5000", "8888:5000", "5000:8888", ":5000", "0:5000"}, addresses: []string{"127.0.0.1", "::1"},