Skip to content

Commit

Permalink
fix: [TKC-2713] helm and kubectl init info (#6002)
Browse files Browse the repository at this point in the history
* fix: helm and kubectl debug info

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: debug for dashboard

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: pass set helm options

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: mode debug info

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: executed command

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: add/modify sets

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: add command context

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: helm arg options

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: ignore empty value

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: adjust error message

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: spinner

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

---------

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>
  • Loading branch information
vsukhin authored Nov 18, 2024
1 parent 59c91b3 commit b806dd3
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 57 deletions.
33 changes: 26 additions & 7 deletions cmd/kubectl-testkube/commands/common/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package common
import (
"fmt"
"os"
"strings"

"github.com/spf13/cobra"

Expand Down Expand Up @@ -52,13 +53,14 @@ const (
const helpUrl = "https://testkubeworkspace.slack.com"

type CLIError struct {
Code ErrorCode
Title string
Description string
ActualError error
StackTrace string
MoreInfo string
Telemetry *ErrorTelemetry
Code ErrorCode
Title string
Description string
ActualError error
StackTrace string
MoreInfo string
ExecutedCommand string
Telemetry *ErrorTelemetry
}

type ErrorTelemetry struct {
Expand Down Expand Up @@ -86,6 +88,15 @@ func (e *CLIError) Print() {

pterm.DefaultSection.Println("Error Details")

cmd := ""
if e.ExecutedCommand != "" {
pterm.FgDarkGray.Printfln("Executed command: %s", e.ExecutedCommand)
params := strings.Split(e.ExecutedCommand, " ")
if len(params) > 0 {
cmd = params[0]
}
}

items := []pterm.BulletListItem{
{Level: 0, Text: pterm.Sprintf("[%s]: %s", e.Code, e.Title), TextStyle: pterm.NewStyle(pterm.FgRed)},
{Level: 0, Text: pterm.Sprintf("%s", e.Description), TextStyle: pterm.NewStyle(pterm.FgLightWhite)},
Expand All @@ -94,6 +105,9 @@ func (e *CLIError) Print() {
items = append(items, pterm.BulletListItem{Level: 0, Text: pterm.Sprintf("%s", e.MoreInfo), TextStyle: pterm.NewStyle(pterm.FgGray)})
}
pterm.DefaultBulletList.WithItems(items).Render()
if cmd != "" {
pterm.DefaultBox.Printfln("Error description is provided in context of binary execution %s", cmd)
}

pterm.Println()
pterm.Println("Let us help you!")
Expand All @@ -111,6 +125,11 @@ func NewCLIError(code ErrorCode, title, moreInfoURL string, err error) *CLIError
}
}

func (err *CLIError) WithExecutedCommand(executedCommand string) *CLIError {
err.ExecutedCommand = executedCommand
return err
}

// HandleCLIError checks does the error exist, and if it does, prints the error and exits the program.
func HandleCLIError(err *CLIError) {
if err != nil {
Expand Down
137 changes: 91 additions & 46 deletions cmd/kubectl-testkube/commands/common/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type HelmOptions struct {
Name, Namespace, Chart, Values string
NoMinio, NoMongo, NoConfirm bool
MinioReplicas, MongoReplicas int
SetOptions, ArgOptions map[string]string

// On-prem
LicenseKey string
Expand Down Expand Up @@ -101,16 +102,11 @@ func HelmUpgradeOrInstallTestkubeAgent(options HelmOptions, cfg config.Data, isM
}

args := prepareTestkubeProHelmArgs(options, isMigration)
output, err := runHelmCommand(helmPath, args, options.DryRun)
_, err := runHelmCommand(helmPath, args, options.DryRun)
if err != nil {
return err
}

ui.Debug("Helm command output:")
ui.Debug(helmPath, args...)

ui.Debug("Helm install testkube output", output)

return nil
}

Expand Down Expand Up @@ -147,7 +143,7 @@ func lookupHelmPath() (string, *CLIError) {
return helmPath, nil
}

func updateHelmRepo(helmPath string, dryRun bool, isOnPrem bool) *CLIError {
func updateHelmRepo(helmPath string, dryRun, isOnPrem bool) *CLIError {
registryURL := "https://kubeshop.github.io/helm-charts"
registryName := "kubeshop"
if isOnPrem {
Expand Down Expand Up @@ -176,99 +172,139 @@ func CleanExistingCompletedMigrationJobs(namespace string) (cliErr *CLIError) {
}

// Clean the job only when it's found and it's state is successful - ignore pending migrations.
succeeded, _ := runKubectlCommand(kubectlPath, []string{"get", "job", "testkube-enterprise-api-migrations", "-n", namespace, "-o", "jsonpath={.status.succeeded}"})
cmd := []string{"get", "job", "testkube-enterprise-api-migrations", "-n", namespace, "-o", "jsonpath={.status.succeeded}"}
succeeded, _ := runKubectlCommand(kubectlPath, cmd)
if succeeded == "1" {
_, err := runKubectlCommand(kubectlPath, []string{"delete", "job", "testkube-enterprise-api-migrations", "--namespace", namespace})
cmd = []string{"delete", "job", "testkube-enterprise-api-migrations", "--namespace", namespace}
_, err := runKubectlCommand(kubectlPath, cmd)
if err != nil {
return NewCLIError(
TKErrCleanOldMigrationJobFailed,
"Can't clean old migrations job",
"Migration job can't be deleted from some reason, check for errors in installation namespace, check execution. As a workaround try to delete job manually and retry installation/upgrade process",
err,
)
).WithExecutedCommand(strings.Join(cmd, " "))
}
}

return nil
}

func runHelmCommand(helmPath string, args []string, dryRun bool) (commandOutput string, cliErr *CLIError) {
cmd := strings.Join(append([]string{helmPath}, args...), " ")
ui.DebugNL()
ui.Debug("Helm command:")
ui.Debug(cmd)

output, err := process.ExecuteWithOptions(process.Options{Command: helmPath, Args: args, DryRun: dryRun})
ui.DebugNL()
ui.Debug("Helm output:")
ui.Debug(string(output))
if err != nil {
return "", NewCLIError(
TKErrHelmCommandFailed,
"Helm command failed",
"Retry the command with a bigger timeout by setting --timeout 30m, if the error still persists, reach out to Testkube support",
"Retry the command with a bigger timeout by setting --helm-arg timeout=30m, if the error still persists, reach out to Testkube support",
err,
)
).WithExecutedCommand(cmd)
}
return string(output), nil
}

func appendHelmArgs(args []string, options HelmOptions, settings map[string]string) []string {
for key, value := range settings {
if _, ok := options.SetOptions[key]; !ok {
args = append(args, "--set", fmt.Sprintf("%s=%s", key, value))
}
}

for key, value := range options.SetOptions {
args = append(args, "--set", fmt.Sprintf("%s=%s", key, value))
}

for key, value := range options.ArgOptions {
args = append(args, fmt.Sprintf("--%s", key))
if value != "" {
args = append(args, value)
}
}

return args
}

func prepareTestkubeOnPremDemoArgs(options HelmOptions) []string {
return []string{
args := []string{
"upgrade", "--install",
"--create-namespace",
"--namespace", options.Namespace,
"--set", "global.enterpriseLicenseKey=" + options.LicenseKey,
"--values", options.DemoValuesURL,
}

settings := map[string]string{
"global.enterpriseLicenseKey": options.LicenseKey,
}

args = append(appendHelmArgs(args, options, settings), "--values", options.DemoValuesURL,
"--wait",
"testkube", "testkubeenterprise/testkube-enterprise"}
"testkube", "testkubeenterprise/testkube-enterprise")

return args
}

// prepareTestkubeProHelmArgs prepares Helm arguments for Testkube Pro installation.
func prepareTestkubeProHelmArgs(options HelmOptions, isMigration bool) []string {
args := prepareCommonHelmArgs(options)
args, settings := prepareCommonHelmArgs(options)

args = append(args,
"--set", "testkube-api.cloud.url="+options.Master.URIs.Agent,
"--set", "testkube-api.cloud.key="+options.Master.AgentToken,
"--set", "testkube-api.cloud.uiURL="+options.Master.URIs.Ui,
"--set", "testkube-logs.pro.url="+options.Master.URIs.Logs,
"--set", "testkube-logs.pro.key="+options.Master.AgentToken,
)
settings["testkube-api.cloud.url"] = options.Master.URIs.Agent
settings["testkube-api.cloud.key"] = options.Master.AgentToken
settings["testkube-api.cloud.uiURL"] = options.Master.URIs.Ui
settings["testkube-logs.pro.url"] = options.Master.URIs.Logs
settings["testkube-logs.pro.key"] = options.Master.AgentToken

if isMigration {
args = append(args, "--set", "testkube-api.cloud.migrate=true")
settings["testkube-api.cloud.migrate"] = "true"
}

if options.Master.EnvId != "" {
args = append(args, "--set", fmt.Sprintf("testkube-api.cloud.envId=%s", options.Master.EnvId))
args = append(args, "--set", fmt.Sprintf("testkube-logs.pro.envId=%s", options.Master.EnvId))
settings["testkube-api.cloud.envId"] = options.Master.EnvId
settings["testkube-logs.pro.envId"] = options.Master.EnvId
}

if options.Master.OrgId != "" {
args = append(args, "--set", fmt.Sprintf("testkube-api.cloud.orgId=%s", options.Master.OrgId))
args = append(args, "--set", fmt.Sprintf("testkube-logs.pro.orgId=%s", options.Master.OrgId))
settings["testkube-api.cloud.orgId"] = options.Master.OrgId
settings["testkube-logs.pro.orgId"] = options.Master.OrgId
}

return args
return appendHelmArgs(args, options, settings)
}

// prepareTestkubeHelmArgs prepares Helm arguments for Testkube OS installation.
func prepareTestkubeHelmArgs(options HelmOptions) []string {
args := prepareCommonHelmArgs(options)
args, settings := prepareCommonHelmArgs(options)

if options.NoMinio {
args = append(args, "--set", "testkube-api.logs.storage=mongo")
settings["testkube-api.logs.storage"] = "mongo"
} else {
args = append(args, "--set", "testkube-api.logs.storage=minio")
settings["testkube-api.logs.storage"] = "minio"
}

return args
return appendHelmArgs(args, options, settings)
}

// prepareCommonHelmArgs prepares common Helm arguments for both OS and Pro installation.
func prepareCommonHelmArgs(options HelmOptions) []string {
func prepareCommonHelmArgs(options HelmOptions) ([]string, map[string]string) {
args := []string{
"upgrade", "--install", "--create-namespace",
"--namespace", options.Namespace,
"--set", fmt.Sprintf("global.features.logsV2=%v", options.Master.Features.LogsV2),
"--set", fmt.Sprintf("testkube-api.multinamespace.enabled=%t", options.MultiNamespace),
"--set", fmt.Sprintf("testkube-api.minio.enabled=%t", !options.NoMinio),
"--set", fmt.Sprintf("testkube-api.minio.replicas=%d", options.MinioReplicas), "--set", fmt.Sprintf("testkube-operator.enabled=%t", !options.NoOperator),
"--set", fmt.Sprintf("mongodb.enabled=%t", !options.NoMongo),
"--set", fmt.Sprintf("mongodb.replicas=%d", options.MongoReplicas),
}

settings := map[string]string{
"global.features.logsV2": fmt.Sprintf("%v", options.Master.Features.LogsV2),
"testkube-api.multinamespace.enabled": fmt.Sprintf("%t", options.MultiNamespace),
"testkube-api.minio.enabled": fmt.Sprintf("%t", !options.NoMinio),
"testkube-api.minio.replicas": fmt.Sprintf("%d", options.MinioReplicas),
"testkube-operator.enabled": fmt.Sprintf("%t", !options.NoOperator),
"mongodb.enabled": fmt.Sprintf("%t", !options.NoMongo),
"mongodb.replicas": fmt.Sprintf("%d", options.MongoReplicas),
}

if options.Values != "" {
Expand All @@ -277,12 +313,12 @@ func prepareCommonHelmArgs(options HelmOptions) []string {

// if embedded nats is enabled disable nats chart
if options.EmbeddedNATS {
args = append(args, "--set", "testkube-api.nats.enabled=false")
args = append(args, "--set", "testkube-api.nats.embedded=true")
settings["testkube-api.nats.enabled"] = "false"
settings["testkube-api.nats.embedded"] = "true"
}

args = append(args, options.Name, options.Chart)
return args
return args, settings
}

func PopulateHelmFlags(cmd *cobra.Command, options *HelmOptions) {
Expand Down Expand Up @@ -463,6 +499,7 @@ func LoginUser(authUri string, customConnector bool) (string, string, error) {
connectorID = ui.Select("Choose your login method", []string{github, gitlab})
}

ui.Debug("Logging into cloud with parameters", authUri, connectorID)
authUrl, tokenChan, err := cloudlogin.CloudLogin(context.Background(), authUri, strings.ToLower(connectorID))
if err != nil {
return "", "", fmt.Errorf("cloud login: %w", err)
Expand All @@ -477,6 +514,7 @@ func LoginUser(authUri string, customConnector bool) (string, string, error) {
return "", "", fmt.Errorf("login cancelled")
}

ui.Debug("Opening login page in browser to get a token", authUrl)
// open browser with login page and redirect to localhost
open.Run(authUrl)

Expand Down Expand Up @@ -732,14 +770,21 @@ func lookupKubectlPath() (string, *CLIError) {
}

func runKubectlCommand(kubectlPath string, args []string) (output string, cliErr *CLIError) {
cmd := strings.Join(append([]string{kubectlPath}, args...), " ")
ui.DebugNL()
ui.Debug("Kubectl command:")
ui.Debug(cmd)
out, err := process.Execute(kubectlPath, args...)
ui.DebugNL()
ui.Debug("Kubectl output:")
ui.Debug(string(out))
if err != nil {
return "", NewCLIError(
TKErrKubectlCommandFailed,
"Kubectl command failed",
"Check does the kubeconfig file (~/.kube/config) exist and has correct permissions and is the Kubernetes cluster reachable and has Ready nodes by running 'kubectl get nodes' ",
err,
)
).WithExecutedCommand(cmd)
}
return string(out), nil
}
Expand All @@ -766,7 +811,7 @@ func RunDockerCommand(args []string) (output string, cliErr *CLIError) {
"Docker command failed",
"Check is the Docker service installed and running on your computer by executing 'docker info' ",
err,
)
).WithExecutedCommand(strings.Join(append([]string{"docker"}, args...), " "))
}
return string(out), nil
}
Expand Down
7 changes: 6 additions & 1 deletion cmd/kubectl-testkube/commands/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,29 +71,34 @@ func openOnPremDashboard(cmd *cobra.Command, cfg config.Data, verbose, skipBrows
uri := fmt.Sprintf("http://localhost:%d", uiLocalPort)

ctx, cancel := context.WithCancel(context.Background())

ui.Debug("Port forwarding for api", config.EnterpriseApiName)
err = k8sclient.PortForward(ctx, cfg.Namespace, config.EnterpriseApiName, config.EnterpriseApiPort, config.EnterpriseApiForwardingPort, verbose)
if err != nil {
sendErrTelemetry(cmd, cfg, "port_forward", license, "port forwarding api", err)
}
ui.ExitOnError("port forwarding api", err)
ui.Debug("Port forwarding for ui", config.EnterpriseUiName)
err = k8sclient.PortForward(ctx, cfg.Namespace, config.EnterpriseUiName, config.EnterpriseUiPort, uiLocalPort, verbose)
if err != nil {
sendErrTelemetry(cmd, cfg, "port_forward", license, "port forwarding ui", err)
}
ui.ExitOnError("port forwarding ui", err)
ui.Debug("Port forwarding for dex", config.EnterpriseDexName)
err = k8sclient.PortForward(ctx, cfg.Namespace, config.EnterpriseDexName, config.EnterpriseDexPort, config.EnterpriseDexForwardingPort, verbose)
if err != nil {
sendErrTelemetry(cmd, cfg, "port_forward", license, "port forwarding dex", err)
}
ui.ExitOnError("port forwarding dex", err)

ui.Debug("Port forwarding for minio", config.EnterpriseMinioName)
err = k8sclient.PortForward(ctx, cfg.Namespace, config.EnterpriseMinioName, config.EnterpriseMinioPort, config.EnterpriseMinioPortFrwardingPort, verbose)
if err != nil {
sendTelemetry(cmd, cfg, license, "port forwarding minio")
}
ui.ExitOnError("port forwarding minio", err)

if !skipBrowser {
ui.Debug("Opening dashboard in browser", uri)
err = open.Run(uri)
if err != nil {
sendErrTelemetry(cmd, cfg, "open_dashboard", license, "opening dashboard", err)
Expand Down
Loading

0 comments on commit b806dd3

Please sign in to comment.