diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 8000e3dbb..2fd922907 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -43,6 +43,10 @@ func Execute() { } } +const ( + kubeconfigFlag = "kubeconfig-file" +) + func init() { cobra.OnInitialize(cobraInit) @@ -50,10 +54,16 @@ func init() { // Add kubeconfig flag. RootCmd.PersistentFlags().String( - "kubeconfig", + kubeconfigFlag, "", // Special empty default, use getKubeconfig() - "Path to kubeconfig file, taken from the asset dir if not given, and finally falls back to ~/.kube/config") - viper.BindPFlag("kubeconfig", RootCmd.PersistentFlags().Lookup("kubeconfig")) + `Path to a kubeconfig file. If empty, the following precedence order is `+ + `used: 1. cluster asset dir when a lokocfg file is present in the `+ + `current directory 2. KUBECONFIG environment variable 3. `+ + `"~/.kube/config"`) + + if err := viper.BindPFlag(kubeconfigFlag, RootCmd.PersistentFlags().Lookup(kubeconfigFlag)); err != nil { + panic("failed registering kubeconfig flag") + } RootCmd.PersistentFlags().String("lokocfg", "./", "Path to lokocfg directory or file") viper.BindPFlag("lokocfg", RootCmd.PersistentFlags().Lookup("lokocfg")) diff --git a/cli/cmd/utils.go b/cli/cmd/utils.go index 9ed5dfc33..f0fec19bb 100644 --- a/cli/cmd/utils.go +++ b/cli/cmd/utils.go @@ -29,6 +29,11 @@ import ( "github.com/kinvolk/lokomotive/pkg/platform" ) +const ( + kubeconfigEnvVariable = "KUBECONFIG" + defaultKubeconfigPath = "~/.kube/config" +) + // getConfiguredBackend loads a backend from the given configuration file. func getConfiguredBackend(lokoConfig *config.Config) (backend.Backend, hcl.Diagnostics) { if lokoConfig.RootConfig.Backend == nil { @@ -101,26 +106,55 @@ func expandKubeconfigPath(path string) string { return path } -// getKubeconfig finds the kubeconfig to be used. Precedence takes a specified -// flag or environment variable. Then the asset directory of the cluster is searched -// and finally the global default value is used. This cannot be done in Viper -// because we need the other values from Viper to find the asset directory. +// getKubeconfig finds the kubeconfig to be used. The precedence is the following: +// - --kubeconfig-file flag OR KUBECONFIG_FILE environent variable (the latter +// is a side-effect of cobra/viper and should NOT be documented because it's +// confusing). +// - Asset directory from cluster configuration. +// - KUBECONFIG environment variable. +// - ~/.kube/config path, which is the default for kubectl. func getKubeconfig() (string, error) { - kubeconfig := viper.GetString("kubeconfig") - if kubeconfig != "" { - return expandKubeconfigPath(kubeconfig), nil + assetKubeconfig, err := assetsKubeconfigPath() + if err != nil { + return "", fmt.Errorf("reading kubeconfig path from configuration failed: %w", err) } + paths := []string{ + viper.GetString(kubeconfigFlag), + assetKubeconfig, + os.Getenv(kubeconfigEnvVariable), + defaultKubeconfigPath, + } + + return expandKubeconfigPath(pickString(paths...)), nil +} + +// pickString returns first non-empty string. +func pickString(options ...string) string { + for _, option := range options { + if option != "" { + return option + } + } + + return "" +} + +// assetsKubeconfigPath reads the lokocfg configuration and returns +// the kubeconfig path defined in it. +// +// If no configuration is defined, empty string is returned. +func assetsKubeconfigPath() (string, error) { assetDir, err := getAssetDir() if err != nil { return "", err } if assetDir != "" { - return expandKubeconfigPath(assetsKubeconfig(assetDir)), nil + return assetsKubeconfig(assetDir), nil } - return expandKubeconfigPath("~/.kube/config"), nil + return "", nil } func assetsKubeconfig(assetDir string) string { diff --git a/cli/cmd/utils_internal_test.go b/cli/cmd/utils_internal_test.go new file mode 100644 index 000000000..9982e7caa --- /dev/null +++ b/cli/cmd/utils_internal_test.go @@ -0,0 +1,204 @@ +// Copyright 2020 The Lokomotive Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/spf13/viper" +) + +type kubeconfigSources struct { + flag string + env string + configFile string +} + +func prepareKubeconfigSource(t *testing.T, k *kubeconfigSources) { + // Ensure viper flag is NOT empty. + viper.Set(kubeconfigFlag, k.flag) + + if k.env == "" { + // Ensure KUBECONFIG is not set. + if err := os.Unsetenv(kubeconfigEnvVariable); err != nil { + t.Fatalf("unsetting %q environment variable: %v", kubeconfigEnvVariable, err) + } + } + + if k.env != "" { + // Ensure KUBECONFIG IS set. + if err := os.Setenv(kubeconfigEnvVariable, k.env); err != nil { + t.Fatalf("setting %q environment variable: %v", kubeconfigEnvVariable, err) + } + } + + // Ensure there is no lokocfg configuration in working directory. + tmpDir, err := ioutil.TempDir("", "lokoctl-tests-") + if err != nil { + t.Fatalf("creating tmp dir: %v", err) + } + + t.Cleanup(func() { + if err := os.RemoveAll(tmpDir); err != nil { + t.Logf("removing temp dir %q: %v", tmpDir, err) + } + }) + + if err := os.Chdir(tmpDir); err != nil { + t.Fatalf("changing working directory to %q: %v", tmpDir, err) + } + + if k.configFile != "" { + path := filepath.Join(tmpDir, "cluster.lokocfg") + if err := ioutil.WriteFile(path, []byte(k.configFile), 0600); err != nil { + t.Fatalf("writing file %q: %v", path, err) + } + } +} + +func TestGetKubeconfigFlag(t *testing.T) { + expectedPath := "/foo" + + k := &kubeconfigSources{ + configFile: `cluster "packet" { + asset_dir = "/bad" + + cluster_name = "" + controller_count = 0 + facility = "" + management_cidrs = [] + node_private_cidr = "" + project_id = "" + ssh_pubkeys = [] + dns { + provider = "" + zone = "" + } + worker_pool "foo" { + count = 0 + } +}`, + flag: expectedPath, + env: "/badpath", + } + + prepareKubeconfigSource(t, k) + + kubeconfig, err := getKubeconfig() + if err != nil { + t.Fatalf("getting kubeconfig: %v", err) + } + + if kubeconfig != expectedPath { + t.Fatalf("expected %q, got %q", expectedPath, kubeconfig) + } +} + +func TestGetKubeconfigConfigFile(t *testing.T) { + expectedPath := assetsKubeconfig("/foo") + + k := &kubeconfigSources{ + configFile: `cluster "packet" { + asset_dir = "/foo" + + cluster_name = "" + controller_count = 0 + facility = "" + management_cidrs = [] + node_private_cidr = "" + project_id = "" + ssh_pubkeys = [] + dns { + provider = "" + zone = "" + } + worker_pool "foo" { + count = 0 + } +}`, + env: "/badpath", + } + + prepareKubeconfigSource(t, k) + + kubeconfig, err := getKubeconfig() + if err != nil { + t.Fatalf("getting kubeconfig: %v", err) + } + + if kubeconfig != expectedPath { + t.Fatalf("expected %q, got %q", expectedPath, kubeconfig) + } +} + +func TestGetKubeconfigBadConfigFile(t *testing.T) { + expectedPath := "" + + k := &kubeconfigSources{ + configFile: `cluster "packet" { + asset_dir = "/foo" +}`, + } + + prepareKubeconfigSource(t, k) + + kubeconfig, err := getKubeconfig() + if err == nil { + t.Errorf("getting kubeconfig with bad configuration should fail") + } + + if kubeconfig != expectedPath { + t.Fatalf("if getting kubeconfig fails, empty path should be returned") + } +} + +func TestGetKubeconfigEnvVariable(t *testing.T) { + expectedPath := "/foo" + + k := &kubeconfigSources{ + env: expectedPath, + } + + prepareKubeconfigSource(t, k) + + kubeconfig, err := getKubeconfig() + if err != nil { + t.Fatalf("getting kubeconfig: %v", err) + } + + if kubeconfig != expectedPath { + t.Fatalf("expected %q, got %q", expectedPath, kubeconfig) + } +} + +func TestGetKubeconfigDefault(t *testing.T) { + expectedPath := expandKubeconfigPath(defaultKubeconfigPath) + + k := &kubeconfigSources{} + + prepareKubeconfigSource(t, k) + + kubeconfig, err := getKubeconfig() + if err != nil { + t.Fatalf("getting kubeconfig: %v", err) + } + + if kubeconfig != expectedPath { + t.Fatalf("expected %q, got %q", expectedPath, kubeconfig) + } +} diff --git a/docs/cli/lokoctl.md b/docs/cli/lokoctl.md index dab08c260..ea599934c 100644 --- a/docs/cli/lokoctl.md +++ b/docs/cli/lokoctl.md @@ -9,10 +9,10 @@ Manage Lokomotive clusters ### Options ``` - -h, --help help for lokoctl - --kubeconfig string Path to kubeconfig file, taken from the asset dir if not given, and finally falls back to ~/.kube/config - --lokocfg string Path to lokocfg directory or file (default "./") - --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") + -h, --help help for lokoctl + --kubeconfig-file string Path to a kubeconfig file. If empty, the following precedence order is used: 1. cluster asset dir when a lokocfg file is present in the current directory 2. KUBECONFIG environment variable 3. "~/.kube/config" + --lokocfg string Path to lokocfg directory or file (default "./") + --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") ``` ### SEE ALSO diff --git a/docs/cli/lokoctl_cluster.md b/docs/cli/lokoctl_cluster.md index 07a855cdb..3bf7177bb 100644 --- a/docs/cli/lokoctl_cluster.md +++ b/docs/cli/lokoctl_cluster.md @@ -15,9 +15,9 @@ Manage a cluster ### Options inherited from parent commands ``` - --kubeconfig string Path to kubeconfig file, taken from the asset dir if not given, and finally falls back to ~/.kube/config - --lokocfg string Path to lokocfg directory or file (default "./") - --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") + --kubeconfig-file string Path to a kubeconfig file. If empty, the following precedence order is used: 1. cluster asset dir when a lokocfg file is present in the current directory 2. KUBECONFIG environment variable 3. "~/.kube/config" + --lokocfg string Path to lokocfg directory or file (default "./") + --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") ``` ### SEE ALSO diff --git a/docs/cli/lokoctl_cluster_apply.md b/docs/cli/lokoctl_cluster_apply.md index 8dab63bdf..1cabc87c8 100644 --- a/docs/cli/lokoctl_cluster_apply.md +++ b/docs/cli/lokoctl_cluster_apply.md @@ -25,9 +25,9 @@ lokoctl cluster apply [flags] ### Options inherited from parent commands ``` - --kubeconfig string Path to kubeconfig file, taken from the asset dir if not given, and finally falls back to ~/.kube/config - --lokocfg string Path to lokocfg directory or file (default "./") - --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") + --kubeconfig-file string Path to a kubeconfig file. If empty, the following precedence order is used: 1. cluster asset dir when a lokocfg file is present in the current directory 2. KUBECONFIG environment variable 3. "~/.kube/config" + --lokocfg string Path to lokocfg directory or file (default "./") + --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") ``` ### SEE ALSO diff --git a/docs/cli/lokoctl_cluster_destroy.md b/docs/cli/lokoctl_cluster_destroy.md index 1b3413b64..c097c7529 100644 --- a/docs/cli/lokoctl_cluster_destroy.md +++ b/docs/cli/lokoctl_cluster_destroy.md @@ -21,9 +21,9 @@ lokoctl cluster destroy [flags] ### Options inherited from parent commands ``` - --kubeconfig string Path to kubeconfig file, taken from the asset dir if not given, and finally falls back to ~/.kube/config - --lokocfg string Path to lokocfg directory or file (default "./") - --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") + --kubeconfig-file string Path to a kubeconfig file. If empty, the following precedence order is used: 1. cluster asset dir when a lokocfg file is present in the current directory 2. KUBECONFIG environment variable 3. "~/.kube/config" + --lokocfg string Path to lokocfg directory or file (default "./") + --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") ``` ### SEE ALSO diff --git a/docs/cli/lokoctl_component.md b/docs/cli/lokoctl_component.md index 9447cf9b3..ea3b04e14 100644 --- a/docs/cli/lokoctl_component.md +++ b/docs/cli/lokoctl_component.md @@ -15,9 +15,9 @@ Manage components ### Options inherited from parent commands ``` - --kubeconfig string Path to kubeconfig file, taken from the asset dir if not given, and finally falls back to ~/.kube/config - --lokocfg string Path to lokocfg directory or file (default "./") - --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") + --kubeconfig-file string Path to a kubeconfig file. If empty, the following precedence order is used: 1. cluster asset dir when a lokocfg file is present in the current directory 2. KUBECONFIG environment variable 3. "~/.kube/config" + --lokocfg string Path to lokocfg directory or file (default "./") + --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") ``` ### SEE ALSO diff --git a/docs/cli/lokoctl_component_apply.md b/docs/cli/lokoctl_component_apply.md index b60eed8d4..dd1de1311 100644 --- a/docs/cli/lokoctl_component_apply.md +++ b/docs/cli/lokoctl_component_apply.md @@ -21,9 +21,9 @@ lokoctl component apply [flags] ### Options inherited from parent commands ``` - --kubeconfig string Path to kubeconfig file, taken from the asset dir if not given, and finally falls back to ~/.kube/config - --lokocfg string Path to lokocfg directory or file (default "./") - --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") + --kubeconfig-file string Path to a kubeconfig file. If empty, the following precedence order is used: 1. cluster asset dir when a lokocfg file is present in the current directory 2. KUBECONFIG environment variable 3. "~/.kube/config" + --lokocfg string Path to lokocfg directory or file (default "./") + --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") ``` ### SEE ALSO diff --git a/docs/cli/lokoctl_component_delete.md b/docs/cli/lokoctl_component_delete.md index 955f09c2a..819db5cc3 100644 --- a/docs/cli/lokoctl_component_delete.md +++ b/docs/cli/lokoctl_component_delete.md @@ -22,9 +22,9 @@ lokoctl component delete [flags] ### Options inherited from parent commands ``` - --kubeconfig string Path to kubeconfig file, taken from the asset dir if not given, and finally falls back to ~/.kube/config - --lokocfg string Path to lokocfg directory or file (default "./") - --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") + --kubeconfig-file string Path to a kubeconfig file. If empty, the following precedence order is used: 1. cluster asset dir when a lokocfg file is present in the current directory 2. KUBECONFIG environment variable 3. "~/.kube/config" + --lokocfg string Path to lokocfg directory or file (default "./") + --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") ``` ### SEE ALSO diff --git a/docs/cli/lokoctl_component_list.md b/docs/cli/lokoctl_component_list.md index 6b5ca04f5..31b5b9885 100644 --- a/docs/cli/lokoctl_component_list.md +++ b/docs/cli/lokoctl_component_list.md @@ -19,9 +19,9 @@ lokoctl component list [flags] ### Options inherited from parent commands ``` - --kubeconfig string Path to kubeconfig file, taken from the asset dir if not given, and finally falls back to ~/.kube/config - --lokocfg string Path to lokocfg directory or file (default "./") - --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") + --kubeconfig-file string Path to a kubeconfig file. If empty, the following precedence order is used: 1. cluster asset dir when a lokocfg file is present in the current directory 2. KUBECONFIG environment variable 3. "~/.kube/config" + --lokocfg string Path to lokocfg directory or file (default "./") + --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") ``` ### SEE ALSO diff --git a/docs/cli/lokoctl_component_render-manifest.md b/docs/cli/lokoctl_component_render-manifest.md index 7226baf1c..63e343993 100644 --- a/docs/cli/lokoctl_component_render-manifest.md +++ b/docs/cli/lokoctl_component_render-manifest.md @@ -19,9 +19,9 @@ lokoctl component render-manifest [flags] ### Options inherited from parent commands ``` - --kubeconfig string Path to kubeconfig file, taken from the asset dir if not given, and finally falls back to ~/.kube/config - --lokocfg string Path to lokocfg directory or file (default "./") - --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") + --kubeconfig-file string Path to a kubeconfig file. If empty, the following precedence order is used: 1. cluster asset dir when a lokocfg file is present in the current directory 2. KUBECONFIG environment variable 3. "~/.kube/config" + --lokocfg string Path to lokocfg directory or file (default "./") + --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") ``` ### SEE ALSO diff --git a/docs/cli/lokoctl_health.md b/docs/cli/lokoctl_health.md index 2a883a042..169d4ca26 100644 --- a/docs/cli/lokoctl_health.md +++ b/docs/cli/lokoctl_health.md @@ -19,9 +19,9 @@ lokoctl health [flags] ### Options inherited from parent commands ``` - --kubeconfig string Path to kubeconfig file, taken from the asset dir if not given, and finally falls back to ~/.kube/config - --lokocfg string Path to lokocfg directory or file (default "./") - --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") + --kubeconfig-file string Path to a kubeconfig file. If empty, the following precedence order is used: 1. cluster asset dir when a lokocfg file is present in the current directory 2. KUBECONFIG environment variable 3. "~/.kube/config" + --lokocfg string Path to lokocfg directory or file (default "./") + --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") ``` ### SEE ALSO diff --git a/docs/cli/lokoctl_version.md b/docs/cli/lokoctl_version.md index 9429a8682..7169d4688 100644 --- a/docs/cli/lokoctl_version.md +++ b/docs/cli/lokoctl_version.md @@ -19,9 +19,9 @@ lokoctl version [flags] ### Options inherited from parent commands ``` - --kubeconfig string Path to kubeconfig file, taken from the asset dir if not given, and finally falls back to ~/.kube/config - --lokocfg string Path to lokocfg directory or file (default "./") - --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") + --kubeconfig-file string Path to a kubeconfig file. If empty, the following precedence order is used: 1. cluster asset dir when a lokocfg file is present in the current directory 2. KUBECONFIG environment variable 3. "~/.kube/config" + --lokocfg string Path to lokocfg directory or file (default "./") + --lokocfg-vars string Path to lokocfg.vars file (default "./lokocfg.vars") ``` ### SEE ALSO