From fabdf0bea1fa2bf6a9d56cc3ea0f28242bf5e812 Mon Sep 17 00:00:00 2001 From: John Houston Date: Tue, 20 Apr 2021 10:05:45 -0400 Subject: [PATCH] Add config_paths and drop KUBECONFIG env variable in kubernetes backend (#26997) --- backend/remote-state/kubernetes/backend.go | 66 +++++++++++-------- backend/remote-state/kubernetes/client.go | 1 + .../settings/backends/kubernetes.html.md | 10 +-- 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/backend/remote-state/kubernetes/backend.go b/backend/remote-state/kubernetes/backend.go index 12530b0bf..a3398a464 100644 --- a/backend/remote-state/kubernetes/backend.go +++ b/backend/remote-state/kubernetes/backend.go @@ -6,11 +6,11 @@ import ( "fmt" "log" "os" + "path/filepath" "github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/internal/legacy/helper/schema" "github.com/hashicorp/terraform/version" - "github.com/mitchellh/cli" "github.com/mitchellh/go-homedir" k8sSchema "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" @@ -114,16 +114,17 @@ func New() backend.Backend { DefaultFunc: schema.EnvDefaultFunc("KUBE_CLUSTER_CA_CERT_DATA", ""), Description: "PEM-encoded root certificates bundle for TLS authentication.", }, + "config_paths": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Description: "A list of paths to kube config files. Can be set with KUBE_CONFIG_PATHS environment variable.", + }, "config_path": { - Type: schema.TypeString, - Optional: true, - DefaultFunc: schema.MultiEnvDefaultFunc( - []string{ - "KUBE_CONFIG", - "KUBECONFIG", - }, - "~/.kube/config"), - Description: "Path to the kube config file, defaults to ~/.kube/config", + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("KUBE_CONFIG_PATH", ""), + Description: "Path to the kube config file. Can be set with KUBE_CONFIG_PATH environment variable.", }, "config_context": { Type: schema.TypeString, @@ -285,15 +286,7 @@ func getInitialConfig(data *schema.ResourceData) (*restclient.Config, error) { var cfg *restclient.Config var err error - c := &cli.BasicUi{Writer: os.Stdout} - inCluster := data.Get("in_cluster_config").(bool) - cf := data.Get("load_config_file").(bool) - - if !inCluster && !cf { - c.Output(noConfigError) - } - if inCluster { cfg, err = restclient.InClusterConfig() if err != nil { @@ -313,13 +306,34 @@ func getInitialConfig(data *schema.ResourceData) (*restclient.Config, error) { } func tryLoadingConfigFile(d *schema.ResourceData) (*restclient.Config, error) { - path, err := homedir.Expand(d.Get("config_path").(string)) - if err != nil { - return nil, err + loader := &clientcmd.ClientConfigLoadingRules{} + + configPaths := []string{} + if v, ok := d.Get("config_path").(string); ok && v != "" { + configPaths = []string{v} + } else if v, ok := d.Get("config_paths").([]interface{}); ok && len(v) > 0 { + for _, p := range v { + configPaths = append(configPaths, p.(string)) + } + } else if v := os.Getenv("KUBE_CONFIG_PATHS"); v != "" { + configPaths = filepath.SplitList(v) } - loader := &clientcmd.ClientConfigLoadingRules{ - ExplicitPath: path, + expandedPaths := []string{} + for _, p := range configPaths { + path, err := homedir.Expand(p) + if err != nil { + log.Printf("[DEBUG] Could not expand path: %s", err) + return nil, err + } + log.Printf("[DEBUG] Using kubeconfig: %s", path) + expandedPaths = append(expandedPaths, path) + } + + if len(expandedPaths) == 1 { + loader.ExplicitPath = expandedPaths[0] + } else { + loader.Precedence = expandedPaths } overrides := &clientcmd.ConfigOverrides{} @@ -367,13 +381,13 @@ func tryLoadingConfigFile(d *schema.ResourceData) (*restclient.Config, error) { cfg, err := cc.ClientConfig() if err != nil { if pathErr, ok := err.(*os.PathError); ok && os.IsNotExist(pathErr.Err) { - log.Printf("[INFO] Unable to load config file as it doesn't exist at %q", path) + log.Printf("[INFO] Unable to load config file as it doesn't exist at %q", pathErr.Path) return nil, nil } - return nil, fmt.Errorf("Failed to load config (%s%s): %s", path, ctxSuffix, err) + return nil, fmt.Errorf("Failed to initialize kubernetes configuration: %s", err) } - log.Printf("[INFO] Successfully loaded config file (%s%s)", path, ctxSuffix) + log.Printf("[INFO] Successfully initialized config") return cfg, nil } diff --git a/backend/remote-state/kubernetes/client.go b/backend/remote-state/kubernetes/client.go index 5bc57f6bb..3490f91b5 100644 --- a/backend/remote-state/kubernetes/client.go +++ b/backend/remote-state/kubernetes/client.go @@ -17,6 +17,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/client-go/dynamic" + _ "k8s.io/client-go/plugin/pkg/client/auth" // Import to initialize client auth plugins. "k8s.io/utils/pointer" coordinationv1 "k8s.io/api/coordination/v1" diff --git a/website/docs/language/settings/backends/kubernetes.html.md b/website/docs/language/settings/backends/kubernetes.html.md index 715db82c5..71d80e24f 100644 --- a/website/docs/language/settings/backends/kubernetes.html.md +++ b/website/docs/language/settings/backends/kubernetes.html.md @@ -20,18 +20,18 @@ Stores the state in a [Kubernetes secret](https://kubernetes.io/docs/concepts/co terraform { backend "kubernetes" { secret_suffix = "state" - load_config_file = true + config_path = "~/.kube/config" } } ``` This assumes the user/service account running terraform has [permissions](https://kubernetes.io/docs/reference/access-authn-authz/authorization/) to read/write secrets in the [namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/) used to store the secret. -If the `load_config_file` flag is set the backend will attempt to use a [kubeconfig file](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) to gain access to the cluster. +If the `config_path` or `config_paths` attribute is set the backend will attempt to use a [kubeconfig file](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) to gain access to the cluster. If the `in_cluster_config` flag is set the backend will attempt to use a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) to access the cluster. This can be used if Terraform is being run from within a pod running in the Kubernetes cluster. -For most use cases either `in_cluster_config` or `load_config_file` will need to be set to `true`. If both flags are set the configuration from `load_config_file` will be used. +For most use cases either `in_cluster_config`, `config_path`, or `config_paths` will need to be set. If all flags are set the configuration at `config_path` will be used. Note that for the access credentials we recommend using a [partial configuration](/docs/language/settings/backends/configuration.html#partial-configuration). @@ -56,7 +56,6 @@ The following configuration options are supported: * `labels` - (Optional) Map of additional labels to be applied to the secret and lease. * `namespace` - (Optional) Namespace to store the secret and lease in. Can be sourced from `KUBE_NAMESPACE`. * `in_cluster_config` - (Optional) Used to authenticate to the cluster from inside a pod. Can be sourced from `KUBE_IN_CLUSTER_CONFIG`. -* `load_config_file` - (Optional) Use a kubeconfig file to access the cluster. Can be sourced from `KUBE_LOAD_CONFIG_FILE`. * `host` - (Optional) The hostname (in form of URI) of Kubernetes master. Can be sourced from `KUBE_HOST`. Defaults to `https://localhost`. * `username` - (Optional) The username to use for HTTP basic authentication when accessing the Kubernetes master endpoint. Can be sourced from `KUBE_USER`. * `password` - (Optional) The password to use for HTTP basic authentication when accessing the Kubernetes master endpoint. Can be sourced from `KUBE_PASSWORD`. @@ -64,7 +63,8 @@ The following configuration options are supported: * `client_certificate` - (Optional) PEM-encoded client certificate for TLS authentication. Can be sourced from `KUBE_CLIENT_CERT_DATA`. * `client_key` - (Optional) PEM-encoded client certificate key for TLS authentication. Can be sourced from `KUBE_CLIENT_KEY_DATA`. * `cluster_ca_certificate` - (Optional) PEM-encoded root certificates bundle for TLS authentication. Can be sourced from `KUBE_CLUSTER_CA_CERT_DATA`. -* `config_path` - (Optional) Path to the kube config file. Can be sourced from `KUBE_CONFIG` or `KUBECONFIG`. Defaults to `~/.kube/config`. +* `config_path` - (Optional) Path to the kube config file. Can be sourced from `KUBE_CONFIG_PATH`. +* `config_paths` - (Optional) List of paths to kube config files. Can be sourced from `KUBE_CONFIG_PATHS`. * `config_context` - (Optional) Context to choose from the config file. Can be sourced from `KUBE_CTX`. * `config_context_auth_info` - (Optional) Authentication info context of the kube config (name of the kubeconfig user, `--user` flag in `kubectl`). Can be sourced from `KUBE_CTX_AUTH_INFO`. * `config_context_cluster` - (Optional) Cluster context of the kube config (name of the kubeconfig cluster, `--cluster` flag in `kubectl`). Can be sourced from `KUBE_CTX_CLUSTER`.