From 3ff1c4fa72d237394cd3be13dc400d8d0bdf70e4 Mon Sep 17 00:00:00 2001 From: Jacek Wysocki Date: Fri, 28 Jun 2024 12:32:57 +0200 Subject: [PATCH] feat: improved debug cmd (#5617) * feat: improved debug cmd * feat: added possibility to move around different debug sets * chore: rename features to show * fix: unused code cleanup * fix: proper error message instead of exit 1 --- cmd/kubectl-testkube/commands/agent/debug.go | 45 +---- cmd/kubectl-testkube/commands/common/flags.go | 28 +++ .../commands/common/helper.go | 110 ++++++++++- cmd/kubectl-testkube/commands/debug.go | 67 +------ cmd/kubectl-testkube/commands/debug/agent.go | 130 +++++++++++++ .../commands/debug/controlplane.go | 175 +++++++++--------- .../commands/debug/features.go | 50 +++++ cmd/kubectl-testkube/commands/debug/info.go | 26 --- cmd/kubectl-testkube/commands/debug/oss.go | 36 ++++ common.UiGetNamespace | 0 pkg/process/exec.go | 24 ++- pkg/ui/textinput.go | 10 +- pkg/ui/ui.go | 4 +- 13 files changed, 481 insertions(+), 224 deletions(-) create mode 100644 cmd/kubectl-testkube/commands/debug/agent.go create mode 100644 cmd/kubectl-testkube/commands/debug/features.go delete mode 100644 cmd/kubectl-testkube/commands/debug/info.go create mode 100644 cmd/kubectl-testkube/commands/debug/oss.go create mode 100644 common.UiGetNamespace diff --git a/cmd/kubectl-testkube/commands/agent/debug.go b/cmd/kubectl-testkube/commands/agent/debug.go index 612342b1c0d..23c15d2efef 100644 --- a/cmd/kubectl-testkube/commands/agent/debug.go +++ b/cmd/kubectl-testkube/commands/agent/debug.go @@ -2,52 +2,13 @@ package agent import ( "github.com/spf13/cobra" - - "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" - "github.com/kubeshop/testkube/cmd/kubectl-testkube/config" - "github.com/kubeshop/testkube/pkg/ui" ) func NewDebugAgentCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "debug", - Short: "Debug Agent info", - Run: func(cmd *cobra.Command, args []string) { - cfg, err := config.Load() - ui.ExitOnError("loading config file", err) - ui.NL() - - if cfg.ContextType != config.ContextTypeCloud { - ui.Errf("Agent debug is only available for cloud context") - ui.NL() - ui.ShellCommand("Please try command below to set your context into Cloud mode", `testkube set context -o -e -k `) - ui.NL() - return - } - - common.UiPrintContext(cfg) - - client, _, err := common.GetClient(cmd) - ui.ExitOnError("getting client", err) - - i, err := client.GetServerInfo() - if err != nil { - ui.Errf("Error %v", err) - ui.NL() - ui.Info("Possible reasons:") - ui.Warn("- Please check if your agent organization and environment are set correctly") - ui.Warn("- Please check if your API token is set correctly") - ui.NL() - } else { - ui.Warn("Agent correctly connected to cloud:\n") - ui.InfoGrid(map[string]string{ - "Agent version ": i.Version, - "Agent namespace": i.Namespace, - }) - } - ui.NL() - }, + Use: "debug", + Short: "Debug Agent info", + Deprecated: "use `testkube debug agent` instead", } return cmd diff --git a/cmd/kubectl-testkube/commands/common/flags.go b/cmd/kubectl-testkube/commands/common/flags.go index 92333bea261..0363c96d009 100644 --- a/cmd/kubectl-testkube/commands/common/flags.go +++ b/cmd/kubectl-testkube/commands/common/flags.go @@ -188,3 +188,31 @@ func ProcessMasterFlags(cmd *cobra.Command, opts *HelmOptions, cfg *config.Data) func IsBothEnabledAndDisabledSet(cmd *cobra.Command) bool { return cmd.Flag("enable-webhooks").Changed && cmd.Flag("disable-webhooks").Changed } + +// CommaList is a custom flag type for features +type CommaList []string + +func (s CommaList) String() string { + return strings.Join(s, ",") +} +func (s *CommaList) Type() string { + return "[]string" +} + +func (s *CommaList) Set(value string) error { + *s = strings.Split(value, ",") + return nil +} + +// Enabled returns true if the feature is enabled, defaults to all +func (s *CommaList) Enabled(value string) bool { + if len(*s) == 0 { + return true + } + for _, f := range *s { + if f == value { + return true + } + } + return false +} diff --git a/cmd/kubectl-testkube/commands/common/helper.go b/cmd/kubectl-testkube/commands/common/helper.go index 1b8ce4ce81a..cdf5da2d3fd 100644 --- a/cmd/kubectl-testkube/commands/common/helper.go +++ b/cmd/kubectl-testkube/commands/common/helper.go @@ -434,7 +434,7 @@ func uiGetToken(tokenChan chan cloudlogin.Tokens) (string, string, error) { return token.IDToken, token.RefreshToken, nil } -func KubectlPrintLogs(namespace string, labels map[string]string) error { +func KubectlLogs(namespace string, labels map[string]string) error { kubectl, err := exec.LookPath("kubectl") if err != nil { return err @@ -491,6 +491,24 @@ func KubectlPrintEvents(namespace string) error { return process.ExecuteAndStreamOutput(kubectl, args...) } +func KubectlDescribePods(namespace string) error { + kubectl, err := exec.LookPath("kubectl") + if err != nil { + return err + } + + args := []string{ + "describe", + "pods", + "-n", namespace, + } + + ui.ShellCommand(kubectl, args...) + ui.NL() + + return process.ExecuteAndStreamOutput(kubectl, args...) +} + func KubectlPrintPods(namespace string) error { kubectl, err := exec.LookPath("kubectl") if err != nil { @@ -510,7 +528,7 @@ func KubectlPrintPods(namespace string) error { return process.ExecuteAndStreamOutput(kubectl, args...) } -func KubectlPrintStorageClass(namespace string) error { +func KubectlGetStorageClass(namespace string) error { kubectl, err := exec.LookPath("kubectl") if err != nil { return err @@ -526,3 +544,91 @@ func KubectlPrintStorageClass(namespace string) error { return process.ExecuteAndStreamOutput(kubectl, args...) } + +func KubectlGetServices(namespace string) error { + kubectl, err := exec.LookPath("kubectl") + if err != nil { + return err + } + + args := []string{ + "get", + "services", + "-n", namespace, + } + + ui.ShellCommand(kubectl, args...) + ui.NL() + + return process.ExecuteAndStreamOutput(kubectl, args...) +} + +func KubectlDescribeServices(namespace string) error { + kubectl, err := exec.LookPath("kubectl") + if err != nil { + return err + } + + args := []string{ + "get", + "services", + "-n", namespace, + "-o", "yaml", + } + + ui.ShellCommand(kubectl, args...) + ui.NL() + + return process.ExecuteAndStreamOutput(kubectl, args...) +} + +func KubectlGetIngresses(namespace string) error { + kubectl, err := exec.LookPath("kubectl") + if err != nil { + return err + } + + args := []string{ + "get", + "ingresses", + "-n", namespace, + } + + ui.ShellCommand(kubectl, args...) + ui.NL() + + return process.ExecuteAndStreamOutput(kubectl, args...) +} + +func KubectlDescribeIngresses(namespace string) error { + kubectl, err := exec.LookPath("kubectl") + if err != nil { + return err + } + + args := []string{ + "get", + "ingresses", + "-n", namespace, + "-o", "yaml", + } + + ui.ShellCommand(kubectl, args...) + ui.NL() + + return process.ExecuteAndStreamOutput(kubectl, args...) +} + +func UiGetNamespace(cmd *cobra.Command, defaultNamespace string) string { + var namespace string + var err error + + if cmd.Flag("namespace").Changed { + namespace, err = cmd.Flags().GetString("namespace") + ui.ExitOnError("getting namespace", err) + } else { + namespace = ui.TextInput("Please provide namespace for Control Plane", defaultNamespace) + } + + return namespace +} diff --git a/cmd/kubectl-testkube/commands/debug.go b/cmd/kubectl-testkube/commands/debug.go index 6e8a362521a..cbc4a4e9bff 100644 --- a/cmd/kubectl-testkube/commands/debug.go +++ b/cmd/kubectl-testkube/commands/debug.go @@ -3,11 +3,7 @@ package commands import ( "github.com/spf13/cobra" - "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" - "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common/validator" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/debug" - "github.com/kubeshop/testkube/cmd/kubectl-testkube/config" - "github.com/kubeshop/testkube/pkg/ui" ) // NewDebugCmd creates the 'testkube debug' command @@ -15,66 +11,11 @@ func NewDebugCmd() *cobra.Command { cmd := &cobra.Command{ Use: "debug", Aliases: []string{"dbg", "d"}, - Short: "Print environment information for debugging", - Run: func(cmd *cobra.Command, args []string) { + Short: "Print debugging info", + } - cfg, err := config.Load() - ui.ExitOnError("loading config file", err) - - if cfg.ContextType == config.ContextTypeCloud { - cfg, err := config.Load() - ui.ExitOnError("loading config file", err) - ui.NL() - - if cfg.ContextType != config.ContextTypeCloud { - ui.Errf("Agent debug is only available for cloud context") - ui.NL() - ui.ShellCommand("Please try command below to set your context into Cloud mode", `testkube set context -o -e -k `) - ui.NL() - return - } - - common.UiPrintContext(cfg) - - client, _, err := common.GetClient(cmd) - ui.ExitOnError("getting client", err) - - i, err := client.GetServerInfo() - if err != nil { - ui.Errf("Error %v", err) - ui.NL() - ui.Info("Possible reasons:") - ui.Warn("- Please check if your agent organization and environment are set correctly") - ui.Warn("- Please check if your API token is set correctly") - ui.NL() - } else { - ui.Warn("Agent correctly connected to cloud:\n") - ui.InfoGrid(map[string]string{ - "Agent version ": i.Version, - "Agent namespace": i.Namespace, - }) - } - ui.NL() - } else { - client, _, err := common.GetClient(cmd) - ui.ExitOnError("getting client", err) - - d, err := debug.GetDebugInfo(client) - ui.ExitOnError("get debug info", err) - - debug.PrintDebugInfo(d) - - } - }, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - cfg, err := config.Load() - ui.ExitOnError("loading config", err) - common.UiContextHeader(cmd, cfg) - - validator.PersistentPreRunVersionCheck(cmd, common.Version) - }} - - cmd.AddCommand(debug.NewShowDebugInfoCmd()) + cmd.AddCommand(debug.NewDebugOssCmd()) + cmd.AddCommand(debug.NewDebugAgentCmd()) cmd.AddCommand(debug.NewDebugControlPlaneCmd()) return cmd diff --git a/cmd/kubectl-testkube/commands/debug/agent.go b/cmd/kubectl-testkube/commands/debug/agent.go new file mode 100644 index 00000000000..70195f7b53b --- /dev/null +++ b/cmd/kubectl-testkube/commands/debug/agent.go @@ -0,0 +1,130 @@ +package debug + +import ( + "github.com/spf13/cobra" + + "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" + "github.com/kubeshop/testkube/cmd/kubectl-testkube/config" + "github.com/kubeshop/testkube/pkg/ui" +) + +const ( + defaultAgentNamespace = "testkube" +) + +func NewDebugAgentCmd() *cobra.Command { + var show common.CommaList + + cmd := &cobra.Command{ + Use: "agent", + Aliases: []string{"ag", "a"}, + Short: "Show Agent debug information", + Long: "Get all the necessary information to debug an issue in Testkube Agent you can fiter through comma separated list of items to show with additional flag `--show " + agentFeaturesStr + "`", + Run: RunDebugAgentCmdFunc(&show), + } + + cmd.Flags().VarP(&show, "show", "s", "Comma-separated list of features to show, one of: "+agentFeaturesStr+", defaults to all") + + return cmd +} + +func RunDebugAgentCmdFunc(show *common.CommaList) func(cmd *cobra.Command, args []string) { + return func(cmd *cobra.Command, args []string) { + cfg, err := config.Load() + ui.ExitOnError("loading config file", err) + ui.NL() + + ui.H1("Agent Insights") + + if cfg.ContextType != config.ContextTypeCloud { + ui.Errf("Agent debug is only available for cloud context") + ui.NL() + ui.ShellCommand("Please try command below to set your context into Cloud mode", `testkube set context -o -e -k `) + ui.NL() + return + } + + namespace := common.UiGetNamespace(cmd, defaultAgentNamespace) + + if show.Enabled(showPods) { + ui.H2("Pods") + err = common.KubectlPrintPods(namespace) + ui.WarnOnError("getting Kubernetes pods", err) + + ui.NL(3) + err = common.KubectlDescribePods(namespace) + ui.WarnOnError("describing Kubernetes pods", err) + } + + if show.Enabled(showServices) { + ui.H2("Services") + err = common.KubectlGetServices(namespace) + ui.WarnOnError("describing Kubernetes pods", err) + + ui.NL(3) + err = common.KubectlDescribeServices(namespace) + ui.WarnOnError("describing Kubernetes services", err) + } + + if show.Enabled(showIngresses) { + ui.H2("Ingresses") + err = common.KubectlGetIngresses(namespace) + ui.WarnOnError("describing Kubernetes ingresses", err) + } + + if show.Enabled(showApiLogs) { + ui.H2("Agent API Logs") + err = common.KubectlLogs(namespace, map[string]string{"app.kubernetes.io/name": "api-server"}) + ui.ExitOnError("getting agent logs", err) + ui.NL(2) + } + + if show.Enabled(showNatsLogs) { + ui.H2("NATS logs") + err = common.KubectlLogs(namespace, map[string]string{"app.kubernetes.io/name": "nats"}) + ui.WarnOnError("getting worker service logs", err) + } + + if show.Enabled(showEvents) { + ui.H2("Kubernetes Events") + err = common.KubectlPrintEvents(namespace) + ui.WarnOnError("getting Kubernetes events", err) + } + + client, _, err := common.GetClient(cmd) + ui.ExitOnError("getting client", err) + + if show.Enabled(showRoundtrip) { + ui.H2("Agent connection through Control Plane from CLI") + + i, err := client.GetServerInfo() + if err != nil { + ui.Errf("Error while doing roundtrip to agent: %v", err) + ui.NL() + ui.Info("Possible reasons:") + ui.Warn("- Please check if your agent organization and environment are set correctly") + ui.Warn("- Please check if your API token is set correctly") + ui.NL() + } else { + ui.Warn("Agent correctly connected to cloud:\n") + ui.InfoGrid(map[string]string{ + "Agent version ": i.Version, + "Agent namespace": i.Namespace, + }) + } + } + + if show.Enabled(showCLIToControlPlane) { + ui.H2("Agent connection to Control Plane from CLI") + + debug, err := GetDebugInfo(client) + ui.ExitOnError("connecting to Control Plane", err) + PrintDebugInfo(debug) + ui.NL(2) + + common.UiPrintContext(cfg) + } + + ui.NL() + } +} diff --git a/cmd/kubectl-testkube/commands/debug/controlplane.go b/cmd/kubectl-testkube/commands/debug/controlplane.go index 9efd6dc9bec..959540f056c 100644 --- a/cmd/kubectl-testkube/commands/debug/controlplane.go +++ b/cmd/kubectl-testkube/commands/debug/controlplane.go @@ -1,8 +1,6 @@ package debug import ( - "os" - "github.com/spf13/cobra" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" @@ -11,100 +9,107 @@ import ( // NewDebugControlPlaneCmd creates a new cobra command to print the debug info to the CLI func NewDebugControlPlaneCmd() *cobra.Command { - var additionalLabels map[string]string - var attachAgentLogs bool + const defaultCPNamespace = "testkube-enterprise" + var features common.CommaList cmd := &cobra.Command{ Use: "controlplane", - Aliases: []string{"ctl", "cp"}, - Short: "Show debug info", - Long: "Get all the necessary information to debug an issue in Testkube Control Plane", + Aliases: []string{"ctl", "cp", "c"}, + Short: "Show Control Plane debug information", + Long: "Get all the necessary information to debug an issue in Testkube Control Plane you can fiter through comma separated list of items to show with additional flag `--show " + controlPlaneFeaturesStr + "`", Run: func(cmd *cobra.Command, args []string) { + namespace := common.UiGetNamespace(cmd, defaultCPNamespace) + + ui.H1("Getting Control Plane insights, namespace: " + namespace) + + if features.Enabled(showPods) { + ui.H2("Pods") + err := common.KubectlPrintPods(namespace) + ui.WarnOnError("getting Kubernetes pods", err) + + ui.NL(3) + err = common.KubectlDescribePods(namespace) + ui.WarnOnError("describing Kubernetes pods", err) + } + + if features.Enabled(showServices) { + ui.H2("Services") + err := common.KubectlGetServices(namespace) + ui.WarnOnError("describing Kubernetes pods", err) + + ui.NL(3) + err = common.KubectlDescribeServices(namespace) + ui.WarnOnError("describing Kubernetes services", err) + } + + if features.Enabled(showIngresses) { + ui.H2("Ingresses") + err := common.KubectlGetIngresses(namespace) + ui.WarnOnError("describing Kubernetes ingresses", err) + + ui.NL(3) + err = common.KubectlDescribeIngresses(namespace) + ui.WarnOnError("describing Kubernetes services", err) + } - spinner := ui.NewSpinner("").WithWriter(os.Stderr) - spinner, err := spinner.Start() - ui.ExitOnError("starting spinner", err) - - namespace, err := cmd.Flags().GetString("namespace") - ui.ExitOnError("getting namespace", err) - - ui.H1("Getting control plane logs") - - spinner.UpdateText("Getting Kubernetes pods") - ui.H2("Kubernetes Pods in namespace:" + namespace) - err = common.KubectlPrintPods(namespace) - ui.WarnOnError("getting Kubernetes pods", err) - - spinner.UpdateText("Kubernetes Storage Classes") - ui.H2("Kubernetes Storage Classes") - err = common.KubectlPrintStorageClass(namespace) - ui.WarnOnError("getting Kubernetes Storage Classes", err) - - spinner.UpdateText("API Server Logs") - ui.H2("API Server Logs") - err = common.KubectlPrintLogs(namespace, map[string]string{"app.kubernetes.io/name": "testkube-cloud-api"}) - ui.WarnOnError("getting api server logs", err) - - spinner.UpdateText("Worker Service Logs") - ui.H2("Worker Service Logs") - err = common.KubectlPrintLogs(namespace, map[string]string{"app.kubernetes.io/name": "testkube-worker-service"}) - ui.WarnOnError("getting worker service logs", err) - - spinner.UpdateText("UI Logs") - ui.H2("UI Logs") - err = common.KubectlPrintLogs(namespace, map[string]string{"app.kubernetes.io/name": "testkube-cloud-ui"}) - ui.WarnOnError("getting UI logs", err) - - spinner.UpdateText("UI Logs") - ui.H2("Dex Logs") - err = common.KubectlPrintLogs(namespace, map[string]string{"app.kubernetes.io/name": "dex"}) - ui.WarnOnError("getting Dex logs", err) - - spinner.UpdateText("UI Logs") - ui.H2("Minio Logs") - err = common.KubectlPrintLogs(namespace, map[string]string{"app.kubernetes.io/name": "minio"}) - ui.WarnOnError("getting MinIO logs", err) - - spinner.UpdateText("MongoDB logs") - ui.H2("MongoDB logs") - err = common.KubectlPrintLogs(namespace, map[string]string{"app.kubernetes.io/name": "mongodb"}) - ui.WarnOnError("getting MongoDB logs", err) - - spinner.UpdateText("NATS Logs") - ui.H2("NATS logs") - err = common.KubectlPrintLogs(namespace, map[string]string{"app.kubernetes.io/name": "nats"}) - ui.WarnOnError("getting worker service logs", err) - - spinner.UpdateText("Kubernetes Events") - ui.H2("Kubernetes Events") - err = common.KubectlPrintEvents(namespace) - ui.WarnOnError("getting Kubernetes events", err) - - if cmd.Flag("attach-agent-log").Value.String() == "true" { - spinner.UpdateText("UI Logs") - ui.H2("Agent Logs") - err = common.KubectlPrintLogs(namespace, map[string]string{"app.kubernetes.io/name": "testkube-agent"}) - ui.ExitOnError("getting agent logs", err) - - spinner.UpdateText("UI Logs") - ui.H1("Agent debug info") - client, _, err := common.GetClient(cmd) - ui.ExitOnError("getting client", err) - - debug, err := GetDebugInfo(client) - ui.ExitOnError("get debug info", err) - - PrintDebugInfo(debug) + if features.Enabled(showStorageClasses) { + ui.H2("Storage Classes") + err := common.KubectlGetStorageClass(namespace) + ui.WarnOnError("getting Kubernetes Storage Classes", err) } - spinner.Success("Testkube logs collected successfully") + if features.Enabled(showEvents) { + ui.H2("Kubernetes Events") + err := common.KubectlPrintEvents(namespace) + ui.WarnOnError("getting Kubernetes events", err) + } + + if features.Enabled(showApiLogs) { + ui.H2("API Server Logs") + err := common.KubectlLogs(namespace, map[string]string{"app.kubernetes.io/name": "testkube-cloud-api"}) + ui.WarnOnError("getting api server logs", err) + } + + if features.Enabled(showWorkerLogs) { + ui.H2("Worker Service Logs") + err := common.KubectlLogs(namespace, map[string]string{"app.kubernetes.io/name": "testkube-worker-service"}) + ui.WarnOnError("getting worker service logs", err) + } + + if features.Enabled(showUiLogs) { + ui.H2("UI Logs") + err := common.KubectlLogs(namespace, map[string]string{"app.kubernetes.io/name": "testkube-cloud-ui"}) + ui.WarnOnError("getting UI logs", err) + } + + if features.Enabled(showDexLogs) { + ui.H2("Dex Logs") + err := common.KubectlLogs(namespace, map[string]string{"app.kubernetes.io/name": "dex"}) + ui.WarnOnError("getting Dex logs", err) + } + + if features.Enabled(showMinioLogs) { + ui.H2("Minio Logs") + err := common.KubectlLogs(namespace, map[string]string{"app.kubernetes.io/name": "minio"}) + ui.WarnOnError("getting MinIO logs", err) + } + + if features.Enabled(showMongoLogs) { + ui.H2("MongoDB logs") + err := common.KubectlLogs(namespace, map[string]string{"app.kubernetes.io/name": "mongodb"}) + ui.WarnOnError("getting MongoDB logs", err) + } + + if features.Enabled(showNatsLogs) { + ui.H2("NATS logs") + err := common.KubectlLogs(namespace, map[string]string{"app.kubernetes.io/name": "nats"}) + ui.WarnOnError("getting worker service logs", err) + } }, } - cmd.Flags().StringToStringVar(&additionalLabels, "labels", map[string]string{}, "Labels to filter logs by") - cmd.Flags().BoolVar(&attachAgentLogs, "attach-agent-log", false, "Attach agent log to the output keep in mind to configure valid agent first in the Testkube CLI") + cmd.Flags().VarP(&features, "show", "s", "Comma-separated list of features to show, one of: "+controlPlaneFeaturesStr+", defaults to all") return cmd - } diff --git a/cmd/kubectl-testkube/commands/debug/features.go b/cmd/kubectl-testkube/commands/debug/features.go new file mode 100644 index 00000000000..cd8996f67cf --- /dev/null +++ b/cmd/kubectl-testkube/commands/debug/features.go @@ -0,0 +1,50 @@ +package debug + +import "strings" + +const ( + showPods = "pods" + showServices = "services" + showIngresses = "ingresses" + showStorageClasses = "storageclasses" + showEvents = "events" + showCLIToControlPlane = "connection" + showRoundtrip = "roundtrip" + + showApiLogs = "api" + showWorkerLogs = "worker" + showUiLogs = "ui" + showDexLogs = "dex" + showNatsLogs = "nats" + showMongoLogs = "mongo" + showMinioLogs = "minio" +) + +var controlPlaneFeatures = []string{ + showPods, + showServices, + showIngresses, + showStorageClasses, + showEvents, + showNatsLogs, + showCLIToControlPlane, + showApiLogs, + showNatsLogs, + showMongoLogs, + showDexLogs, + showUiLogs, + showWorkerLogs, +} + +var agentFeatures = []string{ + showPods, + showServices, + showIngresses, + showEvents, + showNatsLogs, + showCLIToControlPlane, + showRoundtrip, +} + +var agentFeaturesStr = strings.Join(agentFeatures, ",") +var controlPlaneFeaturesStr = strings.Join(controlPlaneFeatures, ",") diff --git a/cmd/kubectl-testkube/commands/debug/info.go b/cmd/kubectl-testkube/commands/debug/info.go deleted file mode 100644 index e6ba33bbe62..00000000000 --- a/cmd/kubectl-testkube/commands/debug/info.go +++ /dev/null @@ -1,26 +0,0 @@ -package debug - -import ( - "github.com/spf13/cobra" - - "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" - "github.com/kubeshop/testkube/pkg/ui" -) - -// NewShowDebugInfoCmd creates a new cobra command to print the debug info to the CLI -func NewShowDebugInfoCmd() *cobra.Command { - return &cobra.Command{ - Use: "info", - Short: "Show debug info", - Long: "Get all the necessary information to debug an issue in Testkube", - Run: func(cmd *cobra.Command, args []string) { - client, _, err := common.GetClient(cmd) - ui.ExitOnError("getting client", err) - - debug, err := GetDebugInfo(client) - ui.ExitOnError("get debug info", err) - - PrintDebugInfo(debug) - }, - } -} diff --git a/cmd/kubectl-testkube/commands/debug/oss.go b/cmd/kubectl-testkube/commands/debug/oss.go new file mode 100644 index 00000000000..e2994bc69f8 --- /dev/null +++ b/cmd/kubectl-testkube/commands/debug/oss.go @@ -0,0 +1,36 @@ +package debug + +import ( + "github.com/spf13/cobra" + + "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" + "github.com/kubeshop/testkube/cmd/kubectl-testkube/config" + "github.com/kubeshop/testkube/pkg/ui" +) + +func NewDebugOssCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "oss", + Aliases: []string{"o"}, + Short: "Show OSS installation debug info", + Run: func(cmd *cobra.Command, args []string) { + cfg, err := config.Load() + ui.ExitOnError("loading config file", err) + + if cfg.ContextType != config.ContextTypeKubeconfig { + ui.Errf("OSS debug is only available for kubeconfig context, use `testkube set context` to set kubeconfig context, or `testkube debug agent|controlplane` to debug other variants of Testkube") + return + } + + client, _, err := common.GetClient(cmd) + ui.ExitOnError("getting client", err) + + d, err := GetDebugInfo(client) + ui.ExitOnError("get debug info", err) + + PrintDebugInfo(d) + }, + } + + return cmd +} diff --git a/common.UiGetNamespace b/common.UiGetNamespace new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pkg/process/exec.go b/pkg/process/exec.go index 3ba974ac1df..8b591629aa5 100644 --- a/pkg/process/exec.go +++ b/pkg/process/exec.go @@ -7,6 +7,8 @@ import ( "io" "os/exec" "strings" + + "github.com/pkg/errors" ) type Options struct { @@ -145,8 +147,15 @@ func ExecuteAndStreamOutput(command string, arguments ...string) error { return err } + stderr, err := cmd.StderrPipe() + if err != nil { + return err + } + err = cmd.Start() if err != nil { + fmt.Printf("ERR: %+v\n", err) + return err } @@ -156,10 +165,23 @@ func ExecuteAndStreamOutput(command string, arguments ...string) error { m := scanner.Text() fmt.Println(m) } + if scanner.Err() != nil { + return scanner.Err() + } + scanner = bufio.NewScanner(stderr) + errorsBuffer := strings.Builder{} + for scanner.Scan() { + errorsBuffer.Write(scanner.Bytes()) + } if scanner.Err() != nil { return scanner.Err() } - return cmd.Wait() + err = cmd.Wait() + if err != nil { + return errors.Wrap(err, errorsBuffer.String()) + } + + return nil } diff --git a/pkg/ui/textinput.go b/pkg/ui/textinput.go index 7293dbc4715..9cc105f425b 100644 --- a/pkg/ui/textinput.go +++ b/pkg/ui/textinput.go @@ -2,10 +2,12 @@ package ui import "github.com/pterm/pterm" -func (ui *UI) TextInput(text string) string { - in, _ := pterm.DefaultInteractiveTextInput. - WithMultiLine(false). - Show(text) +func (ui *UI) TextInput(text string, defaultValue ...string) string { + t := pterm.DefaultInteractiveTextInput.WithMultiLine(false) + if len(defaultValue) > 0 { + t = t.WithDefaultValue(defaultValue[0]) + } + in, _ := t.Show(text) return in } diff --git a/pkg/ui/ui.go b/pkg/ui/ui.go index 6a7d13f5b6c..82563b7c39b 100644 --- a/pkg/ui/ui.go +++ b/pkg/ui/ui.go @@ -101,7 +101,9 @@ func NewArrayTable(a [][]string) ArrayTable { return ui.NewArray func PrintArrayTable(a [][]string) { ui.PrintArrayTable(a) } func Confirm(message string) bool { return ui.Confirm(message) } func Select(title string, options []string) string { return ui.Select(title, options) } -func TextInput(message string) string { return ui.TextInput(message) } +func TextInput(message string, defaultValue ...string) string { + return ui.TextInput(message, defaultValue...) +} func PrintCRD[T interface{}](cr T, kind string, groupVersion schema.GroupVersion) { PrintCRDs([]T{cr}, kind, groupVersion)