diff --git a/cmd/minikube/cmd/dashboard.go b/cmd/minikube/cmd/dashboard.go index baccdf85b2..a082abee0a 100644 --- a/cmd/minikube/cmd/dashboard.go +++ b/cmd/minikube/cmd/dashboard.go @@ -22,6 +22,7 @@ import ( "time" "github.com/docker/machine/libmachine" + "github.com/golang/glog" "github.com/pkg/browser" "github.com/spf13/cobra" "k8s.io/minikube/pkg/minikube/cluster" @@ -52,19 +53,23 @@ var dashboardCmd = &cobra.Command{ os.Exit(1) } - url, err := cluster.GetServiceURL(api, namespace, service, nil) + urls, err := cluster.GetServiceURLs(api, namespace, service, nil) if err != nil { fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, "Check that minikube is running.") os.Exit(1) } + if len(urls) == 0 { + errMsg := "There appears to be no url associated with dashboard, this is not expected, exiting" + glog.Infoln(errMsg) + os.Exit(1) + } if dashboardURLMode { - fmt.Fprintln(os.Stdout, url) + fmt.Fprintln(os.Stdout, urls[0]) } else { fmt.Fprintln(os.Stdout, "Opening kubernetes dashboard in default browser...") - browser.OpenURL(url) + browser.OpenURL(urls[0]) } - }, } diff --git a/cmd/minikube/cmd/service.go b/cmd/minikube/cmd/service.go index 49cfa024ff..452db6b182 100644 --- a/cmd/minikube/cmd/service.go +++ b/cmd/minikube/cmd/service.go @@ -44,8 +44,8 @@ var ( // serviceCmd represents the service command var serviceCmd = &cobra.Command{ Use: "service [flags] SERVICE", - Short: "Gets the kubernetes URL for the specified service in your local cluster", - Long: `Gets the kubernetes URL for the specified service in your local cluster`, + Short: "Gets the kubernetes URL(s) for the specified service in your local cluster", + Long: `Gets the kubernetes URL(s) for the specified service in your local cluster. In the case of multiple URLs they will be printed one at a time`, PersistentPreRun: func(cmd *cobra.Command, args []string) { t, err := template.New("serviceURL").Parse(serviceURLFormat) if err != nil { @@ -76,20 +76,22 @@ var serviceCmd = &cobra.Command{ os.Exit(1) } - url, err := cluster.GetServiceURL(api, namespace, service, serviceURLTemplate) + urls, err := cluster.GetServiceURLs(api, namespace, service, serviceURLTemplate) if err != nil { fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, "Check that minikube is running and that you have specified the correct namespace (-n flag).") os.Exit(1) } - if https { - url = strings.Replace(url, "http", "https", 1) - } - if serviceURLMode { - fmt.Fprintln(os.Stdout, url) - } else { - fmt.Fprintln(os.Stdout, "Opening kubernetes service "+namespace+"/"+service+" in default browser...") - browser.OpenURL(url) + for _, url := range urls { + if https { + url = strings.Replace(url, "http", "https", 1) + } + if serviceURLMode || !strings.HasPrefix(url, "http") { + fmt.Fprintln(os.Stdout, url) + } else { + fmt.Fprintln(os.Stdout, "Opening kubernetes service "+namespace+"/"+service+" in default browser...") + browser.OpenURL(url) + } } }, } @@ -99,7 +101,7 @@ func init() { serviceCmd.Flags().BoolVar(&serviceURLMode, "url", false, "Display the kubernetes service URL in the CLI instead of opening it in the default browser") serviceCmd.Flags().BoolVar(&https, "https", false, "Open the service URL with https instead of http") - serviceCmd.PersistentFlags().StringVar(&serviceURLFormat, "format", "http://{{.IP}}:{{.Port}}", "Format to output service URL in") + serviceCmd.PersistentFlags().StringVar(&serviceURLFormat, "format", "http://{{.IP}}:{{.Port}}", "Format to output service URL in. This format will be applied to each url individually and they will be printed one at a time.") RootCmd.AddCommand(serviceCmd) } diff --git a/docs/minikube.md b/docs/minikube.md index 02c81a9105..fa5bc2584b 100644 --- a/docs/minikube.md +++ b/docs/minikube.md @@ -30,7 +30,7 @@ Minikube is a CLI tool that provisions and manages single-node Kubernetes cluste * [minikube get-k8s-versions](minikube_get-k8s-versions.md) - Gets the list of available kubernetes versions available for minikube. * [minikube ip](minikube_ip.md) - Retrieve the IP address of the running cluster. * [minikube logs](minikube_logs.md) - Gets the logs of the running localkube instance, used for debugging minikube, not user code. -* [minikube service](minikube_service.md) - Gets the kubernetes URL for the specified service in your local cluster +* [minikube service](minikube_service.md) - Gets the kubernetes URL(s) for the specified service in your local cluster * [minikube ssh](minikube_ssh.md) - Log into or run a command on a machine with SSH; similar to 'docker-machine ssh' * [minikube start](minikube_start.md) - Starts a local kubernetes cluster. * [minikube status](minikube_status.md) - Gets the status of a local kubernetes cluster. diff --git a/docs/minikube_service.md b/docs/minikube_service.md index 395834c126..e46909c8d2 100644 --- a/docs/minikube_service.md +++ b/docs/minikube_service.md @@ -1,11 +1,11 @@ ## minikube service -Gets the kubernetes URL for the specified service in your local cluster +Gets the kubernetes URL(s) for the specified service in your local cluster ### Synopsis -Gets the kubernetes URL for the specified service in your local cluster +Gets the kubernetes URL(s) for the specified service in your local cluster. In the case of multiple URLs they will be printed one at a time ``` minikube service [flags] SERVICE @@ -14,7 +14,7 @@ minikube service [flags] SERVICE ### Options ``` - --format string Format to output service URL in (default "http://{{.IP}}:{{.Port}}") + --format string Format to output service URL in. This format will be applied to each url individually and they will be printed one at a time. (default "http://{{.IP}}:{{.Port}}") --https Open the service URL with https instead of http -n, --namespace string The service namespace (default "default") --url Display the kubernetes service URL in the CLI instead of opening it in the default browser diff --git a/pkg/minikube/cluster/cluster.go b/pkg/minikube/cluster/cluster.go index 3f8808aa04..06639e1d4b 100644 --- a/pkg/minikube/cluster/cluster.go +++ b/pkg/minikube/cluster/cluster.go @@ -27,7 +27,6 @@ import ( "net/url" "os" "path/filepath" - "strconv" "strings" "text/template" "time" @@ -513,50 +512,54 @@ func CreateSSHShell(api libmachine.API, args []string) error { type ipPort struct { IP string - Port int + Port int32 } -func GetServiceURL(api libmachine.API, namespace, service string, t *template.Template) (string, error) { +func GetServiceURLs(api libmachine.API, namespace, service string, t *template.Template) ([]string, error) { host, err := CheckIfApiExistsAndLoad(api) if err != nil { - return "", errors.Wrap(err, "Error checking if api exist and loading it") + return nil, errors.Wrap(err, "Error checking if api exist and loading it") } ip, err := host.Driver.GetIP() if err != nil { - return "", errors.Wrap(err, "Error getting ip from host") + return nil, errors.Wrap(err, "Error getting ip from host") } client, err := GetKubernetesClient() if err != nil { - return "", err + return nil, err } - return getServiceURLWithClient(client, ip, namespace, service, t) + return getServiceURLsWithClient(client, ip, namespace, service, t) } -func getServiceURLWithClient(client *unversioned.Client, ip, namespace, service string, t *template.Template) (string, error) { - port, err := getServicePort(client, namespace, service) - if err != nil { - return "", err - } - +func getServiceURLsWithClient(client *unversioned.Client, ip, namespace, service string, t *template.Template) ([]string, error) { if t == nil { - return fmt.Sprintf("http://%s", net.JoinHostPort(ip, strconv.Itoa(port))), nil + return nil, errors.New("Error, attempted to generate service url with nil --format template") } - var doc bytes.Buffer - err = t.Execute(&doc, ipPort{ip, port}) + ports, err := getServicePorts(client, namespace, service) if err != nil { - return "", err + return nil, err } + urls := []string{} + for _, port := range ports { - u, err := url.Parse(doc.String()) - if err != nil { - return "", err + var doc bytes.Buffer + err = t.Execute(&doc, ipPort{ip, port}) + if err != nil { + return nil, err + } + + u, err := url.Parse(doc.String()) + if err != nil { + return nil, err + } + + urls = append(urls, u.String()) } - - return u.String(), nil + return urls, nil } type serviceGetter interface { @@ -568,9 +571,9 @@ type endpointGetter interface { Get(name string) (*kubeapi.Endpoints, error) } -func getServicePort(client *unversioned.Client, namespace, service string) (int, error) { +func getServicePorts(client *unversioned.Client, namespace, service string) ([]int32, error) { services := client.Services(namespace) - return getServicePortFromServiceGetter(services, service) + return getServicePortsFromServiceGetter(services, service) } type MissingNodePortError struct { @@ -589,19 +592,21 @@ func getServiceFromServiceGetter(services serviceGetter, service string) (*kubea return svc, nil } -func getServicePortFromServiceGetter(services serviceGetter, service string) (int, error) { +func getServicePortsFromServiceGetter(services serviceGetter, service string) ([]int32, error) { svc, err := getServiceFromServiceGetter(services, service) if err != nil { - return 0, err + return nil, err } - nodePort := 0 + var nodePorts []int32 if len(svc.Spec.Ports) > 0 { - nodePort = int(svc.Spec.Ports[0].NodePort) + for _, port := range svc.Spec.Ports { + nodePorts = append(nodePorts, port.NodePort) + } } - if nodePort == 0 { - return 0, MissingNodePortError{svc} + if len(nodePorts) == 0 { + return nil, MissingNodePortError{svc} } - return nodePort, nil + return nodePorts, nil } func GetKubernetesClient() (*unversioned.Client, error) { diff --git a/pkg/minikube/cluster/cluster_test.go b/pkg/minikube/cluster/cluster_test.go index 134fd9f11c..5636ab8a5a 100644 --- a/pkg/minikube/cluster/cluster_test.go +++ b/pkg/minikube/cluster/cluster_test.go @@ -551,27 +551,30 @@ func (mockServiceGetter *MockServiceGetter) List(options api.ListOptions) (*api. return &services, nil } -func TestGetDashboardURL(t *testing.T) { +func TestGetServiceURLs(t *testing.T) { mockServiceGetter := NewMockServiceGetter() - nodeport := api.ServicePort{ - NodePort: 1234, - } + expected := []int32{1111, 2222} mockDashboardService := api.Service{ Spec: api.ServiceSpec{ - Ports: []api.ServicePort{nodeport}, + Ports: []api.ServicePort{ + { + NodePort: expected[0], + }, { + NodePort: expected[1], + }}, }, } - mockServiceGetter.services["kubernetes-dashboard"] = mockDashboardService + mockServiceGetter.services["mock-service"] = mockDashboardService - port, err := getServicePortFromServiceGetter(mockServiceGetter, "kubernetes-dashboard") + ports, err := getServicePortsFromServiceGetter(mockServiceGetter, "mock-service") if err != nil { - t.Fatalf("Error getting dashboard port from api: Error: %s", err) + t.Fatalf("Error getting mock-service ports from api: Error: %s", err) } - expected := 1234 - if port != expected { - t.Fatalf("Error getting dashboard port from api: Expected: %d, Got: %d", port, expected) + for i := range ports { + if ports[i] != expected[i] { + t.Fatalf("Error getting mock-service port from api: Expected: %d, Got: %d", ports[0], expected) + } } - } func TestGetServiceURLWithoutNodePort(t *testing.T) { @@ -579,7 +582,7 @@ func TestGetServiceURLWithoutNodePort(t *testing.T) { mockDashboardService := api.Service{} mockServiceGetter.services["mock-service"] = mockDashboardService - _, err := getServicePortFromServiceGetter(mockServiceGetter, "mock-service") + _, err := getServicePortsFromServiceGetter(mockServiceGetter, "mock-service") if err == nil { t.Fatalf("Expected error getting service with no node port") }