github.com/kubeshop/testkube@v1.17.23/cmd/kubectl-testkube/commands/dashboard.go (about)

     1  package commands
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"os"
     8  	"os/signal"
     9  	"syscall"
    10  
    11  	"github.com/pkg/errors"
    12  	"github.com/skratchdot/open-golang/open"
    13  	"github.com/spf13/cobra"
    14  
    15  	"github.com/kubeshop/testkube/cmd/kubectl-testkube/config"
    16  	"github.com/kubeshop/testkube/pkg/k8sclient"
    17  	"github.com/kubeshop/testkube/pkg/ui"
    18  )
    19  
    20  const maxPortNumber = 65535
    21  
    22  // NewDashboardCmd is a method to create new dashboard command
    23  func NewDashboardCmd() *cobra.Command {
    24  	var namespace string
    25  	var verbose bool
    26  
    27  	cmd := &cobra.Command{
    28  		Use:     "dashboard",
    29  		Aliases: []string{"d", "open-dashboard", "ui"},
    30  		Short:   "Open Testkube dashboard",
    31  		Long:    `Open Testkube dashboard`,
    32  		Run: func(cmd *cobra.Command, args []string) {
    33  			cfg, err := config.Load()
    34  			ui.ExitOnError("loading config file", err)
    35  
    36  			if namespace == "" {
    37  				namespace = cfg.Namespace
    38  			}
    39  
    40  			if cfg.ContextType != config.ContextTypeCloud {
    41  				isDashboardRunning, _ := k8sclient.IsPodOfServiceRunning(context.Background(), cfg.Namespace, config.EnterpriseUiName)
    42  				if isDashboardRunning {
    43  					openOnPremDashboard(cmd, cfg, verbose)
    44  				} else {
    45  					ui.Warn("No dashboard found. Is it running in the " + namespace + " namespace?")
    46  				}
    47  			} else {
    48  				openCloudDashboard(cfg)
    49  			}
    50  		},
    51  	}
    52  
    53  	cmd.Flags().BoolVarP(&verbose, "verbose", "", false, "show additional debug messages")
    54  	cmd.Flags().StringVarP(&namespace, "namespace", "n", "", "Namespace to install "+demoInstallationName)
    55  
    56  	return cmd
    57  }
    58  
    59  func openCloudDashboard(cfg config.Data) {
    60  	// open browser
    61  	uri := fmt.Sprintf("%s/organization/%s/environment/%s", cfg.CloudContext.UiUri, cfg.CloudContext.OrganizationId, cfg.CloudContext.EnvironmentId)
    62  	err := open.Run(uri)
    63  	ui.PrintOnError("openning dashboard", err)
    64  }
    65  
    66  func openOnPremDashboard(cmd *cobra.Command, cfg config.Data, verbose bool) {
    67  	uiLocalPort, err := getDashboardLocalPort(config.EnterpriseApiForwardingPort)
    68  	ui.PrintOnError("getting an ui forwarding available port", err)
    69  	uri := fmt.Sprintf("http://localhost:%d", uiLocalPort)
    70  
    71  	ctx, cancel := context.WithCancel(context.Background())
    72  	err = k8sclient.PortForward(ctx, cfg.Namespace, config.EnterpriseApiName, config.EnterpriseApiPort, config.EnterpriseApiForwardingPort, verbose)
    73  	ui.PrintOnError("port forwarding api", err)
    74  	err = k8sclient.PortForward(ctx, cfg.Namespace, config.EnterpriseUiName, config.EnterpriseUiPort, uiLocalPort, verbose)
    75  	ui.PrintOnError("port forwarding ui", err)
    76  	err = k8sclient.PortForward(ctx, cfg.Namespace, config.EnterpriseDexName, config.EnterpriseDexPort, config.EnterpriseDexForwardingPort, verbose)
    77  	ui.PrintOnError("port forwarding dex", err)
    78  
    79  	err = open.Run(uri)
    80  	ui.ExitOnError("opening dashboard in browser", err)
    81  	c := make(chan os.Signal, 1)
    82  	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
    83  
    84  	ui.Success("The dashboard is accessible here:", uri)
    85  	ui.Success("Port forwarding the necessary services, hit Ctrl+c (or Cmd+c) to stop")
    86  	<-c
    87  	cancel()
    88  
    89  }
    90  
    91  func localPortCheck(port int) error {
    92  	ln, err := net.Listen("tcp", ":"+fmt.Sprint(port))
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	ln.Close()
    98  	return nil
    99  }
   100  
   101  func getDashboardLocalPort(apiServerPort int) (int, error) {
   102  	for port := DashboardLocalPort; port <= maxPortNumber; port++ {
   103  		if port == apiServerPort {
   104  			continue
   105  		}
   106  
   107  		if localPortCheck(port) == nil {
   108  			return port, nil
   109  		}
   110  	}
   111  
   112  	return 0, errors.New("no available local port")
   113  }