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

     1  package pro
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/pterm/pterm"
     8  
     9  	"github.com/spf13/cobra"
    10  
    11  	"github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common"
    12  	"github.com/kubeshop/testkube/cmd/kubectl-testkube/config"
    13  	cloudclient "github.com/kubeshop/testkube/pkg/cloud/client"
    14  	"github.com/kubeshop/testkube/pkg/ui"
    15  )
    16  
    17  const (
    18  	listenAddr      = "127.0.0.1:8090"
    19  	docsUrl         = "https://docs.testkube.io/testkube-pro/intro"
    20  	tokenQueryParam = "token"
    21  )
    22  
    23  func NewConnectCmd() *cobra.Command {
    24  	var opts = common.HelmOptions{}
    25  
    26  	cmd := &cobra.Command{
    27  		Use:     "connect",
    28  		Aliases: []string{"c"},
    29  		Short:   "Testkube Pro connect ",
    30  		Run: func(cmd *cobra.Command, args []string) {
    31  			client, _, err := common.GetClient(cmd)
    32  			ui.ExitOnError("getting client", err)
    33  
    34  			info, err := client.GetServerInfo()
    35  			firstInstall := err != nil && strings.Contains(err.Error(), "not found")
    36  			if err != nil && !firstInstall {
    37  				ui.Failf("Can't get Testkube cluster information: %s", err.Error())
    38  			}
    39  
    40  			var apiContext string
    41  			if actx, ok := contextDescription[info.Context]; ok {
    42  				apiContext = actx
    43  			}
    44  
    45  			ui.H1("Connect your Pro environment:")
    46  			ui.Paragraph("You can learn more about connecting your Testkube instance to Testkube Pro here:\n" + docsUrl)
    47  			ui.H2("You can safely switch between connecting Pro and disconnecting without losing your data.")
    48  
    49  			cfg, err := config.Load()
    50  			ui.ExitOnError("loading config", err)
    51  
    52  			common.ProcessMasterFlags(cmd, &opts, &cfg)
    53  
    54  			var clusterContext string
    55  			if cfg.ContextType == config.ContextTypeKubeconfig {
    56  				clusterContext, err = common.GetCurrentKubernetesContext()
    57  				ui.ExitOnError("getting current kubernetes context", err)
    58  			}
    59  
    60  			// TODO: implement context info
    61  			ui.H1("Current status of your Testkube instance")
    62  			ui.Properties([][]string{
    63  				{"Context", apiContext},
    64  				{"Kubectl context", clusterContext},
    65  				{"Namespace", cfg.Namespace},
    66  			})
    67  
    68  			newStatus := [][]string{
    69  				{"Testkube mode"},
    70  				{"Context", contextDescription["cloud"]},
    71  				{"Kubectl context", clusterContext},
    72  				{"Namespace", cfg.Namespace},
    73  				{ui.Separator, ""},
    74  			}
    75  
    76  			var (
    77  				token        string
    78  				refreshToken string
    79  			)
    80  			// if no agent is passed create new environment and get its token
    81  			if opts.Master.AgentToken == "" && opts.Master.OrgId == "" && opts.Master.EnvId == "" {
    82  				token, refreshToken, err = common.LoginUser(opts.Master.URIs.Auth)
    83  				ui.ExitOnError("login", err)
    84  
    85  				orgId, orgName, err := common.UiGetOrganizationId(opts.Master.URIs.Api, token)
    86  				ui.ExitOnError("getting organization", err)
    87  
    88  				envName, err := uiGetEnvName()
    89  				ui.ExitOnError("getting environment name", err)
    90  
    91  				envClient := cloudclient.NewEnvironmentsClient(opts.Master.URIs.Api, token, orgId)
    92  				env, err := envClient.Create(cloudclient.Environment{Name: envName, Owner: orgId})
    93  				ui.ExitOnError("creating environment", err)
    94  
    95  				opts.Master.OrgId = orgId
    96  				opts.Master.EnvId = env.Id
    97  				opts.Master.AgentToken = env.AgentToken
    98  
    99  				newStatus = append(
   100  					newStatus,
   101  					[][]string{
   102  						{"Testkube will be connected to Pro org/env"},
   103  						{"Organization Id", opts.Master.OrgId},
   104  						{"Organization name", orgName},
   105  						{"Environment Id", opts.Master.EnvId},
   106  						{"Environment name", env.Name},
   107  						{ui.Separator, ""},
   108  					}...)
   109  			}
   110  
   111  			// validate if user created env - or was passed from flags
   112  			if opts.Master.EnvId == "" {
   113  				ui.Failf("You need pass valid environment id to connect to Pro")
   114  			}
   115  			if opts.Master.OrgId == "" {
   116  				ui.Failf("You need pass valid organization id to connect to Pro")
   117  			}
   118  
   119  			// update summary
   120  			newStatus = append(newStatus, []string{"Testkube support services not needed anymore"})
   121  			newStatus = append(newStatus, []string{"MinIO    ", "Stopped and scaled down, (not deleted)"})
   122  			newStatus = append(newStatus, []string{"MongoDB  ", "Stopped and scaled down, (not deleted)"})
   123  			newStatus = append(newStatus, []string{"Dashboard", "Stopped and scaled down, (not deleted)"})
   124  
   125  			ui.NL(2)
   126  
   127  			ui.H1("Summary of your setup after connecting to Testkube Pro")
   128  			ui.Properties(newStatus)
   129  
   130  			ui.NL()
   131  			ui.Warn("Remember: All your historical data and artifacts will be safe in case you want to rollback. OSS and Pro executions will be separated.")
   132  			ui.NL()
   133  
   134  			if ok := ui.Confirm("Proceed with connecting Testkube Pro?"); !ok {
   135  				return
   136  			}
   137  
   138  			spinner := ui.NewSpinner("Connecting Testkube Pro")
   139  			err = common.HelmUpgradeOrInstallTestkubeCloud(opts, cfg, true)
   140  			ui.ExitOnError("Installing Testkube Pro", err)
   141  			spinner.Success()
   142  
   143  			ui.NL()
   144  
   145  			// let's scale down deployment of mongo
   146  			if opts.MongoReplicas == 0 {
   147  				spinner = ui.NewSpinner("Scaling down MongoDB")
   148  				common.KubectlScaleDeployment(opts.Namespace, "testkube-mongodb", opts.MongoReplicas)
   149  				spinner.Success()
   150  			}
   151  			if opts.MinioReplicas == 0 {
   152  				spinner = ui.NewSpinner("Scaling down MinIO")
   153  				common.KubectlScaleDeployment(opts.Namespace, "testkube-minio-testkube", opts.MinioReplicas)
   154  				spinner.Success()
   155  			}
   156  
   157  			ui.H2("Testkube Pro is connected to your Testkube instance, saving local configuration")
   158  
   159  			ui.H2("Saving Testkube CLI Pro context")
   160  			if token == "" && !common.IsUserLoggedIn(cfg, opts) {
   161  				token, refreshToken, err = common.LoginUser(opts.Master.URIs.Auth)
   162  				ui.ExitOnError("user login", err)
   163  			}
   164  			err = common.PopulateLoginDataToContext(opts.Master.OrgId, opts.Master.EnvId, token, refreshToken, opts, cfg)
   165  
   166  			ui.ExitOnError("Setting Pro environment context", err)
   167  
   168  			ui.NL(2)
   169  
   170  			ui.ShellCommand("In case you want to roll back you can simply run the following command in your CLI:", "testkube pro disconnect")
   171  
   172  			ui.Success("You can now login to Testkube Pro and validate your connection:")
   173  			ui.NL()
   174  			ui.Link(opts.Master.URIs.Ui + "/organization/" + opts.Master.OrgId + "/environment/" + opts.Master.EnvId + "/dashboard/tests")
   175  
   176  			ui.NL(2)
   177  		},
   178  	}
   179  
   180  	common.PopulateHelmFlags(cmd, &opts)
   181  	common.PopulateMasterFlags(cmd, &opts)
   182  
   183  	cmd.Flags().IntVar(&opts.MinioReplicas, "minio-replicas", 0, "MinIO replicas")
   184  	cmd.Flags().IntVar(&opts.MongoReplicas, "mongo-replicas", 0, "MongoDB replicas")
   185  	return cmd
   186  }
   187  
   188  var contextDescription = map[string]string{
   189  	"":      "Unknown context, try updating your testkube cluster installation",
   190  	"oss":   "Open Source Testkube",
   191  	"cloud": "Testkube in Pro mode",
   192  }
   193  
   194  func uiGetEnvName() (string, error) {
   195  	for i := 0; i < 3; i++ {
   196  		if envName := ui.TextInput("Tell us the name of your environment"); envName != "" {
   197  			return envName, nil
   198  		}
   199  		pterm.Error.Println("Environment name cannot be empty")
   200  	}
   201  
   202  	return "", fmt.Errorf("environment name cannot be empty")
   203  }