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 }