diff --git a/cmd/minikube/cmd/service.go b/cmd/minikube/cmd/service.go index 65e1f69297..b7ec05a6c3 100644 --- a/cmd/minikube/cmd/service.go +++ b/cmd/minikube/cmd/service.go @@ -20,8 +20,13 @@ import ( "fmt" "net/url" "os" + "os/signal" + "path/filepath" "runtime" + "strconv" + "strings" "text/template" + "time" "github.com/golang/glog" "github.com/pkg/browser" @@ -32,9 +37,11 @@ import ( "k8s.io/minikube/pkg/minikube/config" pkg_config "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/exit" + "k8s.io/minikube/pkg/minikube/localpath" "k8s.io/minikube/pkg/minikube/machine" "k8s.io/minikube/pkg/minikube/out" "k8s.io/minikube/pkg/minikube/service" + "k8s.io/minikube/pkg/minikube/tunnel/kic" ) const defaultServiceFormatTemplate = "http://{{.IP}}:{{.Port}}" @@ -85,34 +92,17 @@ var serviceCmd = &cobra.Command{ exit.WithError("Error getting config", err) } + if runtime.GOOS == "darwin" && cfg.Driver == oci.Docker { + startKicServiceTunnel(svc, cfg.Name) + return + } + urls, err := service.WaitForService(api, namespace, svc, serviceURLTemplate, serviceURLMode, https, wait, interval) if err != nil { exit.WithError("Error opening service", err) } - if runtime.GOOS == "darwin" && cfg.Driver == oci.Docker { - out.FailureT("Opening service in browser is not implemented yet for docker driver on Mac.\nThe following issue is tracking the in progress work:\nhttps://github.com/kubernetes/minikube/issues/6778") - exit.WithCodeT(exit.Unavailable, "Not yet implemented for docker driver on MacOS.") - } - - for _, u := range urls { - _, err := url.Parse(u) - if err != nil { - glog.Warningf("failed to parse url %q: %v (will not open)", u, err) - out.String(fmt.Sprintf("%s\n", u)) - continue - } - - if serviceURLMode { - out.String(fmt.Sprintf("%s\n", u)) - continue - } - - out.T(out.Celebrate, "Opening service {{.namespace_name}}/{{.service_name}} in default browser...", out.V{"namespace_name": namespace, "service_name": svc}) - if err := browser.OpenURL(u); err != nil { - exit.WithError(fmt.Sprintf("open url failed: %s", u), err) - } - } + openURLs(svc, urls) }, } @@ -126,3 +116,65 @@ func init() { serviceCmd.PersistentFlags().StringVar(&serviceURLFormat, "format", defaultServiceFormatTemplate, "Format to output service URL in. This format will be applied to each url individually and they will be printed one at a time.") } + +func startKicServiceTunnel(svc, configName string) { + ctrlC := make(chan os.Signal, 1) + signal.Notify(ctrlC, os.Interrupt) + + clientset, err := service.K8s.GetClientset(1 * time.Second) + if err != nil { + exit.WithError("error creating clientset", err) + } + + port, err := oci.HostPortBinding(oci.Docker, configName, 22) + if err != nil { + exit.WithError("error getting ssh port", err) + } + sshPort := strconv.Itoa(port) + sshKey := filepath.Join(localpath.MiniPath(), "machines", configName, "id_rsa") + + serviceTunnel := kic.NewServiceTunnel(sshPort, sshKey, clientset.CoreV1()) + urls, err := serviceTunnel.Start(svc, namespace) + if err != nil { + exit.WithError("error starting tunnel", err) + } + + // wait for tunnel to come up + time.Sleep(1 * time.Second) + + data := [][]string{{namespace, svc, "", strings.Join(urls, "\n")}} + service.PrintServiceList(os.Stdout, data) + + openURLs(svc, urls) + + <-ctrlC + + err = serviceTunnel.Stop() + if err != nil { + exit.WithError("error stopping tunnel", err) + } + + return +} + +func openURLs(svc string, urls []string) { + for _, u := range urls { + _, err := url.Parse(u) + if err != nil { + glog.Warningf("failed to parse url %q: %v (will not open)", u, err) + out.String(fmt.Sprintf("%s\n", u)) + continue + } + + if serviceURLMode { + out.String(fmt.Sprintf("%s\n", u)) + continue + } + + out.T(out.Celebrate, "Opening service {{.namespace_name}}/{{.service_name}} in default browser...", out.V{"namespace_name": namespace, "service_name": svc}) + if err := browser.OpenURL(u); err != nil { + exit.WithError(fmt.Sprintf("open url failed: %s", u), err) + } + } + +} diff --git a/pkg/minikube/tunnel/kic/service_tunnel.go b/pkg/minikube/tunnel/kic/service_tunnel.go new file mode 100644 index 0000000000..5cc6bff7ac --- /dev/null +++ b/pkg/minikube/tunnel/kic/service_tunnel.go @@ -0,0 +1,78 @@ +/* +Copyright 2020 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kic + +import ( + "fmt" + + "github.com/golang/glog" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + typed_core "k8s.io/client-go/kubernetes/typed/core/v1" +) + +// ServiceTunnel ... +type ServiceTunnel struct { + sshPort string + sshKey string + v1Core typed_core.CoreV1Interface + sshConn *sshConn +} + +// NewServiceTunnel ... +func NewServiceTunnel(sshPort, sshKey string, v1Core typed_core.CoreV1Interface) *ServiceTunnel { + return &ServiceTunnel{ + sshPort: sshPort, + sshKey: sshKey, + v1Core: v1Core, + } +} + +// Start ... +func (t *ServiceTunnel) Start(svcName, namespace string) ([]string, error) { + svc, err := t.v1Core.Services(namespace).Get(svcName, metav1.GetOptions{}) + if err != nil { + glog.Errorf("error listing services: %v", err) + return nil, err + } + + t.sshConn = createSSHConn(svcName, t.sshPort, t.sshKey, svc) + + go func() { + err = t.sshConn.startAndWait() + if err != nil { + glog.Errorf("error starting ssh tunnel: %v", err) + } + }() + + urls := make([]string, 0, len(svc.Spec.Ports)) + for _, port := range svc.Spec.Ports { + urls = append(urls, fmt.Sprintf("http://127.0.0.1:%d", port.Port)) + } + + return urls, nil +} + +// Stop ... +func (t *ServiceTunnel) Stop() error { + err := t.sshConn.stop() + if err != nil { + glog.Errorf("error stopping ssh tunnel: %v", err) + return err + } + + return nil +} diff --git a/pkg/minikube/tunnel/kic/ssh_conn.go b/pkg/minikube/tunnel/kic/ssh_conn.go index 53c7304a3c..4c98f0836f 100644 --- a/pkg/minikube/tunnel/kic/ssh_conn.go +++ b/pkg/minikube/tunnel/kic/ssh_conn.go @@ -29,7 +29,7 @@ type sshConn struct { cmd *exec.Cmd } -func createSSHConn(name, sshPort, sshKey string, svc v1.Service) *sshConn { +func createSSHConn(name, sshPort, sshKey string, svc *v1.Service) *sshConn { // extract sshArgs sshArgs := []string{ // TODO: document the options here diff --git a/pkg/minikube/tunnel/kic/ssh_tunnel.go b/pkg/minikube/tunnel/kic/ssh_tunnel.go index 3dca0a1d34..65acaf325b 100644 --- a/pkg/minikube/tunnel/kic/ssh_tunnel.go +++ b/pkg/minikube/tunnel/kic/ssh_tunnel.go @@ -104,7 +104,7 @@ func (t *SSHTunnel) startConnection(svc v1.Service) { } // create new ssh conn - newSSHConn := createSSHConn(uniqName, t.sshPort, t.sshKey, svc) + newSSHConn := createSSHConn(uniqName, t.sshPort, t.sshKey, &svc) t.conns[newSSHConn.name] = newSSHConn go func() { @@ -147,3 +147,33 @@ func sshConnUniqName(service v1.Service) string { return strings.Join(n, "") } + +// StartServiceTunnel ... +func (t *SSHTunnel) StartServiceTunnel(svcName string) error { + svc, err := t.v1Core.Services("default").Get(svcName, metav1.GetOptions{}) + if err != nil { + glog.Errorf("error listing services: %v", err) + } + + newSSHConn := createSSHConn(svcName, t.sshPort, t.sshKey, svc) + + go func() { + err := newSSHConn.startAndWait() + if err != nil { + glog.Errorf("error starting ssh tunnel: %v", err) + } + }() + + //err = t.LoadBalancerEmulator.PatchServiceIP(t.v1Core.RESTClient(), svc, "127.0.0.1") + //if err != nil { + // glog.Errorf("error patching service: %v", err) + //} + + <-t.ctx.Done() + _, err = t.LoadBalancerEmulator.Cleanup() + if err != nil { + glog.Errorf("error cleaning up: %v", err) + } + + return err +}