diff --git a/cmd/create/createCluster.go b/cmd/create/createCluster.go index f05e2c57..9cb02cff 100644 --- a/cmd/create/createCluster.go +++ b/cmd/create/createCluster.go @@ -110,7 +110,7 @@ func NewCmdCreateCluster() *cobra.Command { /********* * Flags * *********/ - cmd.Flags().StringP("api-port", "a", k3d.DefaultAPIPort, "Specify the Kubernetes API server port exposed on the LoadBalancer (Format: `--api-port [HOST:]HOSTPORT`)\n - Example: `k3d create -m 3 -a 0.0.0.0:6550`") + cmd.Flags().StringP("api-port", "a", "random", "Specify the Kubernetes API server port exposed on the LoadBalancer (Format: `--api-port [HOST:]HOSTPORT`)\n - Example: `k3d create -m 3 -a 0.0.0.0:6550`") cmd.Flags().IntP("masters", "m", 1, "Specify how many masters you want to create") cmd.Flags().IntP("workers", "w", 0, "Specify how many workers you want to create") cmd.Flags().StringP("image", "i", fmt.Sprintf("%s:%s", k3d.DefaultK3sImageRepo, version.GetK3sVersion(false)), "Specify k3s image that you want to use for the nodes") diff --git a/cmd/util/ports.go b/cmd/util/ports.go index b77fb30c..1728b784 100644 --- a/cmd/util/ports.go +++ b/cmd/util/ports.go @@ -54,14 +54,27 @@ func ParseAPIPort(portString string) (k3d.ExposeAPI, error) { } // Verify 'port' is an integer and within port ranges + if exposeAPI.Port == "" || exposeAPI.Port == "random" { + log.Debugf("API-Port Mapping didn't specify hostPort, choosing one randomly...") + freePort, err := GetFreePort() + if err != nil || freePort == 0 { + log.Warnf("Failed to get random free port:\n%+v", err) + log.Warnf("Falling back to default port %s (may be blocked though)...", k3d.DefaultAPIPort) + exposeAPI.Port = k3d.DefaultAPIPort + } else { + exposeAPI.Port = strconv.Itoa(freePort) + log.Debugf("Got free port for API: '%d'", freePort) + } + } p, err := strconv.Atoi(exposeAPI.Port) if err != nil { + log.Errorln("Failed to parse port mapping") return exposeAPI, err } if p < 0 || p > 65535 { log.Errorln("Failed to parse API Port specification") - return exposeAPI, fmt.Errorf("port value '%d' out of range", p) + return exposeAPI, fmt.Errorf("Port value '%d' out of range", p) } return exposeAPI, nil @@ -72,3 +85,21 @@ func ParseAPIPort(portString string) (k3d.ExposeAPI, error) { func ValidatePortMap(portmap string) (string, error) { return portmap, nil // TODO: ValidatePortMap: add validation of port mapping } + +// GetFreePort tries to fetch an open port from the OS-Kernel +func GetFreePort() (int, error) { + tcpAddress, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + log.Errorln("Failed to resolve address") + return 0, err + } + + tcpListener, err := net.ListenTCP("tcp", tcpAddress) + if err != nil { + log.Errorln("Failed to create TCP Listener") + return 0, err + } + defer tcpListener.Close() + + return tcpListener.Addr().(*net.TCPAddr).Port, nil +} diff --git a/tests/test_basic.sh b/tests/test_basic.sh index bdf9b930..d615ed61 100755 --- a/tests/test_basic.sh +++ b/tests/test_basic.sh @@ -8,7 +8,7 @@ source "$CURR_DIR/common.sh" info "Creating two clusters..." $EXE create cluster c1 --wait --timeout 60s --api-port 6443 || failed "could not create cluster c1" -$EXE create cluster c2 --wait --timeout 60s --api-port 6444 || failed "could not create cluster c2" +$EXE create cluster c2 --wait --timeout 60s || failed "could not create cluster c2" info "Checking that we can get both clusters..." check_cluster_count 2