Skip to content

Commit 697873b

Browse files
authored
Synchronize provider configuration with kubernetes provider (#107)
1 parent 6011af6 commit 697873b

2 files changed

Lines changed: 152 additions & 86 deletions

File tree

docs/index.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,10 @@ then either place it at the root of your Terraform folder or in the Terraform pl
5757

5858
## Configuration
5959

60-
The provider supports the same configuration parameters as the [Kubernetes Terraform Provider](https://www.terraform.io/docs/providers/kubernetes/index.html)
60+
The provider supports the same configuration parameters as the [Kubernetes Terraform Provider](https://www.terraform.io/docs/providers/kubernetes/index.html),
61+
with the addition of `load_config_file` and `apply_retry_count`.
62+
63+
> Note: Unlike the Terraform Kubernetes Provider, this provider will load the `KUBECONFIG` file if the environment variable is set.
6164
6265
```hcl
6366
provider "kubectl" {
@@ -68,6 +71,31 @@ provider "kubectl" {
6871
}
6972
```
7073

74+
### Argument Reference
75+
76+
The following arguments are supported:
77+
78+
* `apply_retry_count` - (Optional) Defines the number of attempts any create/update action will take. Default `1`.
79+
* `load_config_file` - (Optional) Flag to enable/disable loading of the local kubeconf file. Default `true`. Can be sourced from `KUBE_LOAD_CONFIG_FILE`.
80+
* `host` - (Optional) The hostname (in form of URI) of the Kubernetes API. Can be sourced from `KUBE_HOST`.
81+
* `username` - (Optional) The username to use for HTTP basic authentication when accessing the Kubernetes API. Can be sourced from `KUBE_USER`.
82+
* `password` - (Optional) The password to use for HTTP basic authentication when accessing the Kubernetes API. Can be sourced from `KUBE_PASSWORD`.
83+
* `insecure` - (Optional) Whether the server should be accessed without verifying the TLS certificate. Can be sourced from `KUBE_INSECURE`. Defaults to `false`.
84+
* `client_certificate` - (Optional) PEM-encoded client certificate for TLS authentication. Can be sourced from `KUBE_CLIENT_CERT_DATA`.
85+
* `client_key` - (Optional) PEM-encoded client certificate key for TLS authentication. Can be sourced from `KUBE_CLIENT_KEY_DATA`.
86+
* `cluster_ca_certificate` - (Optional) PEM-encoded root certificates bundle for TLS authentication. Can be sourced from `KUBE_CLUSTER_CA_CERT_DATA`.
87+
* `config_path` - (Optional) A path to a kube config file. Can be sourced from `KUBE_CONFIG_PATH` or `KUBECONFIG`.
88+
* `config_paths` - (Optional) A list of paths to the kube config files. Can be sourced from `KUBE_CONFIG_PATHS`.
89+
* `config_context` - (Optional) Context to choose from the config file. Can be sourced from `KUBE_CTX`.
90+
* `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`.
91+
* `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`.
92+
* `token` - (Optional) Token of your service account. Can be sourced from `KUBE_TOKEN`.
93+
* `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.
94+
* `api_version` - (Required) API version to use when decoding the ExecCredentials resource, e.g. `client.authentication.k8s.io/v1beta1`.
95+
* `command` - (Required) Command to execute.
96+
* `args` - (Optional) List of arguments to pass when executing the plugin.
97+
* `env` - (Optional) Map of environment variables to set when executing the plugin.
98+
7199
### Exec Plugin Support
72100

73101
As with the Kubernetes Terraform Provider, this provider also supports using a `exec` based plugin (for example when running on EKS).

kubernetes/provider.go

Lines changed: 123 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
99
"github.com/mitchellh/go-homedir"
1010
"k8s.io/apimachinery/pkg/api/meta"
11+
apimachineryschema "k8s.io/apimachinery/pkg/runtime/schema"
1112
k8sresource "k8s.io/cli-runtime/pkg/resource"
1213
"k8s.io/client-go/discovery"
1314
diskcached "k8s.io/client-go/discovery/cached/disk"
@@ -78,13 +79,20 @@ func Provider() *schema.Provider {
7879
DefaultFunc: schema.EnvDefaultFunc("KUBE_CLUSTER_CA_CERT_DATA", ""),
7980
Description: "PEM-encoded root certificates bundle for TLS authentication.",
8081
},
82+
"config_paths": {
83+
Type: schema.TypeList,
84+
Elem: &schema.Schema{Type: schema.TypeString},
85+
Optional: true,
86+
Description: "A list of paths to kube config files. Can be set with KUBE_CONFIG_PATHS environment variable.",
87+
},
8188
"config_path": {
8289
Type: schema.TypeString,
8390
Optional: true,
8491
DefaultFunc: schema.MultiEnvDefaultFunc(
8592
[]string{
8693
"KUBE_CONFIG",
8794
"KUBECONFIG",
95+
"KUBE_CONFIG_PATH",
8896
},
8997
"~/.kube/config"),
9098
Description: "Path to the kube config file, defaults to ~/.kube/config",
@@ -213,11 +221,13 @@ var kubectlApplyRetryCount uint64
213221

214222
func providerConfigure(d *schema.ResourceData, terraformVersion string) (interface{}, diag.Diagnostics) {
215223

216-
var cfg *restclient.Config
217-
var err error
218-
if d.Get("load_config_file").(bool) {
219-
// Config file loading
220-
cfg, err = tryLoadingConfigFile(d)
224+
cfg, err := initializeConfiguration(d)
225+
if err != nil {
226+
return nil, diag.FromErr(err)
227+
}
228+
229+
if cfg == nil {
230+
cfg = &restclient.Config{}
221231
}
222232

223233
kubectlApplyRetryCount = uint64(d.Get("apply_retry_count").(int))
@@ -226,59 +236,12 @@ func providerConfigure(d *schema.ResourceData, terraformVersion string) (interfa
226236
kubectlApplyRetryCount = uint64(applyEnvValue)
227237
}
228238

229-
if err != nil {
230-
return nil, diag.FromErr(err)
231-
}
232-
if cfg == nil {
233-
cfg = &restclient.Config{}
234-
}
235-
236239
cfg.QPS = 100.0
237240
cfg.Burst = 100
238241

239242
// Overriding with static configuration
240243
cfg.UserAgent = fmt.Sprintf("HashiCorp/1.0 Terraform/%s", terraformVersion)
241244

242-
if v, ok := d.GetOk("host"); ok {
243-
cfg.Host = v.(string)
244-
}
245-
if v, ok := d.GetOk("username"); ok {
246-
cfg.Username = v.(string)
247-
}
248-
if v, ok := d.GetOk("password"); ok {
249-
cfg.Password = v.(string)
250-
}
251-
if v, ok := d.GetOk("insecure"); ok {
252-
cfg.Insecure = v.(bool)
253-
}
254-
if v, ok := d.GetOk("cluster_ca_certificate"); ok {
255-
cfg.CAData = bytes.NewBufferString(v.(string)).Bytes()
256-
}
257-
if v, ok := d.GetOk("client_certificate"); ok {
258-
cfg.CertData = bytes.NewBufferString(v.(string)).Bytes()
259-
}
260-
if v, ok := d.GetOk("client_key"); ok {
261-
cfg.KeyData = bytes.NewBufferString(v.(string)).Bytes()
262-
}
263-
if v, ok := d.GetOk("token"); ok {
264-
cfg.BearerToken = v.(string)
265-
}
266-
267-
if v, ok := d.GetOk("exec"); ok {
268-
exec := &clientcmdapi.ExecConfig{}
269-
if spec, ok := v.([]interface{})[0].(map[string]interface{}); ok {
270-
exec.APIVersion = spec["api_version"].(string)
271-
exec.Command = spec["command"].(string)
272-
exec.Args = expandStringSlice(spec["args"].([]interface{}))
273-
for kk, vv := range spec["env"].(map[string]interface{}) {
274-
exec.Env = append(exec.Env, clientcmdapi.ExecEnvVar{Name: kk, Value: vv.(string)})
275-
}
276-
} else {
277-
return nil, diag.FromErr(fmt.Errorf("failed to parse exec"))
278-
}
279-
cfg.ExecProvider = exec
280-
}
281-
282245
k, err := kubernetes.NewForConfig(cfg)
283246
if err != nil {
284247
return nil, diag.FromErr(fmt.Errorf("failed to configure: %s", err))
@@ -298,53 +261,128 @@ func providerConfigure(d *schema.ResourceData, terraformVersion string) (interfa
298261
}, nil
299262
}
300263

301-
func tryLoadingConfigFile(d *schema.ResourceData) (*restclient.Config, error) {
302-
path, err := homedir.Expand(d.Get("config_path").(string))
303-
if err != nil {
304-
return nil, err
305-
}
264+
func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, error) {
265+
overrides := &clientcmd.ConfigOverrides{}
266+
loader := &clientcmd.ClientConfigLoadingRules{}
267+
268+
configPaths := []string{}
306269

307-
loader := &clientcmd.ClientConfigLoadingRules{
308-
ExplicitPath: path,
270+
if v, ok := d.Get("config_path").(string); ok && v != "" {
271+
configPaths = []string{v}
272+
} else if v, ok := d.Get("config_paths").([]interface{}); ok && len(v) > 0 {
273+
for _, p := range v {
274+
configPaths = append(configPaths, p.(string))
275+
}
276+
} else if v := os.Getenv("KUBE_CONFIG_PATHS"); v != "" {
277+
// NOTE we have to do this here because the schema
278+
// does not yet allow you to set a default for a TypeList
279+
configPaths = filepath.SplitList(v)
309280
}
310281

311-
overrides := &clientcmd.ConfigOverrides{}
312-
ctxSuffix := "; default context"
313-
314-
ctx, ctxOk := d.GetOk("config_context")
315-
authInfo, authInfoOk := d.GetOk("config_context_auth_info")
316-
cluster, clusterOk := d.GetOk("config_context_cluster")
317-
if ctxOk || authInfoOk || clusterOk {
318-
ctxSuffix = "; overriden context"
319-
if ctxOk {
320-
overrides.CurrentContext = ctx.(string)
321-
ctxSuffix += fmt.Sprintf("; config ctx: %s", overrides.CurrentContext)
322-
log.Printf("[DEBUG] Using custom current context: %q", overrides.CurrentContext)
282+
if d.Get("load_config_file").(bool) && len(configPaths) > 0 {
283+
expandedPaths := []string{}
284+
for _, p := range configPaths {
285+
path, err := homedir.Expand(p)
286+
if err != nil {
287+
return nil, err
288+
}
289+
290+
log.Printf("[DEBUG] Using kubeconfig: %s", path)
291+
expandedPaths = append(expandedPaths, path)
323292
}
324293

325-
overrides.Context = clientcmdapi.Context{}
326-
if authInfoOk {
327-
overrides.Context.AuthInfo = authInfo.(string)
328-
ctxSuffix += fmt.Sprintf("; auth_info: %s", overrides.Context.AuthInfo)
294+
if len(expandedPaths) == 1 {
295+
loader.ExplicitPath = expandedPaths[0]
296+
} else {
297+
loader.Precedence = expandedPaths
298+
}
299+
300+
ctxSuffix := "; default context"
301+
302+
kubectx, ctxOk := d.GetOk("config_context")
303+
authInfo, authInfoOk := d.GetOk("config_context_auth_info")
304+
cluster, clusterOk := d.GetOk("config_context_cluster")
305+
if ctxOk || authInfoOk || clusterOk {
306+
ctxSuffix = "; overriden context"
307+
if ctxOk {
308+
overrides.CurrentContext = kubectx.(string)
309+
ctxSuffix += fmt.Sprintf("; config ctx: %s", overrides.CurrentContext)
310+
log.Printf("[DEBUG] Using custom current context: %q", overrides.CurrentContext)
311+
}
312+
313+
overrides.Context = clientcmdapi.Context{}
314+
if authInfoOk {
315+
overrides.Context.AuthInfo = authInfo.(string)
316+
ctxSuffix += fmt.Sprintf("; auth_info: %s", overrides.Context.AuthInfo)
317+
}
318+
if clusterOk {
319+
overrides.Context.Cluster = cluster.(string)
320+
ctxSuffix += fmt.Sprintf("; cluster: %s", overrides.Context.Cluster)
321+
}
322+
log.Printf("[DEBUG] Using overidden context: %#v", overrides.Context)
323+
}
324+
}
325+
326+
// Overriding with static configuration
327+
if v, ok := d.GetOk("insecure"); ok {
328+
overrides.ClusterInfo.InsecureSkipTLSVerify = v.(bool)
329+
}
330+
if v, ok := d.GetOk("cluster_ca_certificate"); ok {
331+
overrides.ClusterInfo.CertificateAuthorityData = bytes.NewBufferString(v.(string)).Bytes()
332+
}
333+
if v, ok := d.GetOk("client_certificate"); ok {
334+
overrides.AuthInfo.ClientCertificateData = bytes.NewBufferString(v.(string)).Bytes()
335+
}
336+
if v, ok := d.GetOk("host"); ok {
337+
// Server has to be the complete address of the kubernetes cluster (scheme://hostname:port), not just the hostname,
338+
// because `overrides` are processed too late to be taken into account by `defaultServerUrlFor()`.
339+
// This basically replicates what defaultServerUrlFor() does with config but for overrides,
340+
// see https://github.com/kubernetes/client-go/blob/v12.0.0/rest/url_utils.go#L85-L87
341+
hasCA := len(overrides.ClusterInfo.CertificateAuthorityData) != 0
342+
hasCert := len(overrides.AuthInfo.ClientCertificateData) != 0
343+
defaultTLS := hasCA || hasCert || overrides.ClusterInfo.InsecureSkipTLSVerify
344+
host, _, err := restclient.DefaultServerURL(v.(string), "", apimachineryschema.GroupVersion{}, defaultTLS)
345+
if err != nil {
346+
return nil, fmt.Errorf("Failed to parse host: %s", err)
329347
}
330-
if clusterOk {
331-
overrides.Context.Cluster = cluster.(string)
332-
ctxSuffix += fmt.Sprintf("; cluster: %s", overrides.Context.Cluster)
348+
349+
overrides.ClusterInfo.Server = host.String()
350+
}
351+
if v, ok := d.GetOk("username"); ok {
352+
overrides.AuthInfo.Username = v.(string)
353+
}
354+
if v, ok := d.GetOk("password"); ok {
355+
overrides.AuthInfo.Password = v.(string)
356+
}
357+
if v, ok := d.GetOk("client_key"); ok {
358+
overrides.AuthInfo.ClientKeyData = bytes.NewBufferString(v.(string)).Bytes()
359+
}
360+
if v, ok := d.GetOk("token"); ok {
361+
overrides.AuthInfo.Token = v.(string)
362+
}
363+
364+
if v, ok := d.GetOk("exec"); ok {
365+
exec := &clientcmdapi.ExecConfig{}
366+
if spec, ok := v.([]interface{})[0].(map[string]interface{}); ok {
367+
exec.APIVersion = spec["api_version"].(string)
368+
exec.Command = spec["command"].(string)
369+
exec.Args = expandStringSlice(spec["args"].([]interface{}))
370+
for kk, vv := range spec["env"].(map[string]interface{}) {
371+
exec.Env = append(exec.Env, clientcmdapi.ExecEnvVar{Name: kk, Value: vv.(string)})
372+
}
373+
} else {
374+
return nil, fmt.Errorf("Failed to parse exec")
333375
}
334-
log.Printf("[DEBUG] Using overidden context: %#v", overrides.Context)
376+
overrides.AuthInfo.Exec = exec
335377
}
336378

337379
cc := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loader, overrides)
338380
cfg, err := cc.ClientConfig()
339381
if err != nil {
340-
if pathErr, ok := err.(*os.PathError); ok && os.IsNotExist(pathErr.Err) {
341-
log.Printf("[INFO] Unable to load config file as it doesn't exist at %q", path)
342-
return nil, nil
343-
}
344-
return nil, fmt.Errorf("failed to load config (%s%s): %s", path, ctxSuffix, err)
382+
log.Printf("[WARN] Invalid provider configuration was supplied. Provider operations likely to fail: %v", err)
383+
return nil, nil
345384
}
346385

347-
log.Printf("[INFO] Successfully loaded config file (%s%s)", path, ctxSuffix)
348386
return cfg, nil
349387
}
350388

0 commit comments

Comments
 (0)