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.
This commit is contained in:
Mikalai Radchuk 2018-11-12 00:06:35 +00:00
parent b862590bfc
commit 4ee2010111
3 changed files with 69 additions and 5 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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"},