Add config_paths and drop KUBECONFIG env variable in kubernetes backend (#26997)

This commit is contained in:
John Houston 2021-04-20 10:05:45 -04:00 committed by GitHub
parent 14336ae6f8
commit fabdf0bea1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 46 additions and 31 deletions

View File

@ -6,11 +6,11 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"path/filepath"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/internal/legacy/helper/schema" "github.com/hashicorp/terraform/internal/legacy/helper/schema"
"github.com/hashicorp/terraform/version" "github.com/hashicorp/terraform/version"
"github.com/mitchellh/cli"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
k8sSchema "k8s.io/apimachinery/pkg/runtime/schema" k8sSchema "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
@ -114,16 +114,17 @@ func New() backend.Backend {
DefaultFunc: schema.EnvDefaultFunc("KUBE_CLUSTER_CA_CERT_DATA", ""), DefaultFunc: schema.EnvDefaultFunc("KUBE_CLUSTER_CA_CERT_DATA", ""),
Description: "PEM-encoded root certificates bundle for TLS authentication.", 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": { "config_path": {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
DefaultFunc: schema.MultiEnvDefaultFunc( DefaultFunc: schema.EnvDefaultFunc("KUBE_CONFIG_PATH", ""),
[]string{ Description: "Path to the kube config file. Can be set with KUBE_CONFIG_PATH environment variable.",
"KUBE_CONFIG",
"KUBECONFIG",
},
"~/.kube/config"),
Description: "Path to the kube config file, defaults to ~/.kube/config",
}, },
"config_context": { "config_context": {
Type: schema.TypeString, Type: schema.TypeString,
@ -285,15 +286,7 @@ func getInitialConfig(data *schema.ResourceData) (*restclient.Config, error) {
var cfg *restclient.Config var cfg *restclient.Config
var err error var err error
c := &cli.BasicUi{Writer: os.Stdout}
inCluster := data.Get("in_cluster_config").(bool) inCluster := data.Get("in_cluster_config").(bool)
cf := data.Get("load_config_file").(bool)
if !inCluster && !cf {
c.Output(noConfigError)
}
if inCluster { if inCluster {
cfg, err = restclient.InClusterConfig() cfg, err = restclient.InClusterConfig()
if err != nil { if err != nil {
@ -313,13 +306,34 @@ func getInitialConfig(data *schema.ResourceData) (*restclient.Config, error) {
} }
func tryLoadingConfigFile(d *schema.ResourceData) (*restclient.Config, error) { func tryLoadingConfigFile(d *schema.ResourceData) (*restclient.Config, error) {
path, err := homedir.Expand(d.Get("config_path").(string)) loader := &clientcmd.ClientConfigLoadingRules{}
if err != nil {
return nil, err 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{ expandedPaths := []string{}
ExplicitPath: path, 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{} overrides := &clientcmd.ConfigOverrides{}
@ -367,13 +381,13 @@ func tryLoadingConfigFile(d *schema.ResourceData) (*restclient.Config, error) {
cfg, err := cc.ClientConfig() cfg, err := cc.ClientConfig()
if err != nil { if err != nil {
if pathErr, ok := err.(*os.PathError); ok && os.IsNotExist(pathErr.Err) { 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, 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 return cfg, nil
} }

View File

@ -17,6 +17,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
_ "k8s.io/client-go/plugin/pkg/client/auth" // Import to initialize client auth plugins.
"k8s.io/utils/pointer" "k8s.io/utils/pointer"
coordinationv1 "k8s.io/api/coordination/v1" coordinationv1 "k8s.io/api/coordination/v1"

View File

@ -20,18 +20,18 @@ Stores the state in a [Kubernetes secret](https://kubernetes.io/docs/concepts/co
terraform { terraform {
backend "kubernetes" { backend "kubernetes" {
secret_suffix = "state" 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. 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. 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). 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. * `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`. * `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`. * `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`. * `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`. * `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`. * `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_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`. * `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`. * `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` - (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_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`. * `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`.