diff --git a/cmd/inspect/inspect.go b/cmd/inspect/inspect.go index 434b298..10bef65 100644 --- a/cmd/inspect/inspect.go +++ b/cmd/inspect/inspect.go @@ -3,17 +3,13 @@ package inspect import ( "bytes" "context" - "errors" "fmt" - "log/slog" - "net/url" "os" - "github.com/middlewaregruppen/tcli/pkg/client" + "github.com/middlewaregruppen/tcli/cmd/internal/auth" "github.com/spf13/cobra" "github.com/spf13/viper" "gopkg.in/yaml.v3" - "k8s.io/client-go/tools/clientcmd" ) var tanzuNamespace string @@ -31,12 +27,9 @@ Examples: Use "tcli --help" for a list of global command-line options (applies to all commands). `, PreRunE: func(cmd *cobra.Command, args []string) error { - if err := viper.BindPFlags(cmd.Flags()); err != nil { - return err - } - return nil + return viper.BindPFlags(cmd.Flags()) }, - RunE: func(cmd *cobra.Command, args []string) (err error) { + RunE: func(cmd *cobra.Command, args []string) error { ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) defer cancel() @@ -46,46 +39,14 @@ Examples: insecureSkipVerify := viper.GetBool("insecure") kubeconfig := viper.GetString("kubeconfig") - u, err := url.Parse(tanzuServer) + c, contextNamespace, err := auth.ClientFromKubeconfig(tanzuServer, kubeconfig, tanzuUsername, insecureSkipVerify) if err != nil { return err } - // Read kubeconfig from file - conf, err := clientcmd.LoadFromFile(kubeconfig) - if err != nil { - return err - } - - // Find credentials from kubeconfig context - contextName := u.Host - if _, ok := conf.Contexts[contextName]; !ok { - return errors.New("credentials missing! Please run 'tcli login' to authenticate") - } - - // AuthInfo name is whatever is set in the context. However it can be overriden with the --username flag - authName := fmt.Sprintf("wcp:%s:%s", u.Host, conf.Contexts[contextName].AuthInfo) - if len(tanzuUsername) > 0 { - authName = fmt.Sprintf("wcp:%s:%s", u.Host, tanzuUsername) - } - - // Check if the AuthInfo object exists - if _, ok := conf.AuthInfos[authName]; !ok { - return errors.New("credentials missing! Please run 'tcli login' to authenticate") - } - - // Check if there is a namespace set in the context that we can use so that we don't have to specify the --namespace flag - if _, ok := conf.Contexts[contextName]; ok && len(tanzuNamespace) == 0 { - tanzuNamespace = conf.Contexts[contextName].Namespace - } - - token := conf.AuthInfos[authName].Token - - // Create rest client - logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) - c, err := client.New(tanzuServer, client.WithLogger(logger), client.WithCredentials(client.TokenCredentials(token)), client.WithInsecure(insecureSkipVerify)) - if err != nil { - return err + // If --namespace was not given, fall back to the namespace stored in the kubeconfig context + if len(tanzuNamespace) == 0 { + tanzuNamespace = contextNamespace } cluster, err := c.Cluster(ctx, tanzuNamespace, tanzuCluster) @@ -96,13 +57,11 @@ Examples: buf := bytes.Buffer{} yamlEncoder := yaml.NewEncoder(&buf) yamlEncoder.SetIndent(2) - err = yamlEncoder.Encode(cluster) - if err != nil { - return err + if err := yamlEncoder.Encode(cluster); err != nil { + return fmt.Errorf("encoding cluster as YAML: %w", err) } - _, err = buf.WriteTo(os.Stdout) - if err != nil { - return err + if _, err := buf.WriteTo(os.Stdout); err != nil { + return fmt.Errorf("writing output: %w", err) } return nil }, diff --git a/cmd/internal/auth/auth.go b/cmd/internal/auth/auth.go new file mode 100644 index 0000000..1c21fe8 --- /dev/null +++ b/cmd/internal/auth/auth.go @@ -0,0 +1,77 @@ +// Package auth provides shared helpers for resolving credentials from a +// kubeconfig file and constructing an authenticated API client. +package auth + +import ( + "errors" + "fmt" + "log/slog" + "net/url" + "os" + + "github.com/middlewaregruppen/tcli/pkg/client" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +// ErrNotAuthenticated is returned when the expected kubeconfig context or +// authinfo is missing — i.e. the user has not run "tcli login" yet. +var ErrNotAuthenticated = errors.New("credentials missing! Please run 'tcli login' to authenticate") + +// ClientFromKubeconfig loads the kubeconfig at kubeconfigPath, resolves the +// stored session token for the given server, and returns a ready-to-use +// client.Client together with the resolved namespace from the context. +// +// If username is non-empty it overrides the username stored in the context +// when constructing the authinfo key. +func ClientFromKubeconfig(server, kubeconfigPath, username string, insecure bool) (client.Client, string, error) { + u, err := url.Parse(server) + if err != nil { + return nil, "", fmt.Errorf("parsing server URL: %w", err) + } + + conf, err := clientcmd.LoadFromFile(kubeconfigPath) + if err != nil { + return nil, "", fmt.Errorf("loading kubeconfig: %w", err) + } + + token, namespace, err := TokenFromConfig(conf, u.Host, username) + if err != nil { + return nil, "", err + } + + logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) + c, err := client.New( + server, + client.WithLogger(logger), + client.WithCredentials(client.TokenCredentials(token)), + client.WithInsecure(insecure), + ) + if err != nil { + return nil, "", fmt.Errorf("creating client: %w", err) + } + + return c, namespace, nil +} + +// TokenFromConfig resolves the session token and context namespace from an +// already-loaded kubeconfig, given the supervisor host (u.Host) and an +// optional username override. +func TokenFromConfig(conf *clientcmdapi.Config, host, username string) (token, namespace string, err error) { + ctx, ok := conf.Contexts[host] + if !ok { + return "", "", ErrNotAuthenticated + } + + authName := fmt.Sprintf("wcp:%s:%s", host, ctx.AuthInfo) + if len(username) > 0 { + authName = fmt.Sprintf("wcp:%s:%s", host, username) + } + + authInfo, ok := conf.AuthInfos[authName] + if !ok { + return "", "", ErrNotAuthenticated + } + + return authInfo.Token, ctx.Namespace, nil +} diff --git a/cmd/list/list.go b/cmd/list/list.go index e61e7ec..0904c9d 100644 --- a/cmd/list/list.go +++ b/cmd/list/list.go @@ -2,20 +2,16 @@ package list import ( "context" - "errors" "fmt" - "log/slog" - "net/url" "os" "strings" - "syscall" - "github.com/middlewaregruppen/tcli/pkg/client" + "github.com/middlewaregruppen/tcli/cmd/internal/auth" "github.com/spf13/cobra" "github.com/spf13/viper" - "golang.org/x/term" "k8s.io/cli-runtime/pkg/printers" - "k8s.io/client-go/tools/clientcmd" + + "github.com/middlewaregruppen/tcli/pkg/client" ) var tanzuNamespace string @@ -43,12 +39,9 @@ Examples: Use "tcli --help" for a list of global command-line options (applies to all commands). `, PreRunE: func(cmd *cobra.Command, args []string) error { - if err := viper.BindPFlags(cmd.Flags()); err != nil { - return err - } - return nil + return viper.BindPFlags(cmd.Flags()) }, - RunE: func(cmd *cobra.Command, args []string) (err error) { + RunE: func(cmd *cobra.Command, args []string) error { ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) defer cancel() @@ -58,60 +51,18 @@ Examples: insecureSkipVerify := viper.GetBool("insecure") kubeconfig := viper.GetString("kubeconfig") - u, err := url.Parse(tanzuServer) - if err != nil { - return err - } - - // Read kubeconfig from file - conf, err := clientcmd.LoadFromFile(kubeconfig) - if err != nil { - return err - } - - // Find credentials from kubeconfig context - contextName := u.Host - if _, ok := conf.Contexts[contextName]; !ok { - return errors.New("credentials missing! Please run 'tcli login' to authenticate") - } - - // AuthInfo name is whatever is set in the context. However it can be overriden with the --username flag - authName := fmt.Sprintf("wcp:%s:%s", u.Host, conf.Contexts[contextName].AuthInfo) - if len(tanzuUsername) > 0 { - authName = fmt.Sprintf("wcp:%s:%s", u.Host, tanzuUsername) - } - - // Check if the AuthInfo object exists - if _, ok := conf.AuthInfos[authName]; !ok { - return errors.New("credentials missing! Please run 'tcli login' to authenticate") - } - - token := conf.AuthInfos[authName].Token - - // Create rest client - logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) - c, err := client.New(tanzuServer, client.WithLogger(logger), client.WithCredentials(client.TokenCredentials(token)), client.WithInsecure(insecureSkipVerify)) + c, contextNamespace, err := auth.ClientFromKubeconfig(tanzuServer, kubeconfig, tanzuUsername, insecureSkipVerify) if err != nil { return err } - if _, ok := conf.Contexts[contextName]; ok && len(tanzuNamespace) == 0 { - tanzuNamespace = conf.Contexts[contextName].Namespace + // If --namespace was not given, fall back to the namespace stored in the kubeconfig context + if len(tanzuNamespace) == 0 { + tanzuNamespace = contextNamespace } - a := strings.ToLower(args[0]) - switch a { + switch strings.ToLower(args[0]) { case "namespaces", "ns": - // Read from stdin if password isn't set anywhere - if len(tanzuPassword) == 0 { - fmt.Printf("Password:") - bytePassword, err := term.ReadPassword(int(syscall.Stdin)) - if err != nil { - return err - } - tanzuPassword = string(bytePassword) - fmt.Printf("\n") - } return listNamespaces(ctx, tanzuServer, tanzuUsername, tanzuPassword, insecureSkipVerify) case "clusters", "clu", "tkc": return listClusters(ctx, c, tanzuNamespace) @@ -120,7 +71,7 @@ Examples: case "addons", "tka": return listAddons(ctx, c) default: - return fmt.Errorf("%s is not a valid resource", a) + return fmt.Errorf("%q is not a valid resource", args[0]) } }, } @@ -134,11 +85,7 @@ func listClusters(ctx context.Context, c client.Client, ns string) error { return err } printer := printers.NewTablePrinter(printers.PrintOptions{}) - err = printer.PrintObj(objs, os.Stdout) - if err != nil { - return err - } - return nil + return printer.PrintObj(objs, os.Stdout) } func listReleases(ctx context.Context, c client.Client) error { @@ -147,11 +94,7 @@ func listReleases(ctx context.Context, c client.Client) error { return err } printer := printers.NewTablePrinter(printers.PrintOptions{}) - err = printer.PrintObj(objs, os.Stdout) - if err != nil { - return err - } - return nil + return printer.PrintObj(objs, os.Stdout) } func listNamespaces(ctx context.Context, server, username, password string, insecure bool) error { @@ -176,9 +119,5 @@ func listAddons(ctx context.Context, c client.Client) error { return err } printer := printers.NewTablePrinter(printers.PrintOptions{}) - err = printer.PrintObj(objs, os.Stdout) - if err != nil { - return err - } - return nil + return printer.PrintObj(objs, os.Stdout) } diff --git a/cmd/login/login.go b/cmd/login/login.go index 99c10da..e314a6a 100644 --- a/cmd/login/login.go +++ b/cmd/login/login.go @@ -11,7 +11,6 @@ import ( "syscall" "github.com/middlewaregruppen/tcli/pkg/client" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" "golang.org/x/term" @@ -27,8 +26,7 @@ var ( func NewCmdLogin() *cobra.Command { c := &cobra.Command{ - Use: "login CLUSTER", - // Args: cobra.MaximumNArgs(1), + Use: "login [CLUSTER...]", Args: cobra.MinimumNArgs(0), Short: "Authenticate user with Tanzu namespaces and clusters", Long: `Authenticate user with Tanzu namespaces and clusters @@ -48,7 +46,7 @@ Examples: # Login to multiple tanzu clusters in one go tcli login CLUSTER1 CLUSTER2 CLUSTER3 ... - # Login to a tanzu clusters in the same namespace + # Login to tanzu clusters in the same namespace tcli login CLUSTER1 CLUSTER2 -n NAMESPACE Use "tcli --help" for a list of global command-line options (applies to all commands). @@ -58,22 +56,21 @@ Examples: return err } - // Read from stdin if password isn't set anywhere + // Prompt for password on stdin if it wasn't provided via flag or env if len(viper.GetString("password")) == 0 { fmt.Printf("Password:") bytePassword, err := term.ReadPassword(int(syscall.Stdin)) if err != nil { return err } - err = cmd.Flags().Set("password", string(bytePassword)) - if err != nil { + if err := cmd.Flags().Set("password", string(bytePassword)); err != nil { return err } fmt.Printf("\n") } return nil }, - RunE: func(cmd *cobra.Command, args []string) (err error) { + RunE: func(cmd *cobra.Command, args []string) error { ctx, cancel := context.WithTimeout(context.Background(), viper.GetDuration("timeout")) defer cancel() @@ -86,59 +83,62 @@ Examples: u, err := url.Parse(tanzuServer) if err != nil { - return err + return fmt.Errorf("parsing server URL: %w", err) } + // Supervisor server speaks the WCP API on port 443, but the + // kubeconfig cluster entry uses the k8s API on port 6443. Build + // the k8s API URL from the already-parsed host to avoid doubling + // the port if the user supplied tanzuServer with an explicit port. + supervisorK8sServer := fmt.Sprintf("https://%s:6443", u.Hostname()) + logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) - c, err := client.New(tanzuServer, client.WithLogger(logger), client.WithCredentials(client.BasicCredentials(tanzuUsername, tanzuPassword))) + c, err := client.New( + tanzuServer, + client.WithLogger(logger), + client.WithCredentials(client.BasicCredentials(tanzuUsername, tanzuPassword)), + client.WithInsecure(insecureSkipVerify), + ) if err != nil { return err } - c.(*client.RestClient).SetInsecure(insecureSkipVerify) + sess, err := c.Login(ctx, tanzuUsername, tanzuPassword) if err != nil { return err } - logrus.Debug("successfully logged in to cluster") - ns, err := c.Namespaces(ctx) if err != nil { return err } - // Define the new cluster to which we have logged in to - cluster := api.NewCluster() - cluster.InsecureSkipTLSVerify = true - cluster.Server = fmt.Sprintf("%s:6443", tanzuServer) + // Build the supervisor cluster entry for kubeconfig + supervisorCluster := api.NewCluster() + supervisorCluster.InsecureSkipTLSVerify = insecureSkipVerify + supervisorCluster.Server = supervisorK8sServer authName := fmt.Sprintf("wcp:%s:%s", u.Host, tanzuUsername) auth := api.NewAuthInfo() auth.Token = sess.SessionID - context := api.NewContext() - context.Cluster = u.Host - context.AuthInfo = authName + kubectx := api.NewContext() + kubectx.Cluster = u.Host + kubectx.AuthInfo = authName if len(ns) > 0 { - context.Namespace = ns[len(ns)-1].Namespace + kubectx.Namespace = ns[len(ns)-1].Namespace } - // Read kubeconfig from file + // Load kubeconfig once; update in memory, write once at the end conf, err := clientcmd.LoadFromFile(kubeconfig) if err != nil { - return err + return fmt.Errorf("loading kubeconfig: %w", err) } - conf.Clusters[u.Host] = cluster + conf.Clusters[u.Host] = supervisorCluster conf.AuthInfos[authName] = auth - conf.Contexts[u.Host] = context + conf.Contexts[u.Host] = kubectx conf.CurrentContext = u.Host - // Write back to kubeconfig - err = clientcmd.WriteToFile(*conf, kubeconfig) - if err != nil { - return err - } - if !silent { fmt.Printf("You have access to following %d namespaces:\n", len(ns)) for _, n := range ns { @@ -146,60 +146,57 @@ Examples: } } - // Range over args and perform login on each of them - for _, arg := range args { - tanzuCluster := arg + // Login to each requested workload cluster, updating conf in memory + for _, tanzuCluster := range args { res, err := c.LoginCluster(ctx, tanzuCluster, tanzuNamespace) if err != nil { if errors.Is(err, client.ErrClusterNotFound) { - fmt.Printf("Cluster %s does not exist", tanzuCluster) - continue + return fmt.Errorf("cluster %q not found", tanzuCluster) } return err } + caCertData, err := base64.StdEncoding.DecodeString(res.GuestClusterCa) if err != nil { - return err - } - cluster := api.NewCluster() - cluster.CertificateAuthorityData = caCertData - cluster.Server = fmt.Sprintf("https://%s:6443", res.GuestClusterServer) - authName := fmt.Sprintf("wcp:%s:%s", res.GuestClusterServer, tanzuUsername) - auth := api.NewAuthInfo() - auth.Token = res.SessionID - context := api.NewContext() - context.Cluster = res.GuestClusterServer - context.AuthInfo = authName - - conf, err := clientcmd.LoadFromFile(kubeconfig) - if err != nil { - return err + return fmt.Errorf("decoding CA cert for cluster %q: %w", tanzuCluster, err) } - // Update namespace field in context for supervisor server. - // This allows us to run commands that require the --namespace flag without having to providing the flag + wlCluster := api.NewCluster() + wlCluster.CertificateAuthorityData = caCertData + wlCluster.Server = fmt.Sprintf("https://%s:6443", res.GuestClusterServer) + + wlAuthName := fmt.Sprintf("wcp:%s:%s", res.GuestClusterServer, tanzuUsername) + wlAuth := api.NewAuthInfo() + wlAuth.Token = res.SessionID + + wlCtx := api.NewContext() + wlCtx.Cluster = res.GuestClusterServer + wlCtx.AuthInfo = wlAuthName + + // Propagate the namespace into the supervisor context so that + // subsequent commands that read --namespace from kubeconfig work + // without requiring the flag explicitly. if _, ok := conf.Contexts[u.Host]; ok { conf.Contexts[u.Host].Namespace = tanzuNamespace } - conf.Clusters[res.GuestClusterServer] = cluster - conf.AuthInfos[authName] = auth - conf.Contexts[tanzuCluster] = context + conf.Clusters[res.GuestClusterServer] = wlCluster + conf.AuthInfos[wlAuthName] = wlAuth + conf.Contexts[tanzuCluster] = wlCtx conf.CurrentContext = tanzuCluster - // Write back to kubeconfig - err = clientcmd.WriteToFile(*conf, kubeconfig) - if err != nil { - return err - } + fmt.Printf("Successfully logged into cluster %s\n", tanzuCluster) + } - fmt.Printf("Successfully logged into cluster %s", tanzuCluster) + // Single write after all in-memory updates are done + if err := clientcmd.WriteToFile(*conf, kubeconfig); err != nil { + return fmt.Errorf("writing kubeconfig: %w", err) } return nil }, } c.Flags().StringVarP(&tanzuNamespace, "namespace", "n", "", "Namespace in which the Tanzu Kubernetes cluster resides.") - c.Flags().BoolVar(&silent, "silent", false, "Silent mode - supress output") + c.Flags().BoolVar(&silent, "silent", false, "Silent mode - suppress output") return c } diff --git a/cmd/logout/logout.go b/cmd/logout/logout.go index 34c9b30..d00d705 100644 --- a/cmd/logout/logout.go +++ b/cmd/logout/logout.go @@ -1,6 +1,7 @@ package logout import ( + "fmt" "strings" "github.com/spf13/cobra" @@ -12,41 +13,49 @@ func NewCmdLogout() *cobra.Command { c := &cobra.Command{ Use: "logout", Short: "Logout user and remove credentials", - Long: "", - PreRunE: func(cmd *cobra.Command, args []string) error { - if err := viper.BindPFlags(cmd.Flags()); err != nil { - return err - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) (err error) { + Long: `Logout user and remove all WCP credentials from the kubeconfig. + +All contexts, clusters, and authinfos that were written by "tcli login" for +the current user are removed from the kubeconfig file. +Examples: + # Logout the current user + tcli -s SERVER -u USER logout + + Use "tcli --help" for a list of global command-line options (applies to all commands). + `, + RunE: func(cmd *cobra.Command, args []string) error { tanzuUsername := viper.GetString("username") kubeconfig := viper.GetString("kubeconfig") // Read kubeconfig from file conf, err := clientcmd.LoadFromFile(kubeconfig) if err != nil { - return err + return fmt.Errorf("loading kubeconfig: %w", err) } + removed := 0 for k, v := range conf.Contexts { userSplit := strings.Split(v.AuthInfo, ":") - if len(userSplit) == 3 { - if userSplit[0] == "wcp" && userSplit[2] == tanzuUsername { - delete(conf.Clusters, v.Cluster) - delete(conf.AuthInfos, v.AuthInfo) - delete(conf.Contexts, k) - } + if len(userSplit) == 3 && userSplit[0] == "wcp" && userSplit[2] == tanzuUsername { + delete(conf.Clusters, v.Cluster) + delete(conf.AuthInfos, v.AuthInfo) + delete(conf.Contexts, k) + removed++ } } + if removed == 0 { + fmt.Printf("No credentials found for user %q — nothing to remove.\n", tanzuUsername) + return nil + } + // Write back to kubeconfig - err = clientcmd.WriteToFile(*conf, kubeconfig) - if err != nil { - return err + if err := clientcmd.WriteToFile(*conf, kubeconfig); err != nil { + return fmt.Errorf("writing kubeconfig: %w", err) } + fmt.Printf("Removed %d context(s) for user %q.\n", removed, tanzuUsername) return nil }, } diff --git a/cmd/root.go b/cmd/root.go index 60269af..250837a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -50,6 +50,7 @@ func NewDefaultCommand() *cobra.Command { export TCLI_SERVER=https://supervisor.local export TCLI_USERNAME=bob export TCLI_PASSWORD=mypassword + export TCLI_INSECURE=true Use "tcli --help" for a list of global command-line options (applies to all commands). `, @@ -91,7 +92,7 @@ func NewDefaultCommand() *cobra.Command { c.PersistentFlags().StringVarP(&tanzuServer, "server", "s", "", "Address of the server to authenticate against.") c.PersistentFlags().StringVarP(&tanzuUsername, "username", "u", "", "Username to authenticate.") c.PersistentFlags().StringVarP(&tanzuPassword, "password", "p", "", "Password to use for authentication.") - c.PersistentFlags().BoolVarP(&insecureSkipVerify, "insecure", "i", true, "Skip certificate verification (this is insecure).") + c.PersistentFlags().BoolVarP(&insecureSkipVerify, "insecure", "i", false, "Skip certificate verification (this is insecure).") c.PersistentFlags().StringVar(&kubeconfig, "kubeconfig", fmt.Sprintf("%s/.kube/config", homedir), "Path to kubeconfig file.") // Setup sub-commands diff --git a/cmd/use/use.go b/cmd/use/use.go index 6e03ba2..0ed20b7 100644 --- a/cmd/use/use.go +++ b/cmd/use/use.go @@ -1,16 +1,14 @@ package use import ( + "fmt" + "github.com/spf13/cobra" "github.com/spf13/viper" "k8s.io/client-go/tools/clientcmd" ) -var ( - tanzuNamespace string -) - func NewCmdUse() *cobra.Command { c := &cobra.Command{ Use: "use NAMESPACE", @@ -23,37 +21,30 @@ Examples: Use "tcli --help" for a list of global command-line options (applies to all commands). `, - PreRunE: func(cmd *cobra.Command, args []string) error { - if err := viper.BindPFlags(cmd.Flags()); err != nil { - return err - } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) (err error) { - + RunE: func(cmd *cobra.Command, args []string) error { kubeconfig := viper.GetString("kubeconfig") + namespace := args[0] // Read kubeconfig from file conf, err := clientcmd.LoadFromFile(kubeconfig) if err != nil { - return err + return fmt.Errorf("loading kubeconfig: %w", err) } // Update namespace in current context currentCtx := conf.CurrentContext if _, ok := conf.Contexts[currentCtx]; ok { - conf.Contexts[currentCtx].Namespace = args[0] + conf.Contexts[currentCtx].Namespace = namespace } // Write back to kubeconfig - err = clientcmd.WriteToFile(*conf, kubeconfig) - if err != nil { - return err + if err := clientcmd.WriteToFile(*conf, kubeconfig); err != nil { + return fmt.Errorf("writing kubeconfig: %w", err) } + fmt.Printf("Namespace set to %q in context %q\n", namespace, currentCtx) return nil }, } - c.Flags().StringVarP(&tanzuNamespace, "namespace", "n", "", "Namespace to use") return c } diff --git a/cmd/version/color.go b/cmd/version/color.go index 473478d..7c66c35 100644 --- a/cmd/version/color.go +++ b/cmd/version/color.go @@ -1,13 +1,8 @@ +//go:build !windows + package version var ( colorReset = "\033[0m" colorGreen = "\033[32m" - // colorRed = "\033[31m" - // colorYellow = "\033[33m" - // colorBlue = "\033[34m" - // colorPurple = "\033[35m" - // colorCyan = "\033[36m" - // colorGray = "\033[37m" - // colorWhite = "\033[97m" ) diff --git a/cmd/version/color_windows.go b/cmd/version/color_windows.go index 9852d23..b34d08c 100644 --- a/cmd/version/color_windows.go +++ b/cmd/version/color_windows.go @@ -1,13 +1,10 @@ +//go:build windows + package version +// ANSI escape codes are not supported on Windows; use empty strings so that +// version output is printed without colour sequences. var ( - reset = "" - red = "" - green = "" - yellow = "" - blue = "" - purple = "" - cyan = "" - gray = "" - white = "" + colorReset = "" + colorGreen = "" ) diff --git a/cmd/version/version.go b/cmd/version/version.go index 2127228..2f663c8 100644 --- a/cmd/version/version.go +++ b/cmd/version/version.go @@ -24,11 +24,13 @@ func NewCmdVersion() *cobra.Command { Use: "version", Short: "Prints the tcli version", Example: `tcli version`, - Run: func(_ *cobra.Command, _ []string) { - fmt.Printf("Version: %s%v%s\t\n", colorGreen, VERSION, colorReset) - fmt.Printf("Built: %v\t\n", DATE) - fmt.Printf("Commit: %v\t\n", COMMIT) - fmt.Printf("Branch: %v\t\n", BRANCH) + RunE: func(_ *cobra.Command, _ []string) error { + fmt.Printf("Version: %s%v%s\n", colorGreen, VERSION, colorReset) + fmt.Printf("Built: %v\n", DATE) + fmt.Printf("Commit: %v\n", COMMIT) + fmt.Printf("Branch: %v\n", BRANCH) + fmt.Printf("Go: %v\n", GOVERSION) + return nil }, } return versionCmd diff --git a/main.go b/main.go index 3d50f1e..4e0107e 100644 --- a/main.go +++ b/main.go @@ -5,11 +5,9 @@ import ( "github.com/middlewaregruppen/tcli/cmd" "github.com/sirupsen/logrus" - "github.com/spf13/viper" ) func main() { - viper.AutomaticEnv() if err := cmd.NewDefaultCommand().Execute(); err != nil { logrus.Error(err) os.Exit(1) diff --git a/pkg/client/client_rest.go b/pkg/client/client_rest.go index bb2295b..22d667a 100644 --- a/pkg/client/client_rest.go +++ b/pkg/client/client_rest.go @@ -128,11 +128,6 @@ func WithLogger(l *slog.Logger) Option { } } -func (r *RestClient) SetInsecure(t bool) *RestClient { - http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - return r -} - func (r *RestClient) SetToken(t string) *RestClient { r.Token = t return r