diff --git a/cmd/skaffold/app/cmd/cmd.go b/cmd/skaffold/app/cmd/cmd.go index 15d597230c7..cdeb8265384 100644 --- a/cmd/skaffold/app/cmd/cmd.go +++ b/cmd/skaffold/app/cmd/cmd.go @@ -143,7 +143,7 @@ func AddRunDeployFlags(cmd *cobra.Command) { func AddRunDevFlags(cmd *cobra.Command) { cmd.Flags().BoolVar(&opts.EnableRPC, "enable-rpc", false, "Enable gRPC for exposing Skaffold events (true by default for `skaffold dev`)") - cmd.Flags().StringVar(&opts.RPCPort, "rpc-port", ":50051", "tcp port to expose event API") + cmd.Flags().StringVar(&opts.RPCPort, "rpc-port", constants.DefaultRPCPort, "tcp port to expose event API") cmd.Flags().StringVarP(&opts.ConfigurationFile, "filename", "f", "skaffold.yaml", "Filename or URL to the pipeline file") cmd.Flags().BoolVar(&opts.Notification, "toot", false, "Emit a terminal beep after the deploy is complete") cmd.Flags().StringArrayVarP(&opts.Profiles, "profile", "p", nil, "Activate profiles by name") diff --git a/pkg/skaffold/constants/constants.go b/pkg/skaffold/constants/constants.go index 0a2c35e44ae..dd818bb23bc 100644 --- a/pkg/skaffold/constants/constants.go +++ b/pkg/skaffold/constants/constants.go @@ -71,6 +71,8 @@ const ( SkaffoldPluginValue = "1337" SkaffoldPluginName = "SKAFFOLD_PLUGIN_NAME" DockerBuilderPluginName = "docker" + + DefaultRPCPort = ":50051" ) var ( diff --git a/pkg/skaffold/event/server.go b/pkg/skaffold/event/server.go index dc17b2f1ff6..8128ebd61c8 100644 --- a/pkg/skaffold/event/server.go +++ b/pkg/skaffold/event/server.go @@ -18,8 +18,13 @@ package event import ( "context" + "fmt" + "math/rand" "net" + "strconv" + "strings" + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/event/proto" empty "github.com/golang/protobuf/ptypes/empty" @@ -66,10 +71,12 @@ func newStatusServer(port string) (func() error, error) { if port == "" { return func() error { return nil }, nil } + port = getAvailablePort(port) l, err := net.Listen("tcp", port) if err != nil { return func() error { return nil }, errors.Wrap(err, "creating listener") } + logrus.Infof("starting gRPC server on port %s", port) s := grpc.NewServer() proto.RegisterSkaffoldServiceServer(s, &server{}) @@ -84,3 +91,37 @@ func newStatusServer(port string) (func() error, error) { return l.Close() }, nil } + +// getOpenPort tests the provided port for availability, +// and if it's already in use, finds another open port. +func getAvailablePort(port string) string { + ln, err := net.Listen("tcp", port) + if err != nil { + // if user provided non-default port, warn them that it is unavailable + if port != constants.DefaultRPCPort { + logrus.Warnf("provided port %s unavailable: finding another available port", port) + } + } else { + ln.Close() + return port + } + for { + var portNum int + logrus.Debugf("port %s already in use: attempting to find an available one", port) + port = strings.Replace(port, ":", "", -1) + portNum, err = strconv.Atoi(port) + if err == nil { + portNum++ + } + if err != nil || portNum > 65535 { + portNum = rand.Intn(64511) + 1024 // range [1024, 65535] + } + port = fmt.Sprintf(":%d", portNum) + ln, err := net.Listen("tcp", port) + if err == nil { + ln.Close() + logrus.Debugf("found open port: %s", port) + return port + } + } +}