From 9a4c8bc399a84dd1cdf214435e0b171a3b97218e Mon Sep 17 00:00:00 2001 From: Mateusz Gozdek Date: Fri, 3 Jul 2020 15:14:54 +0200 Subject: [PATCH] cli/cmd: use kubeconfig from Terraform output, not from assets dir Closes #608 Signed-off-by: Mateusz Gozdek --- cli/cmd/cluster-apply.go | 4 +- cli/cmd/cluster-destroy.go | 2 +- cli/cmd/cluster.go | 10 +++- cli/cmd/component-apply.go | 4 +- cli/cmd/component-delete.go | 7 ++- cli/cmd/health.go | 16 +----- cli/cmd/utils.go | 102 +++++++++++++-------------------- cli/cmd/utils_internal_test.go | 6 +- 8 files changed, 64 insertions(+), 87 deletions(-) diff --git a/cli/cmd/cluster-apply.go b/cli/cmd/cluster-apply.go index 287971086..b1c58f1bf 100644 --- a/cli/cmd/cluster-apply.go +++ b/cli/cmd/cluster-apply.go @@ -57,7 +57,7 @@ func runClusterApply(cmd *cobra.Command, args []string) { "args": args, }) - ex, p, lokoConfig, assetDir := initialize(ctxLogger) + ex, p, lokoConfig, assetDir := initialize(ctxLogger, true) exists := clusterExists(ctxLogger, ex) if exists && !confirm { @@ -79,7 +79,7 @@ func runClusterApply(cmd *cobra.Command, args []string) { fmt.Printf("\nYour configurations are stored in %s\n", assetDir) - kubeconfig, err := getKubeconfig() + kubeconfig, err := getKubeconfig(p, ex) if err != nil { ctxLogger.Fatalf("Failed to get kubeconfig: %v", err) } diff --git a/cli/cmd/cluster-destroy.go b/cli/cmd/cluster-destroy.go index e7a728e1f..6fd38aab8 100644 --- a/cli/cmd/cluster-destroy.go +++ b/cli/cmd/cluster-destroy.go @@ -40,7 +40,7 @@ func runClusterDestroy(cmd *cobra.Command, args []string) { "args": args, }) - ex, p, _, _ := initialize(ctxLogger) + ex, p, _, _ := initialize(ctxLogger, true) if !clusterExists(ctxLogger, ex) { ctxLogger.Println("Cluster already destroyed, nothing to do") diff --git a/cli/cmd/cluster.go b/cli/cmd/cluster.go index d8b261834..36a132fb3 100644 --- a/cli/cmd/cluster.go +++ b/cli/cmd/cluster.go @@ -45,13 +45,13 @@ func init() { // initialize does common initialization actions between cluster operations // and returns created objects to the caller for further use. -func initialize(ctxLogger *logrus.Entry) (*terraform.Executor, platform.Platform, *config.Config, string) { +func initialize(ctxLogger *logrus.Entry, requirePlatform bool) (*terraform.Executor, platform.Platform, *config.Config, string) { lokoConfig, diags := getLokoConfig() if len(diags) > 0 { ctxLogger.Fatal(diags) } - p, diags := getConfiguredPlatform() + p, diags := getConfiguredPlatform(lokoConfig) if diags.HasErrors() { for _, diagnostic := range diags { ctxLogger.Error(diagnostic.Error()) @@ -61,7 +61,11 @@ func initialize(ctxLogger *logrus.Entry) (*terraform.Executor, platform.Platform } if p == nil { - ctxLogger.Fatal("No cluster configured") + if requirePlatform { + ctxLogger.Fatal("No cluster configured") + } + + return nil, nil, nil, "" } // Get the configured backend for the cluster. Backend types currently supported: local, s3. diff --git a/cli/cmd/component-apply.go b/cli/cmd/component-apply.go index fb80a4f39..fd1c18fbc 100644 --- a/cli/cmd/component-apply.go +++ b/cli/cmd/component-apply.go @@ -58,7 +58,9 @@ func runApply(cmd *cobra.Command, args []string) { } } - kubeconfig, err := getKubeconfig() + ex, p, _, _ := initialize(contextLogger, false) + + kubeconfig, err := getKubeconfig(p, ex) if err != nil { contextLogger.Fatalf("Error in finding kubeconfig file: %s", err) } diff --git a/cli/cmd/component-delete.go b/cli/cmd/component-delete.go index ab1fe0ebb..b51e6ee04 100644 --- a/cli/cmd/component-delete.go +++ b/cli/cmd/component-delete.go @@ -23,6 +23,8 @@ import ( "github.com/kinvolk/lokomotive/pkg/components" "github.com/kinvolk/lokomotive/pkg/components/util" + "github.com/kinvolk/lokomotive/pkg/platform" + "github.com/kinvolk/lokomotive/pkg/terraform" ) var componentDeleteCmd = &cobra.Command{ @@ -86,7 +88,10 @@ func runDelete(cmd *cobra.Command, args []string) { return } - kubeconfig, err := getKubeconfig() + var ex *terraform.Executor + var p platform.Platform + + kubeconfig, err := getKubeconfig(p, ex) if err != nil { contextLogger.Fatalf("Error in finding kubeconfig file: %s", err) } diff --git a/cli/cmd/health.go b/cli/cmd/health.go index 1779c07d0..6dd6bb506 100644 --- a/cli/cmd/health.go +++ b/cli/cmd/health.go @@ -42,7 +42,9 @@ func runHealth(cmd *cobra.Command, args []string) { "args": args, }) - kubeconfig, err := getKubeconfig() + ex, p, _, _ := initialize(contextLogger, true) + + kubeconfig, err := getKubeconfig(p, ex) if err != nil { contextLogger.Fatalf("Error in finding kubeconfig file: %s", err) } @@ -52,18 +54,6 @@ func runHealth(cmd *cobra.Command, args []string) { contextLogger.Fatalf("Error in creating setting up Kubernetes client: %q", err) } - p, diags := getConfiguredPlatform() - if diags.HasErrors() { - for _, diagnostic := range diags { - contextLogger.Error(diagnostic.Error()) - } - contextLogger.Fatal("Errors found while loading cluster configuration") - } - - if p == nil { - contextLogger.Fatal("No cluster configured") - } - cluster, err := lokomotive.NewCluster(cs, p.Meta().ExpectedNodes) if err != nil { contextLogger.Fatalf("Error in creating new Lokomotive cluster: %q", err) diff --git a/cli/cmd/utils.go b/cli/cmd/utils.go index 1cd8b00e1..a61b2916c 100644 --- a/cli/cmd/utils.go +++ b/cli/cmd/utils.go @@ -18,7 +18,6 @@ import ( "fmt" "io/ioutil" "os" - "path/filepath" "github.com/hashicorp/hcl/v2" "github.com/mitchellh/go-homedir" @@ -27,11 +26,13 @@ import ( "github.com/kinvolk/lokomotive/pkg/backend" "github.com/kinvolk/lokomotive/pkg/config" "github.com/kinvolk/lokomotive/pkg/platform" + "github.com/kinvolk/lokomotive/pkg/terraform" ) const ( - kubeconfigEnvVariable = "KUBECONFIG" - defaultKubeconfigPath = "~/.kube/config" + kubeconfigEnvVariable = "KUBECONFIG" + defaultKubeconfigPath = "~/.kube/config" + kubeconfigTerraformOutputKey = "kubeconfig" ) // getConfiguredBackend loads a backend from the given configuration file. @@ -54,12 +55,7 @@ func getConfiguredBackend(lokoConfig *config.Config) (backend.Backend, hcl.Diagn } // getConfiguredPlatform loads a platform from the given configuration file. -func getConfiguredPlatform() (platform.Platform, hcl.Diagnostics) { - lokoConfig, diags := getLokoConfig() - if diags.HasErrors() { - return nil, diags - } - +func getConfiguredPlatform(lokoConfig *config.Config) (platform.Platform, hcl.Diagnostics) { if lokoConfig.RootConfig.Cluster == nil { // No cluster defined and no configuration error return nil, hcl.Diagnostics{} @@ -77,28 +73,16 @@ func getConfiguredPlatform() (platform.Platform, hcl.Diagnostics) { return platform, platform.LoadConfig(&lokoConfig.RootConfig.Cluster.Config, lokoConfig.EvalContext) } -// getAssetDir extracts the asset path from the cluster configuration. -// It is empty if there is no cluster defined. An error is returned if the -// cluster configuration has problems. -func getAssetDir() (string, error) { - cfg, diags := getConfiguredPlatform() - if diags.HasErrors() { - return "", fmt.Errorf("cannot load config: %s", diags) - } - if cfg == nil { - // No cluster defined and no configuration error - return "", nil +func readKubeconfigFromTerraformOutput(ex *terraform.Executor, key string) ([]byte, error) { + kubeconfig := "" + if err := ex.Output(key, &kubeconfig); err != nil { + return nil, fmt.Errorf("reading kubeconfig file content from Terraform state: %w", err) } - return cfg.Meta().AssetDir, nil + return []byte(kubeconfig), nil } -func getKubeconfig() ([]byte, error) { - path, err := getKubeconfigPath() - if err != nil { - return nil, fmt.Errorf("failed getting kubeconfig path: %w", err) - } - +func readKubeconfigFromFile(path string) ([]byte, error) { if expandedPath, err := homedir.Expand(path); err == nil { path = expandedPath } @@ -109,54 +93,46 @@ func getKubeconfig() ([]byte, error) { return ioutil.ReadFile(path) // #nosec G304 } -// getKubeconfig finds the kubeconfig to be used. The precedence is the following: -// - --kubeconfig-file flag OR KUBECONFIG_FILE environment 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 getKubeconfigPath() (string, error) { - assetKubeconfig, err := assetsKubeconfigPath() - if err != nil { - return "", fmt.Errorf("reading kubeconfig path from configuration failed: %w", err) +// getKubeconfig returns content of kubeconfig file, based on the cluster configuration, flags and +// environment variables set. +// +// The hierarchy of selecting kubeconfig file to use is the following: +// +// - --kubeconfig-file OR KUBECONFIG_FILE environent variable (the latter +// is a side-effect of cobra/viper and should NOT be documented because it's +// confusing). It always takes precendence if it's not empty. +// +// - If cluster configuration is found and contains platform configuration, kubeconfig from the +// Terraform state will be used. +// +// - Path from KUBECONFIG environment variable. +// +// - Default KUBECONFIG path, which is ~/.kube/config. +func getKubeconfig(p platform.Platform, ex *terraform.Executor) ([]byte, error) { + // TODO: This should probably be passed as an argument, so we don't do global lookups here, + // but for now, it stays here, as it would duplicate the code and require all callers to import + // viper package. + flagPath := viper.GetString(kubeconfigFlag) + + // Path from the flag takes precedence over all other source of kubeconfig content. + if flagPath == "" && p != nil { + return readKubeconfigFromTerraformOutput(ex, kubeconfigTerraformOutputKey) } paths := []string{ - viper.GetString(kubeconfigFlag), - assetKubeconfig, + flagPath, os.Getenv(kubeconfigEnvVariable), defaultKubeconfigPath, } for _, path := range paths { if path != "" { - return path, nil + return readKubeconfigFromFile(path) } } - return "", nil -} - -// 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 assetsKubeconfig(assetDir), nil - } - - return "", nil -} - -func assetsKubeconfig(assetDir string) string { - return filepath.Join(assetDir, "cluster-assets", "auth", "kubeconfig") + // As we use defaultKubeconfigPath, this should never be triggered. + return nil, fmt.Errorf("no valid kubeconfig found") } func getLokoConfig() (*config.Config, hcl.Diagnostics) { diff --git a/cli/cmd/utils_internal_test.go b/cli/cmd/utils_internal_test.go index 541f67125..0fe7e0cf3 100644 --- a/cli/cmd/utils_internal_test.go +++ b/cli/cmd/utils_internal_test.go @@ -85,7 +85,7 @@ func TestGetKubeconfigBadConfig(t *testing.T) { prepareKubeconfigSource(t, k) - kubeconfig, err := getKubeconfig() + kubeconfig, err := getKubeconfig(nil, nil) if err == nil { t.Errorf("getting kubeconfig with bad configuration should fail") } @@ -113,7 +113,7 @@ func TestGetKubeconfig(t *testing.T) { prepareKubeconfigSource(t, k) - kubeconfig, err := getKubeconfig() + kubeconfig, err := getKubeconfig(nil, nil) if err != nil { t.Fatalf("getting kubeconfig: %v", err) } @@ -151,7 +151,7 @@ func TestGetKubeconfigPathFlag(t *testing.T) { prepareKubeconfigSource(t, k) - kubeconfig, err := getKubeconfigPath() + kubeconfig, err := getKubeconfig(nil, nil) if err != nil { t.Fatalf("getting kubeconfig: %v", err) }