Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 11 additions & 52 deletions cmd/inspect/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()

Expand All @@ -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)
Expand All @@ -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
},
Expand Down
77 changes: 77 additions & 0 deletions cmd/internal/auth/auth.go
Original file line number Diff line number Diff line change
@@ -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
}
89 changes: 14 additions & 75 deletions cmd/list/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()

Expand All @@ -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)
Expand All @@ -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])
}
},
}
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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)
}
Loading