Refactor management of config file paths

This change replaces the usage of the clientcmd's PathOptions with a
simpler filepath approach. The handling of the kube config file
associated with airshipctl is now entirely managed by airshipctl.

This also introduces the environment variable AIRSHIP_KUBECONFIG, which
can be used to override the default location for airship's kube config.
It can in turn be overridden by the command line argument.

Prior to this change, the kube config object was created by creating a
kubernetes client, then stripping off that client's config object. As a
side effect, this change removes the middleman, and creates the kube
config object directly from file.

Change-Id: I99ba88d50a0f45c40597a58fe4b3fdfeb7d1467d
This commit is contained in:
Ian Howell 2020-02-05 17:21:18 -06:00
parent c7c1011a5c
commit 32ef58435d
7 changed files with 237 additions and 157 deletions

View File

@ -29,21 +29,20 @@ import (
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
kubeconfig "k8s.io/client-go/tools/clientcmd/api"
"opendev.org/airship/airshipctl/pkg/util" "opendev.org/airship/airshipctl/pkg/util"
) )
// Called from root to Load the initial configuration // LoadConfig populates the Config object using the files found at
func (c *Config) LoadConfig(configFileArg string, kPathOptions *clientcmd.PathOptions) error { // airshipConfigPath and kubeConfigPath
err := c.loadFromAirConfig(configFileArg) func (c *Config) LoadConfig(airshipConfigPath, kubeConfigPath string) error {
err := c.loadFromAirConfig(airshipConfigPath)
if err != nil { if err != nil {
return err return err
} }
// Load or initialize the kubeconfig object from a file err = c.loadKubeConfig(kubeConfigPath)
err = c.loadKubeConfig(kPathOptions)
if err != nil { if err != nil {
return err return err
} }
@ -52,36 +51,45 @@ func (c *Config) LoadConfig(configFileArg string, kPathOptions *clientcmd.PathOp
return c.reconcileConfig() return c.reconcileConfig()
} }
func (c *Config) loadFromAirConfig(configFileArg string) error { // loadFromAirConfig populates the Config from the file found at airshipConfigPath.
// If it exists, Read the ConfigFile data // If there is no file at airshipConfigPath, this function does nothing.
// Only care about the errors here, because there is a file // An error is returned if:
// And essentially I cannot use its data. // * airshipConfigPath is the empty string
// airshipctl probable should stop // * the file at airshipConfigPath is inaccessible
if configFileArg == "" { // * the file at airshipConfigPath cannot be marshaled into Config
func (c *Config) loadFromAirConfig(airshipConfigPath string) error {
if airshipConfigPath == "" {
return errors.New("Configuration file location was not provided.") return errors.New("Configuration file location was not provided.")
} }
// Remember where I loaded the Config from // Remember where I loaded the Config from
c.loadedConfigPath = configFileArg c.loadedConfigPath = airshipConfigPath
// If I have a file to read, load from it
if _, err := os.Stat(configFileArg); os.IsNotExist(err) { // If I can read from the file, load from it
if _, err := os.Stat(airshipConfigPath); os.IsNotExist(err) {
return nil return nil
} } else if err != nil {
return util.ReadYAMLFile(configFileArg, c)
}
func (c *Config) loadKubeConfig(kPathOptions *clientcmd.PathOptions) error {
// Will need this for Persisting the changes
c.loadedPathOptions = kPathOptions
// Now at this point what I load might not reflect the associated kubeconfig yet
kConfig, err := kPathOptions.GetStartingConfig()
if err != nil {
return err return err
} }
// Store the kubeconfig object into an airship managed kubeconfig object
c.kubeConfig = kConfig
return nil return util.ReadYAMLFile(airshipConfigPath, c)
}
func (c *Config) loadKubeConfig(kubeConfigPath string) error {
// Will need this for persisting the changes
c.kubeConfigPath = kubeConfigPath
// If I can read from the file, load from it
var err error
if _, err = os.Stat(kubeConfigPath); os.IsNotExist(err) {
c.kubeConfig = clientcmdapi.NewConfig()
return nil
} else if err != nil {
return err
}
c.kubeConfig, err = clientcmd.LoadFromFile(kubeConfigPath)
return err
} }
// reconcileConfig serves two functions: // reconcileConfig serves two functions:
@ -198,6 +206,7 @@ func (c *Config) rmConfigClusterStragglers(persistIt bool) bool {
} }
return rccs return rccs
} }
func (c *Config) reconcileContexts(updatedClusterNames map[string]string) { func (c *Config) reconcileContexts(updatedClusterNames map[string]string) {
for key, context := range c.kubeConfig.Contexts { for key, context := range c.kubeConfig.Contexts {
// Check if the Cluster name referred to by the context // Check if the Cluster name referred to by the context
@ -254,7 +263,7 @@ func (c *Config) reconcileAuthInfos() {
func (c *Config) reconcileCurrentContext() { func (c *Config) reconcileCurrentContext() {
// If the Airship current context is different that the current context in the kubeconfig // If the Airship current context is different that the current context in the kubeconfig
// then // then
// - if the airship current context is valid, then updated kubeconfiug CC // - if the airship current context is valid, then updated kubeconfig CC
// - if the airship currentcontext is invalid, and the kubeconfig CC is valid, then create the reference // - if the airship currentcontext is invalid, and the kubeconfig CC is valid, then create the reference
// - otherwise , they are both empty. Make sure // - otherwise , they are both empty. Make sure
@ -326,20 +335,17 @@ func (c *Config) EnsureComplete() error {
return nil return nil
} }
// This function is called to update the configuration in the file defined by the // PersistConfig updates the airshipctl config and kubeconfig files to match
// ConfigFile name // the current Config and KubeConfig objects.
// It will completely overwrite the existing file, // If either file did not previously exist, the file will be created.
// If the file specified by ConfigFile exists ts updates with the contents of the Config object // Otherwise, the file will be overwritten
// If the file specified by ConfigFile does not exist it will create a new file.
func (c *Config) PersistConfig() error { func (c *Config) PersistConfig() error {
// Dont care if the file exists or not, will create if needed airshipConfigYaml, err := c.ToYaml()
// We are 100% overwriting the existing file
configyaml, err := c.ToYaml()
if err != nil { if err != nil {
return err return err
} }
// WriteFile doesn't create the directory , create it if needed // WriteFile doesn't create the directory, create it if needed
configDir := filepath.Dir(c.loadedConfigPath) configDir := filepath.Dir(c.loadedConfigPath)
err = os.MkdirAll(configDir, 0755) err = os.MkdirAll(configDir, 0755)
if err != nil { if err != nil {
@ -347,19 +353,13 @@ func (c *Config) PersistConfig() error {
} }
// Write the Airship Config file // Write the Airship Config file
err = ioutil.WriteFile(c.loadedConfigPath, configyaml, 0644) err = ioutil.WriteFile(c.loadedConfigPath, airshipConfigYaml, 0644)
if err != nil { if err != nil {
return err return err
} }
// FIXME(howell): if this fails, then the results from the previous
// actions will persist, meaning that we might have overwritten our
// airshipconfig without updating our kubeconfig. A possible solution
// is to generate brand new config files and then write them at the
// end. That could still fail, but should be more robust
// Persist the kubeconfig file referenced // Persist the kubeconfig file referenced
if err := clientcmd.ModifyConfig(c.loadedPathOptions, *c.kubeConfig, true); err != nil { if err := clientcmd.WriteToFile(*c.kubeConfig, c.kubeConfigPath); err != nil {
return err return err
} }
@ -386,14 +386,15 @@ func (c *Config) SetLoadedConfigPath(lcp string) {
c.loadedConfigPath = lcp c.loadedConfigPath = lcp
} }
func (c *Config) LoadedPathOptions() *clientcmd.PathOptions { func (c *Config) KubeConfigPath() string {
return c.loadedPathOptions return c.kubeConfigPath
}
func (c *Config) SetLoadedPathOptions(po *clientcmd.PathOptions) {
c.loadedPathOptions = po
} }
func (c *Config) KubeConfig() *kubeconfig.Config { func (c *Config) SetKubeConfigPath(kubeConfigPath string) {
c.kubeConfigPath = kubeConfigPath
}
func (c *Config) KubeConfig() *clientcmdapi.Config {
return c.kubeConfig return c.kubeConfig
} }
@ -441,7 +442,7 @@ func (c *Config) AddCluster(theCluster *ClusterOptions) (*Cluster, error) {
nCluster := NewCluster() nCluster := NewCluster()
c.Clusters[theCluster.Name].ClusterTypes[theCluster.ClusterType] = nCluster c.Clusters[theCluster.Name].ClusterTypes[theCluster.ClusterType] = nCluster
// Create a new Kubeconfig Cluster object as well // Create a new Kubeconfig Cluster object as well
kcluster := kubeconfig.NewCluster() kcluster := clientcmdapi.NewCluster()
clusterName := NewClusterComplexName() clusterName := NewClusterComplexName()
clusterName.WithType(theCluster.Name, theCluster.ClusterType) clusterName.WithType(theCluster.Name, theCluster.ClusterType)
nCluster.NameInKubeconf = clusterName.Name() nCluster.NameInKubeconf = clusterName.Name()
@ -541,7 +542,7 @@ func (c *Config) AddContext(theContext *ContextOptions) *Context {
nContext := NewContext() nContext := NewContext()
c.Contexts[theContext.Name] = nContext c.Contexts[theContext.Name] = nContext
// Create a new Kubeconfig Context object as well // Create a new Kubeconfig Context object as well
kContext := kubeconfig.NewContext() kContext := clientcmdapi.NewContext()
nContext.NameInKubeconf = theContext.Name nContext.NameInKubeconf = theContext.Name
contextName := NewClusterComplexName() contextName := NewClusterComplexName()
contextName.WithType(theContext.Name, theContext.ClusterType) contextName.WithType(theContext.Name, theContext.ClusterType)
@ -645,7 +646,7 @@ func (c *Config) AddAuthInfo(theAuthInfo *AuthInfoOptions) *AuthInfo {
nAuthInfo := NewAuthInfo() nAuthInfo := NewAuthInfo()
c.AuthInfos[theAuthInfo.Name] = nAuthInfo c.AuthInfos[theAuthInfo.Name] = nAuthInfo
// Create a new Kubeconfig AuthInfo object as well // Create a new Kubeconfig AuthInfo object as well
kAuthInfo := kubeconfig.NewAuthInfo() kAuthInfo := clientcmdapi.NewAuthInfo()
nAuthInfo.SetKubeAuthInfo(kAuthInfo) nAuthInfo.SetKubeAuthInfo(kAuthInfo)
c.KubeConfig().AuthInfos[theAuthInfo.Name] = kAuthInfo c.KubeConfig().AuthInfos[theAuthInfo.Name] = kAuthInfo
@ -739,10 +740,10 @@ func (c *Cluster) PrettyString() string {
clusterName.ClusterName(), clusterName.ClusterType(), c) clusterName.ClusterName(), clusterName.ClusterType(), c)
} }
func (c *Cluster) KubeCluster() *kubeconfig.Cluster { func (c *Cluster) KubeCluster() *clientcmdapi.Cluster {
return c.kCluster return c.kCluster
} }
func (c *Cluster) SetKubeCluster(kc *kubeconfig.Cluster) { func (c *Cluster) SetKubeCluster(kc *clientcmdapi.Cluster) {
c.kCluster = kc c.kCluster = kc
} }
@ -777,11 +778,11 @@ func (c *Context) PrettyString() string {
clusterName.ClusterName(), c.String()) clusterName.ClusterName(), c.String())
} }
func (c *Context) KubeContext() *kubeconfig.Context { func (c *Context) KubeContext() *clientcmdapi.Context {
return c.kContext return c.kContext
} }
func (c *Context) SetKubeContext(kc *kubeconfig.Context) { func (c *Context) SetKubeContext(kc *clientcmdapi.Context) {
c.kContext = kc c.kContext = kc
} }
@ -808,10 +809,10 @@ func (c *AuthInfo) String() string {
return string(kyaml) return string(kyaml)
} }
func (c *AuthInfo) KubeAuthInfo() *kubeconfig.AuthInfo { func (c *AuthInfo) KubeAuthInfo() *clientcmdapi.AuthInfo {
return c.kAuthInfo return c.kAuthInfo
} }
func (c *AuthInfo) SetKubeAuthInfo(kc *kubeconfig.AuthInfo) { func (c *AuthInfo) SetKubeAuthInfo(kc *clientcmdapi.AuthInfo) {
c.kAuthInfo = kc c.kAuthInfo = kc
} }
@ -979,7 +980,7 @@ PLACEHOLDER UNTIL I IDENTIFY if CLIENTADM
HAS SOMETHING LIKE THIS HAS SOMETHING LIKE THIS
*/ */
func KClusterString(kCluster *kubeconfig.Cluster) string { func KClusterString(kCluster *clientcmdapi.Cluster) string {
yamlData, err := yaml.Marshal(&kCluster) yamlData, err := yaml.Marshal(&kCluster)
if err != nil { if err != nil {
return "" return ""
@ -987,7 +988,8 @@ func KClusterString(kCluster *kubeconfig.Cluster) string {
return string(yamlData) return string(yamlData)
} }
func KContextString(kContext *kubeconfig.Context) string {
func KContextString(kContext *clientcmdapi.Context) string {
yamlData, err := yaml.Marshal(&kContext) yamlData, err := yaml.Marshal(&kContext)
if err != nil { if err != nil {
return "" return ""
@ -995,7 +997,8 @@ func KContextString(kContext *kubeconfig.Context) string {
return string(yamlData) return string(yamlData)
} }
func KAuthInfoString(kAuthInfo *kubeconfig.AuthInfo) string {
func KAuthInfoString(kAuthInfo *clientcmdapi.AuthInfo) string {
yamlData, err := yaml.Marshal(&kAuthInfo) yamlData, err := yaml.Marshal(&kAuthInfo)
if err != nil { if err != nil {
return "" return ""

View File

@ -26,7 +26,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"k8s.io/client-go/tools/clientcmd"
kubeconfig "k8s.io/client-go/tools/clientcmd/api" kubeconfig "k8s.io/client-go/tools/clientcmd/api"
"opendev.org/airship/airshipctl/testutil" "opendev.org/airship/airshipctl/testutil"
@ -224,10 +223,11 @@ func TestPersistConfig(t *testing.T) {
config := InitConfig(t) config := InitConfig(t)
err := config.PersistConfig() err := config.PersistConfig()
assert.NoErrorf(t, err, "Unable to persist configuration expected at %v", config.LoadedConfigPath()) require.NoError(t, err)
kpo := config.LoadedPathOptions() // Check that the files were created
assert.NotNil(t, kpo) assert.FileExists(t, config.LoadedConfigPath())
assert.FileExists(t, config.KubeConfigPath())
} }
func TestEnsureComplete(t *testing.T) { func TestEnsureComplete(t *testing.T) {
@ -347,11 +347,10 @@ func TestPurge(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
airConfigFile := filepath.Join(tempDir, AirshipConfig) airConfigFile := filepath.Join(tempDir, AirshipConfig)
kConfigFile := filepath.Join(tempDir, AirshipKubeConfig)
config.SetLoadedConfigPath(airConfigFile) config.SetLoadedConfigPath(airConfigFile)
kubePathOptions := clientcmd.NewDefaultPathOptions()
kubePathOptions.GlobalFile = kConfigFile kConfigFile := filepath.Join(tempDir, AirshipKubeConfig)
config.SetLoadedPathOptions(kubePathOptions) config.kubeConfigPath = kConfigFile
// Store it // Store it
err = config.PersistConfig() err = config.PersistConfig()

View File

@ -11,19 +11,22 @@ const (
AirshipClusterDefaultType = Target AirshipClusterDefaultType = Target
) )
//Sorted // Sorted
var AllClusterTypes = [2]string{Ephemeral, Target} var AllClusterTypes = [2]string{Ephemeral, Target}
// Constants defining default values // Constants defining default values
const ( const (
AirshipConfigEnv = "airshipconf"
AirshipConfig = "config"
AirshipConfigDir = ".airship"
AirshipConfigKind = "Config"
AirshipConfigVersion = "v1alpha1"
AirshipConfigGroup = "airshipit.org" AirshipConfigGroup = "airshipit.org"
AirshipConfigVersion = "v1alpha1"
AirshipConfigApiVersion = AirshipConfigGroup + "/" + AirshipConfigVersion AirshipConfigApiVersion = AirshipConfigGroup + "/" + AirshipConfigVersion
AirshipKubeConfig = "kubeconfig" AirshipConfigKind = "Config"
AirshipConfigDir = ".airship"
AirshipConfig = "config"
AirshipKubeConfig = "kubeconfig"
AirshipConfigEnv = "AIRSHIPCONFIG"
AirshipKubeConfigEnv = "AIRSHIP_KUBECONFIG"
) )
// Constants defining CLI flags // Constants defining CLI flags
@ -37,7 +40,7 @@ const (
FlagClusterType = "cluster-type" FlagClusterType = "cluster-type"
FlagContext = "context" FlagContext = "context"
FlagCurrentContext = "current-context" FlagCurrentContext = "current-context"
FlagConfigFilePath = AirshipConfigEnv FlagConfigFilePath = "airshipconf"
FlagEmbedCerts = "embed-certs" FlagEmbedCerts = "embed-certs"
FlagImpersonate = "as" FlagImpersonate = "as"
FlagImpersonateGroup = "as-group" FlagImpersonateGroup = "as-group"

View File

@ -22,7 +22,6 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"k8s.io/client-go/tools/clientcmd"
kubeconfig "k8s.io/client-go/tools/clientcmd/api" kubeconfig "k8s.io/client-go/tools/clientcmd/api"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -145,9 +144,7 @@ func InitConfig(t *testing.T) *Config {
conf := NewConfig() conf := NewConfig()
kubePathOptions := clientcmd.NewDefaultPathOptions() err = conf.LoadConfig(configPath, kubeConfigPath)
kubePathOptions.GlobalFile = kubeConfigPath
err = conf.LoadConfig(configPath, kubePathOptions)
require.NoError(t, err) require.NoError(t, err)
return conf return conf

View File

@ -19,7 +19,6 @@ package config
import ( import (
"net/url" "net/url"
"k8s.io/client-go/tools/clientcmd"
kubeconfig "k8s.io/client-go/tools/clientcmd/api" kubeconfig "k8s.io/client-go/tools/clientcmd/api"
) )
@ -57,15 +56,15 @@ type Config struct {
// Such as Bootstrap, Workflows, Document, etc // Such as Bootstrap, Workflows, Document, etc
ModulesConfig *Modules `json:"modules-config"` ModulesConfig *Modules `json:"modules-config"`
// Private LoadedConfigPath is the full path to the the location of the config file // loadedConfigPath is the full path to the the location of the config
// from which these config was loaded // file from which this config was loaded
// +not persisted in file // +not persisted in file
loadedConfigPath string loadedConfigPath string
// Private loadedPathOptions is the full path to the the location of the kubeconfig file // kubeConfigPath is the full path to the the location of the
// associated with this airship config instance // kubeconfig file associated with this airship config instance
// +not persisted in file // +not persisted in file
loadedPathOptions *clientcmd.PathOptions kubeConfigPath string
// Private instance of Kube Config content as an object // Private instance of Kube Config content as an object
kubeConfig *kubeconfig.Config kubeConfig *kubeconfig.Config

View File

@ -3,7 +3,6 @@ package environment
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -64,67 +63,64 @@ func (a *AirshipCTLSettings) SetKubeConfigPath(kcp string) {
func (a *AirshipCTLSettings) InitConfig() { func (a *AirshipCTLSettings) InitConfig() {
a.SetConfig(config.NewConfig()) a.SetConfig(config.NewConfig())
a.setAirshipConfigPath() a.initAirshipConfigPath()
a.initKubeConfigPath()
//Pass the airshipConfigPath and kubeConfig object err := a.Config().LoadConfig(a.AirshipConfigPath(), a.KubeConfigPath())
err := a.Config().LoadConfig(a.AirshipConfigPath(), a.setKubePathOptions())
if err != nil { if err != nil {
// Should stop airshipctl // Should stop airshipctl
log.Fatal(err) log.Fatal(err)
} }
} }
func (a *AirshipCTLSettings) setAirshipConfigPath() { func (a *AirshipCTLSettings) initAirshipConfigPath() {
// (1) If the airshipConfigPath was received as an argument its already set // The airshipConfigPath may already have been received as a command line argument
if a.airshipConfigPath == "" { if a.airshipConfigPath != "" {
// (2) If not , we can check if we got the Path via ENVIRONMENT variable, return
// set the appropriate fields
a.setAirshipConfigPathFromEnv()
} }
// (3) Check if the a.airshipConfigPath is empty still at this point , use the defaults
acp, home := a.replaceHomePlaceholder(a.airshipConfigPath)
a.airshipConfigPath = acp
if a.airshipConfigPath == "" {
a.airshipConfigPath = filepath.Join(home, config.AirshipConfigDir, config.AirshipConfig)
}
}
// setAirshipConfigPathFromEnv Get AIRSHIP CONFIG from an environment variable if set // Otherwise, we can check if we got the path via ENVIRONMENT variable
func (a *AirshipCTLSettings) setAirshipConfigPathFromEnv() {
// Check if AIRSHIPCONF env variable was set
// I have the path and name for the airship config file
a.airshipConfigPath = os.Getenv(config.AirshipConfigEnv) a.airshipConfigPath = os.Getenv(config.AirshipConfigEnv)
} if a.airshipConfigPath != "" {
return
func (a *AirshipCTLSettings) setKubePathOptions() *clientcmd.PathOptions {
// USe default expectations for Kubeconfig
kubePathOptions := clientcmd.NewDefaultPathOptions()
// No need to check the Environment , since we are relying on the kubeconfig defaults
// If we did not get an explicit kubeconfig definition on airshipctl
// as far as airshipctl is concerned will use the default expectations for the kubeconfig
// file location . This avoids messing up someones kubeconfig if they didnt explicitly want
// airshipctl to use it.
kcp, home := a.replaceHomePlaceholder(a.kubeConfigPath)
a.kubeConfigPath = kcp
if a.kubeConfigPath == "" {
a.kubeConfigPath = filepath.Join(home, config.AirshipConfigDir, config.AirshipKubeConfig)
} }
// We will always rely on tha airshipctl cli args or default for where to find kubeconfig
kubePathOptions.GlobalFile = a.kubeConfigPath
kubePathOptions.GlobalFileSubpath = a.kubeConfigPath
return kubePathOptions // Otherwise, we'll try putting it in the home directory
homeDir := userHomeDir()
a.airshipConfigPath = filepath.Join(homeDir, config.AirshipConfigDir, config.AirshipConfig)
} }
func (a *AirshipCTLSettings) replaceHomePlaceholder(configPath string) (string, string) { func (a *AirshipCTLSettings) initKubeConfigPath() {
home, err := os.UserHomeDir() // NOTE(howell): This function will set the kubeConfigPath to the
// default location under the airship directory unless the user
// *explicitly* specifies a different location, either by setting the
// ENVIRONMENT variable or by passing a command line argument.
// This avoids messing up the user's kubeconfig if they didn't
// explicitly want airshipctl to use it.
// The kubeConfigPath may already have been received as a command line argument
if a.kubeConfigPath != "" {
return
}
// Otherwise, we can check if we got the path via ENVIRONMENT variable
a.kubeConfigPath = os.Getenv(config.AirshipKubeConfigEnv)
if a.kubeConfigPath != "" {
return
}
// Otherwise, we'll try putting it in the home directory
homeDir := userHomeDir()
a.kubeConfigPath = filepath.Join(homeDir, config.AirshipConfigDir, config.AirshipKubeConfig)
}
// userHomeDir is a utility function that wraps os.UserHomeDir and returns no
// errors. If the user has no home directory, the returned value will be the
// empty string
func userHomeDir() string {
homeDir, err := os.UserHomeDir()
if err != nil { if err != nil {
// Use defaults under current directory homeDir = ""
home = ""
} }
if configPath == "" { return homeDir
return configPath, home
}
return strings.Replace(configPath, HomePlaceholder, home, 1), home
} }

View File

@ -17,6 +17,7 @@ limitations under the License.
package environment package environment
import ( import (
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -36,25 +37,107 @@ func TestInitFlags(t *testing.T) {
assert.True(t, testCmd.HasPersistentFlags()) assert.True(t, testCmd.HasPersistentFlags())
} }
func TestSpecifyAirConfigFromEnv(t *testing.T) { func TestInitConfig(t *testing.T) {
fakeConfig := "FakeConfigPath" t.Run("DefaultToHomeDirectory", func(subTest *testing.T) {
err := os.Setenv(config.AirshipConfigEnv, fakeConfig) // Set up a fake $HOME directory
testDir := makeTestDir(t)
defer deleteTestDir(t, testDir)
defer setHome(testDir)()
var testSettings AirshipCTLSettings
expectedAirshipConfig := filepath.Join(testDir, config.AirshipConfigDir, config.AirshipConfig)
expectedKubeConfig := filepath.Join(testDir, config.AirshipConfigDir, config.AirshipKubeConfig)
testSettings.InitConfig()
assert.Equal(t, expectedAirshipConfig, testSettings.AirshipConfigPath())
assert.Equal(t, expectedKubeConfig, testSettings.KubeConfigPath())
})
t.Run("PreferEnvToDefault", func(subTest *testing.T) {
// Set up a fake $HOME directory
testDir := makeTestDir(t)
defer deleteTestDir(t, testDir)
defer setHome(testDir)()
var testSettings AirshipCTLSettings
expectedAirshipConfig := filepath.Join(testDir, "airshipEnv")
expectedKubeConfig := filepath.Join(testDir, "kubeEnv")
os.Setenv(config.AirshipConfigEnv, expectedAirshipConfig)
os.Setenv(config.AirshipKubeConfigEnv, expectedKubeConfig)
defer os.Unsetenv(config.AirshipConfigEnv)
defer os.Unsetenv(config.AirshipKubeConfigEnv)
testSettings.InitConfig()
assert.Equal(t, expectedAirshipConfig, testSettings.AirshipConfigPath())
assert.Equal(t, expectedKubeConfig, testSettings.KubeConfigPath())
})
t.Run("PreferCmdLineArgToDefault", func(subTest *testing.T) {
// Set up a fake $HOME directory
testDir := makeTestDir(t)
defer deleteTestDir(t, testDir)
defer setHome(testDir)()
var testSettings AirshipCTLSettings
expectedAirshipConfig := filepath.Join(testDir, "airshipCmdLine")
expectedKubeConfig := filepath.Join(testDir, "kubeCmdLine")
testSettings.SetAirshipConfigPath(expectedAirshipConfig)
testSettings.SetKubeConfigPath(expectedKubeConfig)
// InitConfig should not change any values
testSettings.InitConfig()
assert.Equal(t, expectedAirshipConfig, testSettings.AirshipConfigPath())
assert.Equal(t, expectedKubeConfig, testSettings.KubeConfigPath())
})
t.Run("PreferCmdLineArgToEnv", func(subTest *testing.T) {
// Set up a fake $HOME directory
testDir := makeTestDir(t)
defer deleteTestDir(t, testDir)
defer setHome(testDir)()
var testSettings AirshipCTLSettings
expectedAirshipConfig := filepath.Join(testDir, "airshipCmdLine")
expectedKubeConfig := filepath.Join(testDir, "kubeCmdLine")
// set up "decoy" environment variables. These should be
// ignored, since we're simulating passing in command line
// arguments
wrongAirshipConfig := filepath.Join(testDir, "wrongAirshipConfig")
wrongKubeConfig := filepath.Join(testDir, "wrongKubeConfig")
os.Setenv(config.AirshipConfigEnv, wrongAirshipConfig)
os.Setenv(config.AirshipKubeConfigEnv, wrongKubeConfig)
defer os.Unsetenv(config.AirshipConfigEnv)
defer os.Unsetenv(config.AirshipKubeConfigEnv)
testSettings.SetAirshipConfigPath(expectedAirshipConfig)
testSettings.SetKubeConfigPath(expectedKubeConfig)
testSettings.InitConfig()
assert.Equal(t, expectedAirshipConfig, testSettings.AirshipConfigPath())
assert.Equal(t, expectedKubeConfig, testSettings.KubeConfigPath())
})
}
func makeTestDir(t *testing.T) string {
testDir, err := ioutil.TempDir("", "airship-test")
require.NoError(t, err) require.NoError(t, err)
return testDir
settings := &AirshipCTLSettings{}
settings.InitConfig()
assert.EqualValues(t, fakeConfig, settings.AirshipConfigPath())
} }
func TestGetSetPaths(t *testing.T) { func deleteTestDir(t *testing.T, path string) {
settings := &AirshipCTLSettings{} err := os.Remove(path)
settings.InitConfig() require.NoError(t, err)
airConfigFile := filepath.Join(config.AirshipConfigDir, config.AirshipConfig) }
kConfigFile := filepath.Join(config.AirshipConfigDir, config.AirshipKubeConfig)
settings.SetAirshipConfigPath(airConfigFile) // setHome sets the HOME environment variable to `path`, and returns a function
assert.EqualValues(t, airConfigFile, settings.AirshipConfigPath()) // that can be used to reset HOME to its original value
func setHome(path string) (resetHome func()) {
settings.SetKubeConfigPath(kConfigFile) oldHome := os.Getenv("HOME")
assert.EqualValues(t, kConfigFile, settings.KubeConfigPath()) os.Setenv("HOME", path)
return func() {
os.Setenv("HOME", oldHome)
}
} }