From 3be7d12a245238040d876ab564bdaea07e57477d Mon Sep 17 00:00:00 2001 From: John Houston Date: Mon, 2 Nov 2020 15:44:42 -0500 Subject: [PATCH 01/11] Remove load_config_file and support for KUBECONFIG environment variable --- kubernetes/provider.go | 83 ++++++++++++++------------------ website/docs/index.html.markdown | 4 +- 2 files changed, 37 insertions(+), 50 deletions(-) diff --git a/kubernetes/provider.go b/kubernetes/provider.go index 72fae0baf2..a20c172cab 100644 --- a/kubernetes/provider.go +++ b/kubernetes/provider.go @@ -4,10 +4,11 @@ import ( "bytes" "context" "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "log" "net/http" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/mitchellh/go-homedir" @@ -68,15 +69,10 @@ func Provider() *schema.Provider { Description: "PEM-encoded root certificates bundle for TLS authentication.", }, "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, @@ -101,12 +97,6 @@ func Provider() *schema.Provider { DefaultFunc: schema.EnvDefaultFunc("KUBE_TOKEN", ""), Description: "Token to authenticate an service account", }, - "load_config_file": { - Type: schema.TypeBool, - Optional: true, - DefaultFunc: schema.EnvDefaultFunc("KUBE_LOAD_CONFIG_FILE", true), - Description: "Load local kubeconfig.", - }, "exec": { Type: schema.TypeList, Optional: true, @@ -270,40 +260,37 @@ func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, error) overrides := &clientcmd.ConfigOverrides{} loader := &clientcmd.ClientConfigLoadingRules{} - if d.Get("load_config_file").(bool) { - log.Printf("[DEBUG] Trying to load configuration from file") - if configPath, ok := d.GetOk("config_path"); ok && configPath.(string) != "" { - path, err := homedir.Expand(configPath.(string)) - if err != nil { - return nil, err + if configPath, ok := d.GetOk("config_path"); ok && configPath.(string) != "" { + path, err := homedir.Expand(configPath.(string)) + if err != nil { + return nil, err + } + log.Printf("[DEBUG] Configuration file is: %s", path) + loader.ExplicitPath = path + + ctxSuffix := "; default context" + + kubectx, ctxOk := d.GetOk("config_context") + authInfo, authInfoOk := d.GetOk("config_context_auth_info") + cluster, clusterOk := d.GetOk("config_context_cluster") + if ctxOk || authInfoOk || clusterOk { + ctxSuffix = "; overriden context" + if ctxOk { + overrides.CurrentContext = kubectx.(string) + ctxSuffix += fmt.Sprintf("; config ctx: %s", overrides.CurrentContext) + log.Printf("[DEBUG] Using custom current context: %q", overrides.CurrentContext) + } + + overrides.Context = clientcmdapi.Context{} + if authInfoOk { + overrides.Context.AuthInfo = authInfo.(string) + ctxSuffix += fmt.Sprintf("; auth_info: %s", overrides.Context.AuthInfo) } - log.Printf("[DEBUG] Configuration file is: %s", path) - loader.ExplicitPath = path - - ctxSuffix := "; default context" - - kubectx, ctxOk := d.GetOk("config_context") - authInfo, authInfoOk := d.GetOk("config_context_auth_info") - cluster, clusterOk := d.GetOk("config_context_cluster") - if ctxOk || authInfoOk || clusterOk { - ctxSuffix = "; overriden context" - if ctxOk { - overrides.CurrentContext = kubectx.(string) - ctxSuffix += fmt.Sprintf("; config ctx: %s", overrides.CurrentContext) - log.Printf("[DEBUG] Using custom current context: %q", overrides.CurrentContext) - } - - overrides.Context = clientcmdapi.Context{} - if authInfoOk { - overrides.Context.AuthInfo = authInfo.(string) - ctxSuffix += fmt.Sprintf("; auth_info: %s", overrides.Context.AuthInfo) - } - if clusterOk { - overrides.Context.Cluster = cluster.(string) - ctxSuffix += fmt.Sprintf("; cluster: %s", overrides.Context.Cluster) - } - log.Printf("[DEBUG] Using overidden context: %#v", overrides.Context) + if clusterOk { + overrides.Context.Cluster = cluster.(string) + ctxSuffix += fmt.Sprintf("; cluster: %s", overrides.Context.Cluster) } + log.Printf("[DEBUG] Using overidden context: %#v", overrides.Context) } } diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index 6fa0490e1e..b437a63c6d 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -15,6 +15,7 @@ Use the navigation to the left to read about the available resources. ```hcl provider "kubernetes" { + config_path = "~/.kube/config" config_context = "my-context" } @@ -131,12 +132,11 @@ The following arguments 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`. * `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`. * `token` - (Optional) Token of your service account. Can be sourced from `KUBE_TOKEN`. -* `load_config_file` - (Optional) By default the local config (~/.kube/config) is loaded when you use this provider. This option at false disables this behaviour which is desired when statically specifying the configuration or relying on in-cluster config. Can be sourced from `KUBE_LOAD_CONFIG_FILE`. * `exec` - (Optional) Configuration block to use an [exec-based credential plugin] (https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins), e.g. call an external command to receive user credentials. * `api_version` - (Required) API version to use when decoding the ExecCredentials resource, e.g. `client.authentication.k8s.io/v1beta1`. * `command` - (Required) Command to execute. From 5a16a65c5adf2414ba9f9f180d4ee66d0cfce44e Mon Sep 17 00:00:00 2001 From: John Houston Date: Fri, 13 Nov 2020 16:34:17 -0500 Subject: [PATCH 02/11] Change config_path -> config_paths --- kubernetes/provider.go | 35 ++++++++++++++++++++-------- kubernetes/provider_test.go | 39 +++++++++----------------------- website/docs/index.html.markdown | 16 ++++--------- 3 files changed, 40 insertions(+), 50 deletions(-) diff --git a/kubernetes/provider.go b/kubernetes/provider.go index a20c172cab..3245dbd647 100644 --- a/kubernetes/provider.go +++ b/kubernetes/provider.go @@ -6,6 +6,8 @@ import ( "fmt" "log" "net/http" + "os" + "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -68,11 +70,11 @@ func Provider() *schema.Provider { DefaultFunc: schema.EnvDefaultFunc("KUBE_CLUSTER_CA_CERT_DATA", ""), Description: "PEM-encoded root certificates bundle for TLS authentication.", }, - "config_path": { - Type: schema.TypeString, + "config_paths": { + Type: schema.TypeList, + Elem: &schema.Schema{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.", + Description: "A list of paths to kube config files. Can be set with KUBE_CONFIG_PATHS environment variable.", }, "config_context": { Type: schema.TypeString, @@ -260,13 +262,26 @@ func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, error) overrides := &clientcmd.ConfigOverrides{} loader := &clientcmd.ClientConfigLoadingRules{} - if configPath, ok := d.GetOk("config_path"); ok && configPath.(string) != "" { - path, err := homedir.Expand(configPath.(string)) - if err != nil { - return nil, err + configPaths := []string{} + if v, ok := d.Get("config_paths").([]string); ok && len(v) > 0 { + configPaths = v + } else if v := os.Getenv("KUBE_CONFIG_PATHS"); v != "" { + // NOTE we have to do this here because the schema + // does not yet allow you to set a default for a TypeList + configPaths = strings.Split(v, ":") + } + + if len(configPaths) > 0 { + expandedPaths := []string{} + for _, p := range configPaths { + path, err := homedir.Expand(p) + if err != nil { + return nil, err + } + log.Printf("[DEBUG] Using kubeconfig: %s", path) + expandedPaths = append(expandedPaths, path) } - log.Printf("[DEBUG] Configuration file is: %s", path) - loader.ExplicitPath = path + loader.Precedence = expandedPaths ctxSuffix := "; default context" diff --git a/kubernetes/provider_test.go b/kubernetes/provider_test.go index d07876c0fe..3015b250e2 100644 --- a/kubernetes/provider_test.go +++ b/kubernetes/provider_test.go @@ -4,11 +4,12 @@ import ( "context" "errors" "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "os" "strings" "testing" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + gversion "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -72,7 +73,7 @@ func TestProvider_configure(t *testing.T) { resetEnv := unsetEnv(t) defer resetEnv() - os.Setenv("KUBECONFIG", "test-fixtures/kube-config.yaml") + os.Setenv("KUBE_CONFIG_PATHS", "test-fixtures/kube-config.yaml") os.Setenv("KUBE_CTX", "gcp") rc := terraform.NewResourceConfigRaw(map[string]interface{}{}) @@ -86,11 +87,8 @@ func TestProvider_configure(t *testing.T) { func unsetEnv(t *testing.T) func() { e := getEnv() - if err := os.Unsetenv("KUBECONFIG"); err != nil { - t.Fatalf("Error unsetting env var KUBECONFIG: %s", err) - } - if err := os.Unsetenv("KUBE_CONFIG"); err != nil { - t.Fatalf("Error unsetting env var KUBE_CONFIG: %s", err) + if err := os.Unsetenv("KUBE_CONFIG_PATHS"); err != nil { + t.Fatalf("Error unsetting env var KUBE_CONFIG_PATHS: %s", err) } if err := os.Unsetenv("KUBE_CTX"); err != nil { t.Fatalf("Error unsetting env var KUBE_CTX: %s", err) @@ -122,19 +120,13 @@ func unsetEnv(t *testing.T) func() { if err := os.Unsetenv("KUBE_INSECURE"); err != nil { t.Fatalf("Error unsetting env var KUBE_INSECURE: %s", err) } - if err := os.Unsetenv("KUBE_LOAD_CONFIG_FILE"); err != nil { - t.Fatalf("Error unsetting env var KUBE_LOAD_CONFIG_FILE: %s", err) - } if err := os.Unsetenv("KUBE_TOKEN"); err != nil { t.Fatalf("Error unsetting env var KUBE_TOKEN: %s", err) } return func() { - if err := os.Setenv("KUBE_CONFIG", e.Config); err != nil { - t.Fatalf("Error resetting env var KUBE_CONFIG: %s", err) - } - if err := os.Setenv("KUBECONFIG", e.Config); err != nil { - t.Fatalf("Error resetting env var KUBECONFIG: %s", err) + if err := os.Setenv("KUBE_CONFIG_PATHS", strings.Join(e.ConfigPaths, ":")); err != nil { + t.Fatalf("Error resetting env var KUBE_CONFIG_PATHS: %s", err) } if err := os.Setenv("KUBE_CTX", e.Ctx); err != nil { t.Fatalf("Error resetting env var KUBE_CTX: %s", err) @@ -166,9 +158,6 @@ func unsetEnv(t *testing.T) func() { if err := os.Setenv("KUBE_INSECURE", e.Insecure); err != nil { t.Fatalf("Error resetting env var KUBE_INSECURE: %s", err) } - if err := os.Setenv("KUBE_LOAD_CONFIG_FILE", e.LoadConfigFile); err != nil { - t.Fatalf("Error resetting env var KUBE_LOAD_CONFIG_FILE: %s", err) - } if err := os.Setenv("KUBE_TOKEN", e.Token); err != nil { t.Fatalf("Error resetting env var KUBE_TOKEN: %s", err) } @@ -187,14 +176,10 @@ func getEnv() *currentEnv { ClientKeyData: os.Getenv("KUBE_CLIENT_KEY_DATA"), ClusterCACertData: os.Getenv("KUBE_CLUSTER_CA_CERT_DATA"), Insecure: os.Getenv("KUBE_INSECURE"), - LoadConfigFile: os.Getenv("KUBE_LOAD_CONFIG_FILE"), Token: os.Getenv("KUBE_TOKEN"), } - if cfg := os.Getenv("KUBE_CONFIG"); cfg != "" { - e.Config = cfg - } - if cfg := os.Getenv("KUBECONFIG"); cfg != "" { - e.Config = cfg + if v := os.Getenv("KUBE_CONFIG_PATHS"); v != "" { + e.ConfigPaths = strings.Split(v, ":") } return e } @@ -203,8 +188,7 @@ func testAccPreCheck(t *testing.T) { ctx := context.TODO() hasFileCfg := (os.Getenv("KUBE_CTX_AUTH_INFO") != "" && os.Getenv("KUBE_CTX_CLUSTER") != "") || os.Getenv("KUBE_CTX") != "" || - os.Getenv("KUBECONFIG") != "" || - os.Getenv("KUBE_CONFIG") != "" + os.Getenv("KUBE_CONFIG_PATHS") != "" hasUserCredentials := os.Getenv("KUBE_USER") != "" && os.Getenv("KUBE_PASSWORD") != "" hasClientCert := os.Getenv("KUBE_CLIENT_CERT_DATA") != "" && os.Getenv("KUBE_CLIENT_KEY_DATA") != "" hasStaticCfg := (os.Getenv("KUBE_HOST") != "" && @@ -440,7 +424,7 @@ func clusterVersionLessThan(vs string) bool { } type currentEnv struct { - Config string + ConfigPaths []string Ctx string CtxAuthInfo string CtxCluster string @@ -451,7 +435,6 @@ type currentEnv struct { ClientKeyData string ClusterCACertData string Insecure string - LoadConfigFile string Token string } diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index b437a63c6d..e9687eead0 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -15,7 +15,9 @@ Use the navigation to the left to read about the available resources. ```hcl provider "kubernetes" { - config_path = "~/.kube/config" + config_paths = [ + "~/.kube/config" + ] config_context = "my-context" } @@ -79,12 +81,6 @@ the provider will try to use the service account token from the `/var/run/secret Detection of in-cluster execution is based on the sole availability both of the `KUBERNETES_SERVICE_HOST` and `KUBERNETES_SERVICE_PORT` environment variables, with non empty values. -```hcl -provider "kubernetes" { - load_config_file = "false" -} -``` - If you have any other static configuration setting specified in a config file or static configuration, in-cluster service account token will not be tried. ### Statically defined credentials @@ -93,8 +89,6 @@ Another way is **statically** define TLS certificate credentials: ```hcl provider "kubernetes" { - load_config_file = "false" - host = "https://104.196.242.174" client_certificate = "${file("~/.kube/client-cert.pem")}" @@ -107,8 +101,6 @@ or username and password (HTTP Basic Authorization): ```hcl provider "kubernetes" { - load_config_file = "false" - host = "https://104.196.242.174" username = "username" @@ -132,7 +124,7 @@ The following arguments 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`. +* `config_paths` - (Optional) A list of paths to the kube config files. Can be sourced from `KUBE_CONFIG_PATHS`, which allows `:` to be used to delimit multiple 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`. From f82e5f229c868e19c9d5dba4782e531419d5c54f Mon Sep 17 00:00:00 2001 From: John Houston Date: Wed, 18 Nov 2020 17:51:50 -0500 Subject: [PATCH 03/11] Support config_path and config_paths --- kubernetes/provider.go | 22 ++++++- kubernetes/provider_test.go | 102 +++++++++---------------------- website/docs/index.html.markdown | 5 +- 3 files changed, 51 insertions(+), 78 deletions(-) diff --git a/kubernetes/provider.go b/kubernetes/provider.go index 3245dbd647..93f0727319 100644 --- a/kubernetes/provider.go +++ b/kubernetes/provider.go @@ -76,6 +76,12 @@ func Provider() *schema.Provider { 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.EnvDefaultFunc("KUBE_CONFIG_PATH", ""), + Description: "Path to the kube config file. Can be set with KUBE_CONFIG_PATH.", + }, "config_context": { Type: schema.TypeString, Optional: true, @@ -263,8 +269,13 @@ func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, error) loader := &clientcmd.ClientConfigLoadingRules{} configPaths := []string{} - if v, ok := d.Get("config_paths").([]string); ok && len(v) > 0 { - configPaths = v + + 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 != "" { // NOTE we have to do this here because the schema // does not yet allow you to set a default for a TypeList @@ -281,7 +292,12 @@ func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, error) log.Printf("[DEBUG] Using kubeconfig: %s", path) expandedPaths = append(expandedPaths, path) } - loader.Precedence = expandedPaths + + if len(expandedPaths) == 1 { + loader.ExplicitPath = expandedPaths[0] + } else { + loader.Precedence = expandedPaths + } ctxSuffix := "; default context" diff --git a/kubernetes/provider_test.go b/kubernetes/provider_test.go index 3015b250e2..55024a652c 100644 --- a/kubernetes/provider_test.go +++ b/kubernetes/provider_test.go @@ -73,7 +73,7 @@ func TestProvider_configure(t *testing.T) { resetEnv := unsetEnv(t) defer resetEnv() - os.Setenv("KUBE_CONFIG_PATHS", "test-fixtures/kube-config.yaml") + os.Setenv("KUBE_CONFIG_PATH", "test-fixtures/kube-config.yaml") os.Setenv("KUBE_CTX", "gcp") rc := terraform.NewResourceConfigRaw(map[string]interface{}{}) @@ -87,79 +87,33 @@ func TestProvider_configure(t *testing.T) { func unsetEnv(t *testing.T) func() { e := getEnv() - if err := os.Unsetenv("KUBE_CONFIG_PATHS"); err != nil { - t.Fatalf("Error unsetting env var KUBE_CONFIG_PATHS: %s", err) + envVars := map[string]string{ + "KUBE_CONFIG_PATH": e.ConfigPath, + "KUBE_CONFIG_PATHS": strings.Join(e.ConfigPaths, ":"), + "KUBE_CTX": e.Ctx, + "KUBE_CTX_AUTH_INFO": e.CtxAuthInfo, + "KUBE_CTX_CLUSTER": e.CtxCluster, + "KUBE_HOST": e.Host, + "KUBE_USER": e.User, + "KUBE_PASSWORD": e.Password, + "KUBE_CLIENT_CERT_DATA": e.ClientCertData, + "KUBE_CLIENT_KEY_DATA": e.ClientKeyData, + "KUBE_CLUSTER_CA_CERT_DATA": e.ClusterCACertData, + "KUBE_INSECURE": e.Insecure, + "KUBE_TOKEN": e.Token, } - if err := os.Unsetenv("KUBE_CTX"); err != nil { - t.Fatalf("Error unsetting env var KUBE_CTX: %s", err) - } - if err := os.Unsetenv("KUBE_CTX_AUTH_INFO"); err != nil { - t.Fatalf("Error unsetting env var KUBE_CTX_AUTH_INFO: %s", err) - } - if err := os.Unsetenv("KUBE_CTX_CLUSTER"); err != nil { - t.Fatalf("Error unsetting env var KUBE_CTX_CLUSTER: %s", err) - } - if err := os.Unsetenv("KUBE_HOST"); err != nil { - t.Fatalf("Error unsetting env var KUBE_HOST: %s", err) - } - if err := os.Unsetenv("KUBE_USER"); err != nil { - t.Fatalf("Error unsetting env var KUBE_USER: %s", err) - } - if err := os.Unsetenv("KUBE_PASSWORD"); err != nil { - t.Fatalf("Error unsetting env var KUBE_PASSWORD: %s", err) - } - if err := os.Unsetenv("KUBE_CLIENT_CERT_DATA"); err != nil { - t.Fatalf("Error unsetting env var KUBE_CLIENT_CERT_DATA: %s", err) - } - if err := os.Unsetenv("KUBE_CLIENT_KEY_DATA"); err != nil { - t.Fatalf("Error unsetting env var KUBE_CLIENT_KEY_DATA: %s", err) - } - if err := os.Unsetenv("KUBE_CLUSTER_CA_CERT_DATA"); err != nil { - t.Fatalf("Error unsetting env var KUBE_CLUSTER_CA_CERT_DATA: %s", err) - } - if err := os.Unsetenv("KUBE_INSECURE"); err != nil { - t.Fatalf("Error unsetting env var KUBE_INSECURE: %s", err) - } - if err := os.Unsetenv("KUBE_TOKEN"); err != nil { - t.Fatalf("Error unsetting env var KUBE_TOKEN: %s", err) + + for k, _ := range envVars { + if err := os.Unsetenv(k); err != nil { + t.Fatalf("Error unsetting env var %s: %s", k, err) + } } return func() { - if err := os.Setenv("KUBE_CONFIG_PATHS", strings.Join(e.ConfigPaths, ":")); err != nil { - t.Fatalf("Error resetting env var KUBE_CONFIG_PATHS: %s", err) - } - if err := os.Setenv("KUBE_CTX", e.Ctx); err != nil { - t.Fatalf("Error resetting env var KUBE_CTX: %s", err) - } - if err := os.Setenv("KUBE_CTX_AUTH_INFO", e.CtxAuthInfo); err != nil { - t.Fatalf("Error resetting env var KUBE_CTX_AUTH_INFO: %s", err) - } - if err := os.Setenv("KUBE_CTX_CLUSTER", e.CtxCluster); err != nil { - t.Fatalf("Error resetting env var KUBE_CTX_CLUSTER: %s", err) - } - if err := os.Setenv("KUBE_HOST", e.Host); err != nil { - t.Fatalf("Error resetting env var KUBE_HOST: %s", err) - } - if err := os.Setenv("KUBE_USER", e.User); err != nil { - t.Fatalf("Error resetting env var KUBE_USER: %s", err) - } - if err := os.Setenv("KUBE_PASSWORD", e.Password); err != nil { - t.Fatalf("Error resetting env var KUBE_PASSWORD: %s", err) - } - if err := os.Setenv("KUBE_CLIENT_CERT_DATA", e.ClientCertData); err != nil { - t.Fatalf("Error resetting env var KUBE_CLIENT_CERT_DATA: %s", err) - } - if err := os.Setenv("KUBE_CLIENT_KEY_DATA", e.ClientKeyData); err != nil { - t.Fatalf("Error resetting env var KUBE_CLIENT_KEY_DATA: %s", err) - } - if err := os.Setenv("KUBE_CLUSTER_CA_CERT_DATA", e.ClusterCACertData); err != nil { - t.Fatalf("Error resetting env var KUBE_CLUSTER_CA_CERT_DATA: %s", err) - } - if err := os.Setenv("KUBE_INSECURE", e.Insecure); err != nil { - t.Fatalf("Error resetting env var KUBE_INSECURE: %s", err) - } - if err := os.Setenv("KUBE_TOKEN", e.Token); err != nil { - t.Fatalf("Error resetting env var KUBE_TOKEN: %s", err) + for k, v := range envVars { + if err := os.Setenv(k, v); err != nil { + t.Fatalf("Error resetting env var %s: %s", k, err) + } } } } @@ -178,7 +132,10 @@ func getEnv() *currentEnv { Insecure: os.Getenv("KUBE_INSECURE"), Token: os.Getenv("KUBE_TOKEN"), } - if v := os.Getenv("KUBE_CONFIG_PATHS"); v != "" { + if v := os.Getenv("KUBE_CONFIG_PATH"); v != "" { + e.ConfigPath = v + } + if v := os.Getenv("KUBE_CONFIG_PATH"); v != "" { e.ConfigPaths = strings.Split(v, ":") } return e @@ -188,7 +145,7 @@ func testAccPreCheck(t *testing.T) { ctx := context.TODO() hasFileCfg := (os.Getenv("KUBE_CTX_AUTH_INFO") != "" && os.Getenv("KUBE_CTX_CLUSTER") != "") || os.Getenv("KUBE_CTX") != "" || - os.Getenv("KUBE_CONFIG_PATHS") != "" + os.Getenv("KUBE_CONFIG_PATH") != "" hasUserCredentials := os.Getenv("KUBE_USER") != "" && os.Getenv("KUBE_PASSWORD") != "" hasClientCert := os.Getenv("KUBE_CLIENT_CERT_DATA") != "" && os.Getenv("KUBE_CLIENT_KEY_DATA") != "" hasStaticCfg := (os.Getenv("KUBE_HOST") != "" && @@ -424,6 +381,7 @@ func clusterVersionLessThan(vs string) bool { } type currentEnv struct { + ConfigPath string ConfigPaths []string Ctx string CtxAuthInfo string diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index e9687eead0..b45c37b1f0 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -15,9 +15,7 @@ Use the navigation to the left to read about the available resources. ```hcl provider "kubernetes" { - config_paths = [ - "~/.kube/config" - ] + config_path = "~/.kube/config" config_context = "my-context" } @@ -124,6 +122,7 @@ The following arguments 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) A path to a kube config files. Can be sourced from `KUBE_CONFIG_PATH`. * `config_paths` - (Optional) A list of paths to the kube config files. Can be sourced from `KUBE_CONFIG_PATHS`, which allows `:` to be used to delimit multiple 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`. From 70c644e254e64e5904141bb7cf51ccd9499f604d Mon Sep 17 00:00:00 2001 From: John Houston Date: Fri, 20 Nov 2020 11:52:34 -0500 Subject: [PATCH 04/11] Use system path separator --- kubernetes/provider.go | 4 ++-- kubernetes/provider_test.go | 33 ++++++++++++++++---------------- website/docs/index.html.markdown | 2 +- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/kubernetes/provider.go b/kubernetes/provider.go index 93f0727319..18b507f9b4 100644 --- a/kubernetes/provider.go +++ b/kubernetes/provider.go @@ -7,7 +7,7 @@ import ( "log" "net/http" "os" - "strings" + "path/filepath" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -279,7 +279,7 @@ func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, error) } else if v := os.Getenv("KUBE_CONFIG_PATHS"); v != "" { // NOTE we have to do this here because the schema // does not yet allow you to set a default for a TypeList - configPaths = strings.Split(v, ":") + configPaths = filepath.SplitList(v) } if len(configPaths) > 0 { diff --git a/kubernetes/provider_test.go b/kubernetes/provider_test.go index 55024a652c..4998d95b76 100644 --- a/kubernetes/provider_test.go +++ b/kubernetes/provider_test.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "strings" "testing" @@ -88,32 +89,32 @@ func unsetEnv(t *testing.T) func() { e := getEnv() envVars := map[string]string{ - "KUBE_CONFIG_PATH": e.ConfigPath, - "KUBE_CONFIG_PATHS": strings.Join(e.ConfigPaths, ":"), - "KUBE_CTX": e.Ctx, - "KUBE_CTX_AUTH_INFO": e.CtxAuthInfo, - "KUBE_CTX_CLUSTER": e.CtxCluster, - "KUBE_HOST": e.Host, - "KUBE_USER": e.User, - "KUBE_PASSWORD": e.Password, - "KUBE_CLIENT_CERT_DATA": e.ClientCertData, - "KUBE_CLIENT_KEY_DATA": e.ClientKeyData, + "KUBE_CONFIG_PATH": e.ConfigPath, + "KUBE_CONFIG_PATHS": filepath.Join(e.ConfigPaths...), + "KUBE_CTX": e.Ctx, + "KUBE_CTX_AUTH_INFO": e.CtxAuthInfo, + "KUBE_CTX_CLUSTER": e.CtxCluster, + "KUBE_HOST": e.Host, + "KUBE_USER": e.User, + "KUBE_PASSWORD": e.Password, + "KUBE_CLIENT_CERT_DATA": e.ClientCertData, + "KUBE_CLIENT_KEY_DATA": e.ClientKeyData, "KUBE_CLUSTER_CA_CERT_DATA": e.ClusterCACertData, - "KUBE_INSECURE": e.Insecure, - "KUBE_TOKEN": e.Token, + "KUBE_INSECURE": e.Insecure, + "KUBE_TOKEN": e.Token, } for k, _ := range envVars { if err := os.Unsetenv(k); err != nil { t.Fatalf("Error unsetting env var %s: %s", k, err) - } + } } return func() { for k, v := range envVars { if err := os.Setenv(k, v); err != nil { t.Fatalf("Error resetting env var %s: %s", k, err) - } + } } } } @@ -136,7 +137,7 @@ func getEnv() *currentEnv { e.ConfigPath = v } if v := os.Getenv("KUBE_CONFIG_PATH"); v != "" { - e.ConfigPaths = strings.Split(v, ":") + e.ConfigPaths = filepath.SplitList(v) } return e } @@ -381,7 +382,7 @@ func clusterVersionLessThan(vs string) bool { } type currentEnv struct { - ConfigPath string + ConfigPath string ConfigPaths []string Ctx string CtxAuthInfo string diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index b45c37b1f0..4798c489e5 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -123,7 +123,7 @@ The following arguments are supported: * `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) A path to a kube config files. Can be sourced from `KUBE_CONFIG_PATH`. -* `config_paths` - (Optional) A list of paths to the kube config files. Can be sourced from `KUBE_CONFIG_PATHS`, which allows `:` to be used to delimit multiple paths. +* `config_paths` - (Optional) A list of paths to the 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`. From d7516bc5da2b5b6a8b16664eda700e7ec86e6bdb Mon Sep 17 00:00:00 2001 From: John Houston Date: Fri, 20 Nov 2020 15:17:10 -0500 Subject: [PATCH 05/11] Update website/docs/index.html.markdown Co-authored-by: Stef Forrester --- website/docs/index.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/index.html.markdown b/website/docs/index.html.markdown index 4798c489e5..f073f88a9d 100644 --- a/website/docs/index.html.markdown +++ b/website/docs/index.html.markdown @@ -122,7 +122,7 @@ The following arguments 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) A path to a kube config files. Can be sourced from `KUBE_CONFIG_PATH`. +* `config_path` - (Optional) A path to a kube config file. Can be sourced from `KUBE_CONFIG_PATH`. * `config_paths` - (Optional) A list of paths to the 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`. From 04fdfac6430dd54d4ea95a46428786242b833bc0 Mon Sep 17 00:00:00 2001 From: John Houston Date: Mon, 23 Nov 2020 15:28:49 -0500 Subject: [PATCH 06/11] Add ConflictsWith for config_path --- kubernetes/provider.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/kubernetes/provider.go b/kubernetes/provider.go index 18b507f9b4..e568657e57 100644 --- a/kubernetes/provider.go +++ b/kubernetes/provider.go @@ -77,10 +77,11 @@ func Provider() *schema.Provider { 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.EnvDefaultFunc("KUBE_CONFIG_PATH", ""), - Description: "Path to the kube config file. Can be set with KUBE_CONFIG_PATH.", + 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.", + ConflictsWith: []string{"config_paths"}, }, "config_context": { Type: schema.TypeString, From b0bfaf8fa922f5b00c633a2179e1fac76845b697 Mon Sep 17 00:00:00 2001 From: John Houston Date: Wed, 25 Nov 2020 17:40:19 -0500 Subject: [PATCH 07/11] Generate an error when the user does not supply any configuration --- kubernetes/provider.go | 37 +++++++++++- kubernetes/provider_test.go | 23 +++++++- .../test-fixtures/kube-config-secondary.yaml | 57 +++++++++++++++++++ 3 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 kubernetes/test-fixtures/kube-config-secondary.yaml diff --git a/kubernetes/provider.go b/kubernetes/provider.go index e568657e57..d3e0b99749 100644 --- a/kubernetes/provider.go +++ b/kubernetes/provider.go @@ -233,7 +233,43 @@ func (k kubeClientsets) AggregatorClientset() (*aggregator.Clientset, error) { return k.aggregatorClientset, nil } +func inCluster() bool { + host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT") + return host != "" && port != "" +} + +func checkConfigurationValid(d *schema.ResourceData) error { + if inCluster() { + log.Printf("[DEBUG] Terraform appears to be running inside the Kubernetes cluster") + return nil + } + + if os.Getenv("KUBE_CONFIG_PATHS") != "" { + return nil + } + + atLeastOneOf := []string{ + "host", + "config_path", + "config_paths", + "client_certificate", + "token", + "exec", + } + for _, a := range atLeastOneOf { + if _, ok := d.GetOk(a); ok { + return nil + } + } + + return fmt.Errorf(`provider not configured: you must configure a path to your kubeconfig +or explicitly supply credentials in the provider block`) +} + func providerConfigure(ctx context.Context, d *schema.ResourceData, terraformVersion string) (interface{}, diag.Diagnostics) { + if err := checkConfigurationValid(d); err != nil { + return nil, diag.FromErr(err) + } // Config initialization cfg, err := initializeConfiguration(d) @@ -386,7 +422,6 @@ func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, error) return nil, nil } - log.Printf("[INFO] Successfully initialized config") return cfg, nil } diff --git a/kubernetes/provider_test.go b/kubernetes/provider_test.go index 4998d95b76..cd9e9c4ca0 100644 --- a/kubernetes/provider_test.go +++ b/kubernetes/provider_test.go @@ -69,7 +69,7 @@ func TestProvider_impl(t *testing.T) { var _ schema.Provider = *Provider() } -func TestProvider_configure(t *testing.T) { +func TestProvider_configure_path(t *testing.T) { ctx := context.TODO() resetEnv := unsetEnv(t) defer resetEnv() @@ -85,12 +85,31 @@ func TestProvider_configure(t *testing.T) { } } +func TestProvider_configure_paths(t *testing.T) { + ctx := context.TODO() + resetEnv := unsetEnv(t) + defer resetEnv() + + os.Setenv("KUBE_CONFIG_PATHS", strings.Join([]string{ + "test-fixtures/kube-config.yaml", + "test-fixtures/kube-config-secondary.yaml", + }, string(os.PathListSeparator))) + os.Setenv("KUBE_CTX", "oidc") + + rc := terraform.NewResourceConfigRaw(map[string]interface{}{}) + p := Provider() + diags := p.Configure(ctx, rc) + if diags.HasError() { + t.Fatal(diags) + } +} + func unsetEnv(t *testing.T) func() { e := getEnv() envVars := map[string]string{ "KUBE_CONFIG_PATH": e.ConfigPath, - "KUBE_CONFIG_PATHS": filepath.Join(e.ConfigPaths...), + "KUBE_CONFIG_PATHS": strings.Join(e.ConfigPaths, string(os.PathListSeparator)), "KUBE_CTX": e.Ctx, "KUBE_CTX_AUTH_INFO": e.CtxAuthInfo, "KUBE_CTX_CLUSTER": e.CtxCluster, diff --git a/kubernetes/test-fixtures/kube-config-secondary.yaml b/kubernetes/test-fixtures/kube-config-secondary.yaml new file mode 100644 index 0000000000..366e6e6eeb --- /dev/null +++ b/kubernetes/test-fixtures/kube-config-secondary.yaml @@ -0,0 +1,57 @@ +apiVersion: v1 +kind: Config +preferences: {} +clusters: +- cluster: + certificate-authority-data: ZHVtbXk= + server: https://127.0.0.1 + name: default + +contexts: +- context: + cluster: default + user: azure + name: azure +- context: + cluster: default + user: gcp + name: gcp +- context: + cluster: default + user: oidc + name: oidc + +users: +- name: azure + user: + auth-provider: + config: + access-token: dummy + cmd-args: config config-helper --format=json + cmd-path: /usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/bin/gcloud + expiry: 2017-06-19T14:02:42Z + expiry-key: '{.credential.token_expiry}' + token-key: '{.credential.access_token}' + name: azure +- name: gcp + user: + auth-provider: + config: + access-token: dummy + cmd-args: config config-helper --format=json + cmd-path: /usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/bin/gcloud + expiry: 2017-06-19T14:02:42Z + expiry-key: '{.credential.token_expiry}' + token-key: '{.credential.access_token}' + name: gcp +- name: oidc + user: + auth-provider: + config: + access-token: dummy + cmd-args: config config-helper --format=json + cmd-path: /usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/bin/gcloud + expiry: 2017-06-19T14:02:42Z + expiry-key: '{.credential.token_expiry}' + token-key: '{.credential.access_token}' + name: oidc From 975f4928c421ef5afb9ce693c3c3a99be02d01df Mon Sep 17 00:00:00 2001 From: John Houston Date: Fri, 27 Nov 2020 13:53:46 -0500 Subject: [PATCH 08/11] Add better error message when provider is not configured Check kubeconfig files exist at configure time --- kubernetes/provider.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/kubernetes/provider.go b/kubernetes/provider.go index d3e0b99749..e904f3080b 100644 --- a/kubernetes/provider.go +++ b/kubernetes/provider.go @@ -79,7 +79,7 @@ func Provider() *schema.Provider { "config_path": { Type: schema.TypeString, Optional: true, - DefaultFunc: schema.EnvDefaultFunc("KUBE_CONFIG_PATH", ""), + DefaultFunc: schema.EnvDefaultFunc("KUBE_CONFIG_PATH", nil), Description: "Path to the kube config file. Can be set with KUBE_CONFIG_PATH.", ConflictsWith: []string{"config_paths"}, }, @@ -238,6 +238,8 @@ func inCluster() bool { return host != "" && port != "" } +var authDocumentationURL = "https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs#authentication" + func checkConfigurationValid(d *schema.ResourceData) error { if inCluster() { log.Printf("[DEBUG] Terraform appears to be running inside the Kubernetes cluster") @@ -263,7 +265,9 @@ func checkConfigurationValid(d *schema.ResourceData) error { } return fmt.Errorf(`provider not configured: you must configure a path to your kubeconfig -or explicitly supply credentials in the provider block`) +or explicitly supply credentials via the provider block or environment variables. + +See our documentation at: %s`, authDocumentationURL) } func providerConfigure(ctx context.Context, d *schema.ResourceData, terraformVersion string) (interface{}, diag.Diagnostics) { @@ -326,6 +330,10 @@ func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, error) if err != nil { return nil, err } + if _, err := os.Stat(path); err != nil { + return nil, fmt.Errorf("could not open kubeconfig %q: %v", p, err) + } + log.Printf("[DEBUG] Using kubeconfig: %s", path) expandedPaths = append(expandedPaths, path) } From 75377d45a3fb7a740a3b3527ca61dc6ae6a3ab8f Mon Sep 17 00:00:00 2001 From: John Houston Date: Thu, 17 Dec 2020 23:00:11 -0500 Subject: [PATCH 09/11] Add check for api token path --- kubernetes/provider.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/kubernetes/provider.go b/kubernetes/provider.go index e904f3080b..6946ff11af 100644 --- a/kubernetes/provider.go +++ b/kubernetes/provider.go @@ -233,9 +233,18 @@ func (k kubeClientsets) AggregatorClientset() (*aggregator.Clientset, error) { return k.aggregatorClientset, nil } +var apiTokenMountPath = "/var/run/secrets/kubernetes.io/serviceaccount" + func inCluster() bool { host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT") - return host != "" && port != "" + if host == "" || port == "" { + return false + } + + if _, err := os.Stat(apiTokenMountPath); err != nil { + return false + } + return true } var authDocumentationURL = "https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs#authentication" From b9bcdd2461450c76204e28c1df9b75aa6501343e Mon Sep 17 00:00:00 2001 From: John Houston Date: Mon, 21 Dec 2020 12:20:00 -0500 Subject: [PATCH 10/11] Defer checking valid configuration to avoid progressive apply issue --- kubernetes/provider.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/kubernetes/provider.go b/kubernetes/provider.go index 6946ff11af..b1d3824e18 100644 --- a/kubernetes/provider.go +++ b/kubernetes/provider.go @@ -203,12 +203,19 @@ type kubeClientsets struct { config *restclient.Config mainClientset *kubernetes.Clientset aggregatorClientset *aggregator.Clientset + + configData *schema.ResourceData } func (k kubeClientsets) MainClientset() (*kubernetes.Clientset, error) { if k.mainClientset != nil { return k.mainClientset, nil } + + if err := checkConfigurationValid(k.configData); err != nil { + return nil, err + } + if k.config != nil { kc, err := kubernetes.NewForConfig(k.config) if err != nil { @@ -280,10 +287,6 @@ See our documentation at: %s`, authDocumentationURL) } func providerConfigure(ctx context.Context, d *schema.ResourceData, terraformVersion string) (interface{}, diag.Diagnostics) { - if err := checkConfigurationValid(d); err != nil { - return nil, diag.FromErr(err) - } - // Config initialization cfg, err := initializeConfiguration(d) if err != nil { @@ -310,6 +313,7 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData, terraformVer config: cfg, mainClientset: nil, aggregatorClientset: nil, + configData: d, } return m, diag.Diagnostics{} } From f16553efd7f9235656f3f5e6cf8ab0e3e6bc0b43 Mon Sep 17 00:00:00 2001 From: John Houston Date: Mon, 21 Dec 2020 12:20:31 -0500 Subject: [PATCH 11/11] Fix crash in kubernetes_job waiter --- kubernetes/resource_kubernetes_job.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kubernetes/resource_kubernetes_job.go b/kubernetes/resource_kubernetes_job.go index fc75b46bb3..d83a364312 100644 --- a/kubernetes/resource_kubernetes_job.go +++ b/kubernetes/resource_kubernetes_job.go @@ -129,7 +129,9 @@ func resourceKubernetesJobUpdate(ctx context.Context, d *schema.ResourceData, me if d.Get("wait_for_completion").(bool) { err = resource.RetryContext(ctx, d.Timeout(schema.TimeoutUpdate), retryUntilJobIsFinished(ctx, conn, namespace, name)) - return diag.FromErr(err) + if err != nil { + return diag.FromErr(err) + } } return resourceKubernetesJobRead(ctx, d, meta) }