github.com/someshkoli/terratest@v0.41.1/modules/k8s/config.go (about)

     1  package k8s
     2  
     3  import (
     4  	"errors"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"sort"
     9  
    10  	gwErrors "github.com/gruntwork-io/go-commons/errors"
    11  	homedir "github.com/mitchellh/go-homedir"
    12  	restclient "k8s.io/client-go/rest"
    13  	"k8s.io/client-go/tools/clientcmd"
    14  	"k8s.io/client-go/tools/clientcmd/api"
    15  
    16  	"github.com/gruntwork-io/terratest/modules/environment"
    17  	"github.com/gruntwork-io/terratest/modules/files"
    18  	"github.com/gruntwork-io/terratest/modules/logger"
    19  	"github.com/gruntwork-io/terratest/modules/testing"
    20  )
    21  
    22  // LoadConfigFromPath will load a ClientConfig object from a file path that points to a location on disk containing a
    23  // kubectl config.
    24  func LoadConfigFromPath(path string) clientcmd.ClientConfig {
    25  	config := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
    26  		&clientcmd.ClientConfigLoadingRules{ExplicitPath: path},
    27  		&clientcmd.ConfigOverrides{})
    28  	return config
    29  }
    30  
    31  // LoadApiClientConfigE will load a ClientConfig object from a file path that points to a location on disk containing a
    32  // kubectl config, with the requested context loaded.
    33  func LoadApiClientConfigE(configPath string, contextName string) (*restclient.Config, error) {
    34  	overrides := clientcmd.ConfigOverrides{}
    35  	if contextName != "" {
    36  		overrides.CurrentContext = contextName
    37  	}
    38  	config := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
    39  		&clientcmd.ClientConfigLoadingRules{ExplicitPath: configPath},
    40  		&overrides)
    41  	return config.ClientConfig()
    42  }
    43  
    44  // DeleteConfigContextE will remove the context specified at the provided name, and remove any clusters and authinfos
    45  // that are orphaned as a result of it. The config path is either specified in the environment variable KUBECONFIG or at
    46  // the user's home directory under `.kube/config`.
    47  func DeleteConfigContextE(t testing.TestingT, contextName string) error {
    48  	kubeConfigPath, err := GetKubeConfigPathE(t)
    49  	if err != nil {
    50  		return err
    51  	}
    52  
    53  	return DeleteConfigContextWithPathE(t, kubeConfigPath, contextName)
    54  }
    55  
    56  // DeleteConfigContextWithPathE will remove the context specified at the provided name, and remove any clusters and
    57  // authinfos that are orphaned as a result of it.
    58  func DeleteConfigContextWithPathE(t testing.TestingT, kubeConfigPath string, contextName string) error {
    59  	logger.Logf(t, "Removing kubectl config context %s from config at path %s", contextName, kubeConfigPath)
    60  
    61  	// Load config and get data structure representing config info
    62  	config := LoadConfigFromPath(kubeConfigPath)
    63  	rawConfig, err := config.RawConfig()
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	// Check if the context we want to delete actually exists, and if so, delete it.
    69  	_, ok := rawConfig.Contexts[contextName]
    70  	if !ok {
    71  		logger.Logf(t, "WARNING: Could not find context %s from config at path %s", contextName, kubeConfigPath)
    72  		return nil
    73  	}
    74  	delete(rawConfig.Contexts, contextName)
    75  
    76  	// If the removing context is the current context, be sure to set a new one
    77  	if contextName == rawConfig.CurrentContext {
    78  		if err := setNewContext(&rawConfig); err != nil {
    79  			return err
    80  		}
    81  	}
    82  
    83  	// Finally, clean up orphaned clusters and authinfos and then save config
    84  	RemoveOrphanedClusterAndAuthInfoConfig(&rawConfig)
    85  	if err := clientcmd.ModifyConfig(config.ConfigAccess(), rawConfig, false); err != nil {
    86  		return err
    87  	}
    88  
    89  	logger.Logf(
    90  		t,
    91  		"Removed context %s from config at path %s and any orphaned clusters and authinfos",
    92  		contextName,
    93  		kubeConfigPath)
    94  	return nil
    95  }
    96  
    97  // setNewContext will pick the alphebetically first available context from the list of contexts in the config to use as
    98  // the new current context
    99  func setNewContext(config *api.Config) error {
   100  	// Sort contextNames and pick the first one
   101  	var contextNames []string
   102  	for name := range config.Contexts {
   103  		contextNames = append(contextNames, name)
   104  	}
   105  	sort.Strings(contextNames)
   106  	if len(contextNames) > 0 {
   107  		config.CurrentContext = contextNames[0]
   108  	} else {
   109  		return errors.New("There are no available contexts remaining")
   110  	}
   111  	return nil
   112  }
   113  
   114  // RemoveOrphanedClusterAndAuthInfoConfig will remove all configurations related to clusters and users that have no
   115  // contexts associated with it
   116  func RemoveOrphanedClusterAndAuthInfoConfig(config *api.Config) {
   117  	newAuthInfos := map[string]*api.AuthInfo{}
   118  	newClusters := map[string]*api.Cluster{}
   119  	for _, context := range config.Contexts {
   120  		newClusters[context.Cluster] = config.Clusters[context.Cluster]
   121  		newAuthInfos[context.AuthInfo] = config.AuthInfos[context.AuthInfo]
   122  	}
   123  	config.AuthInfos = newAuthInfos
   124  	config.Clusters = newClusters
   125  }
   126  
   127  // GetKubeConfigPathE determines which file path to use as the kubectl config path
   128  func GetKubeConfigPathE(t testing.TestingT) (string, error) {
   129  	kubeConfigPath := environment.GetFirstNonEmptyEnvVarOrEmptyString(t, []string{"KUBECONFIG"})
   130  	if kubeConfigPath == "" {
   131  		configPath, err := KubeConfigPathFromHomeDirE()
   132  		if err != nil {
   133  			return "", err
   134  		}
   135  		kubeConfigPath = configPath
   136  	}
   137  	return kubeConfigPath, nil
   138  }
   139  
   140  // KubeConfigPathFromHomeDirE returns a string to the default Kubernetes config path in the home directory. This will
   141  // error if the home directory can not be determined.
   142  func KubeConfigPathFromHomeDirE() (string, error) {
   143  	home, err := homedir.Dir()
   144  	if err != nil {
   145  		return "", err
   146  	}
   147  	configPath := filepath.Join(home, ".kube", "config")
   148  	return configPath, err
   149  }
   150  
   151  // CopyHomeKubeConfigToTemp will copy the kubeconfig in the home directory to a temp file. This will fail the test if
   152  // there are any errors.
   153  func CopyHomeKubeConfigToTemp(t testing.TestingT) string {
   154  	path, err := CopyHomeKubeConfigToTempE(t)
   155  	if err != nil {
   156  		if path != "" {
   157  			os.Remove(path)
   158  		}
   159  		t.Fatal(err)
   160  	}
   161  	return path
   162  }
   163  
   164  // CopyHomeKubeConfigToTempE will copy the kubeconfig in the home directory to a temp file.
   165  func CopyHomeKubeConfigToTempE(t testing.TestingT) (string, error) {
   166  	configPath, err := KubeConfigPathFromHomeDirE()
   167  	if err != nil {
   168  		return "", err
   169  	}
   170  	tmpConfig, err := ioutil.TempFile("", "")
   171  	if err != nil {
   172  		return "", gwErrors.WithStackTrace(err)
   173  	}
   174  	defer tmpConfig.Close()
   175  	err = files.CopyFile(configPath, tmpConfig.Name())
   176  	return tmpConfig.Name(), err
   177  }
   178  
   179  // UpsertConfigContext will update or insert a new context to the provided config, binding the provided cluster to the
   180  // provided user.
   181  func UpsertConfigContext(config *api.Config, contextName string, clusterName string, userName string) {
   182  	config.Contexts[contextName] = &api.Context{Cluster: clusterName, AuthInfo: userName}
   183  }