Skip to content

Commit

Permalink
Fix KUBECONFIG default behaviour
Browse files Browse the repository at this point in the history
So far, the `havener` CLI did not behave the same way as `kubectl`, when it
comes to the Kubernetes configuration usage. If the `KUBECONFIG` environment
variable is set, this should always take precedence over the command line flag
or the default system location for the Kubernetes configuration.

Unify usage of `havener.NewHavener` function call.

Introduce options to `NewHavener` function to be able to override settings.

Add check for `KUBECONFIG` environment variable.
  • Loading branch information
HeavyWombat committed Jul 1, 2021
1 parent 6b6e2fe commit 534b68b
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 87 deletions.
25 changes: 8 additions & 17 deletions internal/cmd/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"time"

"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/gonvenience/bunt"
"github.com/gonvenience/wrap"
Expand Down Expand Up @@ -56,15 +55,12 @@ var eventsCmd = &cobra.Command{
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
kubeConfig := viper.GetString("kubeconfig")
switch {
case len(kubeConfig) > 0:
return retrieveClusterEvents(kubeConfig)

default:
cmd.Usage()
hvnr, err := havener.NewHavener(havener.KubeConfig(kubeConfig))
if err != nil {
return wrap.Error(err, "unable to get access to cluster")
}
return nil

return retrieveClusterEvents(hvnr)
},
}

Expand All @@ -74,13 +70,8 @@ func init() {
eventsCmd.PersistentFlags().StringVarP(&namespaceFilter, "namespace", "n", "", "Filter events for specific namespace")
}

