minikube #13075 adds --all feature for service

pull/13367/head
ckannon 2022-01-17 17:39:44 -05:00
parent 89795d1946
commit c8d846d6c1
6 changed files with 183 additions and 40 deletions

View File

@ -30,7 +30,6 @@ import (
"time"
"github.com/spf13/cobra"
"k8s.io/klog/v2"
"k8s.io/minikube/pkg/drivers/kic/oci"
"k8s.io/minikube/pkg/kapi"
@ -50,6 +49,7 @@ const defaultServiceFormatTemplate = "http://{{.IP}}:{{.Port}}"
var (
namespace string
all bool
https bool
serviceURLMode bool
serviceURLFormat string
@ -62,7 +62,7 @@ var (
var serviceCmd = &cobra.Command{
Use: "service [flags] SERVICE",
Short: "Returns a URL to connect to a service",
Long: `Returns the Kubernetes URL for a service in your local cluster. In the case of multiple URLs they will be printed one at a time.`,
Long: `Returns the Kubernetes URL(s) for service(s) 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 {
@ -73,37 +73,94 @@ var serviceCmd = &cobra.Command{
RootCmd.PersistentPreRun(cmd, args)
},
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 || len(args) > 1 {
exit.Message(reason.Usage, "You must specify a service name")
if len(args) == 0 && !all || (len(args) > 0 && all) {
exit.Message(reason.Usage, "You must specify service name(s) or --all")
}
svc := args[0]
svcArgs := make(map[string]bool)
for _, v := range args {
svcArgs[v] = true
}
cname := ClusterFlagValue()
co := mustload.Healthy(cname)
urls, err := service.WaitForService(co.API, co.Config.Name, namespace, svc, serviceURLTemplate, serviceURLMode, https, wait, interval)
var services service.URLs
services, err := service.GetServiceURLs(co.API, co.Config.Name, namespace, serviceURLTemplate)
if err != nil {
var s *service.SVCNotFoundError
if errors.As(err, &s) {
exit.Message(reason.SvcNotFound, `Service '{{.service}}' was not found in '{{.namespace}}' namespace.
You may select another namespace by using 'minikube service {{.service}} -n <namespace>'. Or list out all the services using 'minikube service list'`, out.V{"service": svc, "namespace": namespace})
out.FatalT("Failed to get service URL: {{.error}}", out.V{"error": err})
out.ErrT(style.Notice, "Check that minikube is running and that you have specified the correct namespace (-n flag) if required.")
os.Exit(reason.ExSvcUnavailable)
}
if len(args) >= 1 {
var newServices service.URLs
for _, svc := range services {
if _, ok := svcArgs[svc.Name]; ok {
newServices = append(newServices, svc)
}
}
services = newServices
}
var data [][]string
var openUrls []string
for _, svc := range services {
openUrls, err := service.WaitForService(co.API, co.Config.Name, namespace, svc.Name, serviceURLTemplate, true, https, wait, interval)
if err != nil {
var s *service.SVCNotFoundError
if errors.As(err, &s) {
exit.Message(reason.SvcNotFound, `Service '{{.service}}' was not found in '{{.namespace}}' namespace.
You may select another namespace by using 'minikube service {{.service}} -n <namespace>'. Or list out all the services using 'minikube service list'`, out.V{"service": svc, "namespace": namespace})
}
exit.Error(reason.SvcTimeout, "Error opening service", err)
}
if len(openUrls) == 0 {
data = append(data, []string{svc.Namespace, svc.Name, "No node port"})
} else {
servicePortNames := strings.Join(svc.PortNames, "\n")
serviceURLs := strings.Join(openUrls, "\n")
// if we are running Docker on OSX we empty the internal service URLs
if runtime.GOOS == "darwin" && co.Config.Driver == oci.Docker {
serviceURLs = ""
}
data = append(data, []string{svc.Namespace, svc.Name, servicePortNames, serviceURLs})
}
}
if (!serviceURLMode && serviceURLFormat != defaultServiceFormatTemplate && !all) || all {
service.PrintServiceList(os.Stdout, data)
} else if serviceURLMode && !all {
for _, u := range data {
out.String(fmt.Sprintf("%s\n", u[3]))
}
exit.Error(reason.SvcTimeout, "Error opening service", err)
}
if driver.NeedsPortForward(co.Config.Driver) {
startKicServiceTunnel(svc, cname, co.Config.Driver)
startKicServiceTunnel(args, services, cname, co.Config.Driver)
return
}
openURLs(svc, urls)
if !serviceURLMode && !all && len(args) == 1 {
openURLs(args[0], openUrls)
}
},
}
func shouldOpen(args []string) bool {
if !serviceURLMode && !all && len(args) == 1 {
return true
}
return false
}
func init() {
serviceCmd.Flags().StringVarP(&namespace, "namespace", "n", "default", "The service namespace")
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(&all, "all", false, "Forwards all services in a namespace (defaults to \"false\")")
serviceCmd.Flags().BoolVar(&https, "https", false, "Open the service URL with https instead of http (defaults to \"false\")")
serviceCmd.Flags().IntVar(&wait, "wait", service.DefaultWait, "Amount of time to wait for a service in seconds")
serviceCmd.Flags().IntVar(&interval, "interval", service.DefaultInterval, "The initial time interval for each check that wait performs in seconds")
@ -111,7 +168,7 @@ 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, driverName string) {
func startKicServiceTunnel(args []string, services service.URLs, configName, driverName string) {
ctrlC := make(chan os.Signal, 1)
signal.Notify(ctrlC, os.Interrupt)
@ -120,34 +177,37 @@ func startKicServiceTunnel(svc, configName, driverName string) {
exit.Error(reason.InternalKubernetesClient, "error creating clientset", err)
}
port, err := oci.ForwardedPort(driverName, configName, 22)
if err != nil {
exit.Error(reason.DrvPortForward, "error getting ssh port", err)
}
sshPort := strconv.Itoa(port)
sshKey := filepath.Join(localpath.MiniPath(), "machines", configName, "id_rsa")
var data [][]string
for _, svc := range services {
port, err := oci.ForwardedPort(driverName, configName, 22)
if err != nil {
exit.Error(reason.DrvPortForward, "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.Error(reason.SvcTunnelStart, "error starting tunnel", err)
serviceTunnel := kic.NewServiceTunnel(sshPort, sshKey, clientset.CoreV1())
urls, err := serviceTunnel.Start(svc.Name, namespace)
if err != nil {
exit.Error(reason.SvcTunnelStart, "error starting tunnel", err)
}
defer serviceTunnel.Stop()
data = append(data, []string{namespace, svc.Name, "", strings.Join(urls, "\n")})
}
// wait for tunnel to come up
time.Sleep(1 * time.Second)
data := [][]string{{namespace, svc, "", strings.Join(urls, "\n")}}
service.PrintServiceList(os.Stdout, data)
if !serviceURLMode && serviceURLFormat != defaultServiceFormatTemplate && !all {
service.PrintServiceList(os.Stdout, data)
}
if shouldOpen(args) {
openURLs(services[0].Name, services[0].URLs)
}
openURLs(svc, urls)
out.WarningT("Because you are using a Docker driver on {{.operating_system}}, the terminal needs to be open to run it.", out.V{"operating_system": runtime.GOOS})
<-ctrlC
err = serviceTunnel.Stop()
if err != nil {
exit.Error(reason.SvcTunnelStop, "error stopping tunnel", err)
}
}
func openURLs(svc string, urls []string) {

View File

@ -0,0 +1,71 @@
/*
Copyright 2016 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 cmd
import (
"testing"
)
func TestServiceForwardOpen(t *testing.T) {
var tests = []struct {
name string
serviceURLMode bool
all bool
args []string
want bool
}{
{
name: "multiple_urls",
serviceURLMode: false,
all: false,
args: []string{"test-service-1", "test-service-2"},
want: false,
},
{
name: "service_url_mode",
serviceURLMode: true,
all: false,
args: []string{"test-service-1"},
want: false,
},
{
name: "all",
serviceURLMode: false,
all: true,
args: []string{"test-service-1", "test-service-2"},
want: false,
},
{
name: "single_url",
serviceURLMode: false,
all: false,
args: []string{"test-service-1"},
want: true,
},
}
for _, tc := range tests {
serviceURLMode = tc.serviceURLMode
all = tc.all
t.Run(tc.name, func(t *testing.T) {
got := shouldOpen(tc.args)
if got != tc.want {
t.Errorf("bool(%+v) = %t, want: %t", "shouldOpen", got, tc.want)
}
})
}
}

View File

@ -73,11 +73,9 @@ func (t *ServiceTunnel) Start(svcName, namespace string) ([]string, error) {
}
// Stop ...
func (t *ServiceTunnel) Stop() error {
func (t *ServiceTunnel) Stop() {
err := t.sshConn.stop()
if err != nil {
return errors.Wrap(err, "stopping ssh tunnel")
klog.Warningf("Failed to stop ssh tunnel", err)
}
return nil
}

View File

@ -18,6 +18,7 @@ package kic
import (
"fmt"
"os"
"os/exec"
"runtime"
@ -159,7 +160,12 @@ func (c *sshConn) stop() error {
if c.activeConn {
c.activeConn = false
out.Step(style.Stopping, "Stopping tunnel for service {{.service}}.", out.V{"service": c.service})
return c.cmd.Process.Kill()
err := c.cmd.Process.Kill()
if err == os.ErrProcessDone {
// No need to return an error here
return nil
}
return err
}
out.Step(style.Stopping, "Stopped tunnel for service {{.service}}.", out.V{"service": c.service})
return nil

View File

@ -20,6 +20,7 @@ minikube service [flags] SERVICE
### Options
```
--all Prints URL and port-forwards (if needed) all services in a namespace
--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 (defaults to "false")
--interval int The initial time interval for each check that wait performs in seconds (default 1)

View File

@ -1473,7 +1473,14 @@ func validateServiceCmd(ctx context.Context, t *testing.T, profile string) {
t.Errorf("expected stderr to be empty but got *%q* . args %q", rr.Stderr, rr.Command())
}
endpoint := strings.TrimSpace(rr.Stdout.String())
splits := strings.Split(rr.Stdout.String(), "|")
var endpoint string
// get the last endpoint in the output to test http to https
for _, v := range splits {
if strings.Contains(v, "http") {
endpoint = strings.TrimSpace(v)
}
}
t.Logf("found endpoint: %s", endpoint)
u, err := url.Parse(endpoint)