func retrieveClusterEvents(kubeConfig string) error {
client, _, err := havener.OutOfClusterAuthentication(kubeConfig)
if err != nil {
return wrap.Error(err, "failed to access cluster")
}

namespaces, err := havener.ListNamespaces(client)
func retrieveClusterEvents(hvnr havener.Havener) error {
namespaces, err := havener.ListNamespaces(hvnr.Client())
if err != nil {
return wrap.Error(err, "failed to get a list of namespaces")
}
Expand All @@ -96,7 +87,7 @@ func retrieveClusterEvents(kubeConfig string) error {
}

go func() error {
watcher, err := client.CoreV1().Events(namespace).Watch(context.TODO(), metav1.ListOptions{})
watcher, err := hvnr.Client().CoreV1().Events(namespace).Watch(context.TODO(), metav1.ListOptions{})
if err != nil {
return wrap.Error(err, "failed to setup event watcher")
}
Expand Down
14 changes: 7 additions & 7 deletions internal/cmd/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ The download includes all deployment YAMLs of the pods and the describe output.`
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
return retrieveClusterLogs()
hvnr, err := havener.NewHavener(havener.KubeConfig(kubeConfig))
if err != nil {
return wrap.Error(err, "unable to get access to cluster")
}

return retrieveClusterLogs(hvnr)
},
}

Expand All @@ -67,12 +72,7 @@ func init() {
logsCmd.PersistentFlags().IntVar(&parallelDownloads, "parallel", 64, "number of parallel download jobs")
}

func retrieveClusterLogs() error {
hvnr, err := havener.NewHavener()
if err != nil {
return wrap.Error(err, "unable to get access to cluster")
}

func retrieveClusterLogs(hvnr havener.Havener) error {
var commonText string
if excludeConfigFiles {
commonText = "log files"
Expand Down
15 changes: 8 additions & 7 deletions internal/cmd/nexec.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,12 @@ all nodes automatically.
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
return execInClusterNodes(args)
hvnr, err := havener.NewHavener(havener.KubeConfig(kubeConfig))
if err != nil {
return wrap.Error(err, "unable to get access to cluster")
}

return execInClusterNodes(hvnr, args)
},
}

Expand All @@ -95,16 +100,12 @@ func init() {
nodeExecCmd.PersistentFlags().IntVar(&nodeExecMaxParallel, "max-parallel", 0, "number of parallel executions (defaults to number of nodes)")
}

func execInClusterNodes(args []string) error {
hvnr, err := havener.NewHavener()
if err != nil {
return wrap.Error(err, "unable to get access to cluster")
}

func execInClusterNodes(hvnr havener.Havener, args []string) error {
var (
nodes []corev1.Node
input string
command []string
err error
)

switch {
Expand Down
15 changes: 8 additions & 7 deletions internal/cmd/pexec.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,12 @@ all pods in all namespaces automatically.
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
return execInClusterPods(args)
hvnr, err := havener.NewHavener(havener.KubeConfig(kubeConfig))
if err != nil {
return wrap.Error(err, "unable to get access to cluster")
}

return execInClusterPods(hvnr, args)
},
}

Expand All @@ -87,17 +92,13 @@ func init() {
podExecCmd.PersistentFlags().BoolVar(&podExecBlock, "block", false, "show distributed shell output as block for each pod")
}

func execInClusterPods(args []string) error {
hvnr, err := havener.NewHavener()
if err != nil {
return wrap.Error(err, "unable to get access to cluster")
}

func execInClusterPods(hvnr havener.Havener, args []string) error {
var (
podMap map[*corev1.Pod][]string
countContainers int
input string
command []string
err error
)

switch {
Expand Down
12 changes: 7 additions & 5 deletions internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ import (
"fmt"
"net/url"
"os"
"path/filepath"
"runtime/debug"
"strings"

"github.com/homeport/havener/pkg/havener"
"github.com/spf13/cobra"
"github.com/spf13/viper"

Expand All @@ -38,6 +38,8 @@ import (
"github.com/gonvenience/wrap"
)

var kubeConfig string

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "havener",
Expand Down Expand Up @@ -96,15 +98,16 @@ func Execute() {
}

func init() {
home, err := os.UserHomeDir()
kubeConfigDefault, err := havener.KubeConfigDefault()
if err != nil {
panic(wrap.Error(err, "unable to get home directory"))
panic(err)
}

rootCmd.Flags().SortFlags = false
rootCmd.PersistentFlags().SortFlags = false

rootCmd.PersistentFlags().String("kubeconfig", filepath.Join(home, ".kube", "config"), "Kubernetes configuration file")
rootCmd.PersistentFlags().StringVar(&kubeConfig, "kubeconfig", kubeConfigDefault, "Kubernetes configuration file")

rootCmd.PersistentFlags().Int("terminal-width", -1, "disable autodetection and specify an explicit terminal width")
rootCmd.PersistentFlags().Int("terminal-height", -1, "disable autodetection and specify an explicit terminal height")

Expand All @@ -116,7 +119,6 @@ func init() {
rootCmd.PersistentFlags().Bool("trace", false, "trace output - level 6")

// Bind environment variables to CLI flags
viper.BindPFlag("kubeconfig", rootCmd.PersistentFlags().Lookup("kubeconfig"))
viper.BindPFlag("TERMINAL_WIDTH", rootCmd.PersistentFlags().Lookup("terminal-width"))
viper.BindPFlag("TERMINAL_HEIGHT", rootCmd.PersistentFlags().Lookup("terminal-height"))

Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/top.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ cluster as well as a list per node.
topCmdSettings.cycles = 1
}

hvnr, err := havener.NewHavener()
hvnr, err := havener.NewHavener(havener.KubeConfig(kubeConfig))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ var watchCmd = &cobra.Command{
Short: "Watch status of all pods in all namespaces",
Long: `Continuesly creates a list of all pods in all namespaces.`,
RunE: func(cmd *cobra.Command, args []string) error {
hvnr, err := havener.NewHavener()
hvnr, err := havener.NewHavener(havener.KubeConfig(kubeConfig))
if err != nil {
return err
}
Expand Down
40 changes: 15 additions & 25 deletions pkg/havener/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,44 +21,42 @@
package havener

import (
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"

_ "k8s.io/client-go/plugin/pkg/client/auth/oidc" //from https://github.com/kubernetes/client-go/issues/345

"github.com/spf13/viper"
"github.com/gonvenience/wrap"
"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)

var kubeconfig *string

func getKubeConfig() string {
if kubeconfig == nil {
if home := HomeDir(); home != "" {
kubeconfig = flag.String("kubeconfig", viper.GetString("kubeconfig"), "(optional) absolute path to the kubeconfig file")
}
flag.Parse()
// KubeConfigDefault returns assumed default locaation of the Kubernetes
// configuration, which is expected to be `$HOME/.kube/config`.
func KubeConfigDefault() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", wrap.Error(err, "unable to get home directory")
}

return *kubeconfig
return filepath.Join(home, ".kube", "config"), nil
}

//OutOfClusterAuthentication for kube authentication from the outside
func OutOfClusterAuthentication(kubeConfig string) (*kubernetes.Clientset, *rest.Config, error) {
logf(Verbose, "Connecting to Kubernetes cluster...")

// outOfClusterAuthentication for kube authentication from the outside
func outOfClusterAuthentication(kubeConfig string) (*kubernetes.Clientset, *rest.Config, error) {
if kubeConfig == "" {
kubeConfig = getKubeConfig()
return nil, nil, fmt.Errorf("no kube config supplied")
}

logf(Verbose, "Connecting to Kubernetes cluster...")

// BuildConfigFromFlags is a helper function that builds configs from a master
// url or a kubeconfig filepath.
config, err := clientcmd.BuildConfigFromFlags("", kubeConfig)
Expand All @@ -73,14 +71,6 @@ func OutOfClusterAuthentication(kubeConfig string) (*kubernetes.Clientset, *rest
return clientset, config, err
}

// HomeDir returns the HOME env key
func HomeDir() string {
if h := os.Getenv("HOME"); h != "" {
return h
}
return os.Getenv("USERPROFILE") // windows
}

func isSystemNamespace(namespace string) bool {
switch namespace {
case "default", "kube-system", "ibm-system":
Expand Down
47 changes: 30 additions & 17 deletions pkg/havener/havener.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ package havener
import (
"fmt"
"io"
"os"
"time"

"github.com/gonvenience/wrap"
Expand Down Expand Up @@ -98,6 +99,16 @@ type Havener interface {
NodeExec(node corev1.Node, containerImage string, timeoutSeconds int, command []string, stdin io.Reader, stdout io.Writer, stderr io.Writer, tty bool) error
}

// Option provides a way to set specific settings for creating the Havener setup
type Option func(*Hvnr)

// KubeConfig is an option to set a specific Kubernetes configuration path
func KubeConfig(kubeConfig string) Option {
return func(h *Hvnr) {
h.kubeConfigPath = kubeConfig
}
}

// NewHavenerFromFields returns a new Havener handle using the provided
// input arguments (use for unit testing only)
func NewHavenerFromFields(client kubernetes.Interface, restconfig *rest.Config, clusterName string, kubeConfigPath string) *Hvnr {
Expand All @@ -110,35 +121,37 @@ func NewHavenerFromFields(client kubernetes.Interface, restconfig *rest.Config,
}

// NewHavener returns a new Havener handle to perform cluster actions
func NewHavener(kubeConfigs ...string) (*Hvnr, error) {
var kubeConfigPath string
switch len(kubeConfigs) {
case 0:
kubeConfigPath = getKubeConfig()
func NewHavener(opts ...Option) (hvnr *Hvnr, err error) {
hvnr = &Hvnr{}
for _, opt := range opts {
opt(hvnr)
}

case 1:
kubeConfigPath = kubeConfigs[0]
// In case `KUBECONFIG` environment variable is set, this will take
// precedence over command line flag or default value
if value, ok := os.LookupEnv("KUBECONFIG"); ok {
hvnr.kubeConfigPath = value
}

default:
return nil, fmt.Errorf("multiple Kubernetes configurations are currently not supported")
// In case there is no Kubernetes configuration set, use the default
if hvnr.kubeConfigPath == "" {
hvnr.kubeConfigPath, err = KubeConfigDefault()
if err != nil {
return nil, wrap.Error(err, "failed to look-up default kube config")
}
}

client, restconfig, err := OutOfClusterAuthentication(kubeConfigPath)
hvnr.client, hvnr.restconfig, err = outOfClusterAuthentication(hvnr.kubeConfigPath)
if err != nil {
return nil, wrap.Error(err, "unable to get access to cluster")
}

clusterName, err := clusterName(kubeConfigPath)
hvnr.clusterName, err = clusterName(hvnr.kubeConfigPath)
if err != nil {
return nil, wrap.Error(err, "unable to get cluster name")
}

return &Hvnr{
client: client,
restconfig: restconfig,
clusterName: clusterName,
kubeConfigPath: kubeConfigPath,
}, nil
return hvnr, nil
}

// ClusterName returns the name of the currently configured cluster
Expand Down

0 comments on commit 534b68b

Please sign in to comment.