@@ -4,21 +4,20 @@ import (
44 "bytes"
55 "context"
66 "fmt"
7- "log"
8- "net/http"
9- "os"
10- "path/filepath"
11-
127 "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
13-
148 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging"
159 "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1611 "github.com/mitchellh/go-homedir"
1712 apimachineryschema "k8s.io/apimachinery/pkg/runtime/schema"
1813 "k8s.io/client-go/discovery"
1914 "k8s.io/client-go/kubernetes"
2015 _ "k8s.io/client-go/plugin/pkg/client/auth"
2116 restclient "k8s.io/client-go/rest"
17+ "log"
18+ "net/http"
19+ "os"
20+ "path/filepath"
2221
2322 "k8s.io/client-go/tools/clientcmd"
2423 clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
@@ -29,82 +28,117 @@ func Provider() *schema.Provider {
2928 p := & schema.Provider {
3029 Schema : map [string ]* schema.Schema {
3130 "host" : {
32- Type : schema .TypeString ,
33- Optional : true ,
34- DefaultFunc : schema .EnvDefaultFunc ("KUBE_HOST" , "" ),
35- Description : "The hostname (in form of URI) of Kubernetes master." ,
31+ Type : schema .TypeString ,
32+ Optional : true ,
33+ DefaultFunc : schema .EnvDefaultFunc ("KUBE_HOST" , nil ),
34+ Description : "The hostname (in form of URI) of Kubernetes master." ,
35+ ConflictsWith : []string {"config_path" , "config_paths" },
36+ ValidateDiagFunc : validation .ToDiagFunc (validation .IsURLWithHTTPorHTTPS ),
37+ // TODO: enable this when AtLeastOneOf works with optional attributes.
38+ // https://github.com/hashicorp/terraform-plugin-sdk/issues/705
39+ // AtLeastOneOf: []string{"token", "exec", "username", "password", "client_certificate", "client_key"},
3640 },
3741 "username" : {
38- Type : schema .TypeString ,
39- Optional : true ,
40- DefaultFunc : schema .EnvDefaultFunc ("KUBE_USER" , "" ),
41- Description : "The username to use for HTTP basic authentication when accessing the Kubernetes master endpoint." ,
42+ Type : schema .TypeString ,
43+ Optional : true ,
44+ DefaultFunc : schema .EnvDefaultFunc ("KUBE_USER" , nil ),
45+ Description : "The username to use for HTTP basic authentication when accessing the Kubernetes master endpoint." ,
46+ ConflictsWith : []string {"config_path" , "config_paths" , "exec" , "token" , "client_certificate" , "client_key" },
47+ RequiredWith : []string {"password" , "host" },
4248 },
4349 "password" : {
44- Type : schema .TypeString ,
45- Optional : true ,
46- DefaultFunc : schema .EnvDefaultFunc ("KUBE_PASSWORD" , "" ),
47- Description : "The password to use for HTTP basic authentication when accessing the Kubernetes master endpoint." ,
50+ Type : schema .TypeString ,
51+ Optional : true ,
52+ DefaultFunc : schema .EnvDefaultFunc ("KUBE_PASSWORD" , nil ),
53+ Description : "The password to use for HTTP basic authentication when accessing the Kubernetes master endpoint." ,
54+ ConflictsWith : []string {"config_path" , "config_paths" , "exec" , "token" , "client_certificate" , "client_key" },
55+ RequiredWith : []string {"username" , "host" },
4856 },
4957 "insecure" : {
50- Type : schema .TypeBool ,
51- Optional : true ,
52- DefaultFunc : schema .EnvDefaultFunc ("KUBE_INSECURE" , false ),
53- Description : "Whether server should be accessed without verifying the TLS certificate." ,
58+ Type : schema .TypeBool ,
59+ Optional : true ,
60+ DefaultFunc : schema .EnvDefaultFunc ("KUBE_INSECURE" , nil ),
61+ Description : "Whether server should be accessed without verifying the TLS certificate." ,
62+ ConflictsWith : []string {"cluster_ca_certificate" , "client_key" , "client_certificate" , "exec" },
5463 },
5564 "client_certificate" : {
56- Type : schema .TypeString ,
57- Optional : true ,
58- DefaultFunc : schema .EnvDefaultFunc ("KUBE_CLIENT_CERT_DATA" , "" ),
59- Description : "PEM-encoded client certificate for TLS authentication." ,
65+ Type : schema .TypeString ,
66+ Optional : true ,
67+ DefaultFunc : schema .EnvDefaultFunc ("KUBE_CLIENT_CERT_DATA" , nil ),
68+ Description : "PEM-encoded client certificate for TLS authentication." ,
69+ ConflictsWith : []string {"config_path" , "config_paths" , "username" , "password" , "insecure" },
70+ RequiredWith : []string {"client_key" , "cluster_ca_certificate" , "host" },
6071 },
6172 "client_key" : {
62- Type : schema .TypeString ,
63- Optional : true ,
64- DefaultFunc : schema .EnvDefaultFunc ("KUBE_CLIENT_KEY_DATA" , "" ),
65- Description : "PEM-encoded client certificate key for TLS authentication." ,
73+ Type : schema .TypeString ,
74+ Optional : true ,
75+ DefaultFunc : schema .EnvDefaultFunc ("KUBE_CLIENT_KEY_DATA" , nil ),
76+ Description : "PEM-encoded client certificate key for TLS authentication." ,
77+ ConflictsWith : []string {"config_path" , "config_paths" , "username" , "password" , "exec" , "insecure" },
78+ RequiredWith : []string {"client_certificate" , "cluster_ca_certificate" , "host" },
6679 },
6780 "cluster_ca_certificate" : {
68- Type : schema .TypeString ,
69- Optional : true ,
70- DefaultFunc : schema .EnvDefaultFunc ("KUBE_CLUSTER_CA_CERT_DATA" , "" ),
71- Description : "PEM-encoded root certificates bundle for TLS authentication." ,
81+ Type : schema .TypeString ,
82+ Optional : true ,
83+ DefaultFunc : schema .EnvDefaultFunc ("KUBE_CLUSTER_CA_CERT_DATA" , nil ),
84+ Description : "PEM-encoded root certificates bundle for TLS authentication." ,
85+ ConflictsWith : []string {"config_path" , "config_paths" , "insecure" },
86+ RequiredWith : []string {"host" },
87+ // TODO: enable this when AtLeastOneOf works with optional attributes.
88+ // https://github.com/hashicorp/terraform-plugin-sdk/issues/705
89+ // AtLeastOneOf: []string{"token", "exec", "client_certificate", "client_key"},
7290 },
7391 "config_paths" : {
7492 Type : schema .TypeList ,
7593 Elem : & schema.Schema {Type : schema .TypeString },
94+ DefaultFunc : configPathsEnv ,
7695 Optional : true ,
7796 Description : "A list of paths to kube config files. Can be set with KUBE_CONFIG_PATHS environment variable." ,
97+ // config_paths conflicts with every attribute except for "insecure", since all of these options will be read from the kubeconfig.
98+ ConflictsWith : []string {"config_path" , "exec" , "token" , "host" , "client_certificate" , "client_key" , "cluster_ca_certificate" , "username" , "password" },
7899 },
79100 "config_path" : {
80- Type : schema .TypeString ,
81- Optional : true ,
82- DefaultFunc : schema .EnvDefaultFunc ("KUBE_CONFIG_PATH" , nil ),
83- Description : "Path to the kube config file. Can be set with KUBE_CONFIG_PATH." ,
84- ConflictsWith : []string {"config_paths" },
85- },
86- "config_context" : {
87101 Type : schema .TypeString ,
88102 Optional : true ,
89- DefaultFunc : schema .EnvDefaultFunc ("KUBE_CTX" , "" ),
103+ DefaultFunc : schema .EnvDefaultFunc ("KUBE_CONFIG_PATH" , nil ),
104+ Description : "Path to the kube config file. Can be set with KUBE_CONFIG_PATH." ,
105+ // config_path conflicts with every attribute except for "insecure", since all of these options will be read from the kubeconfig.
106+ ConflictsWith : []string {"config_paths" , "exec" , "token" , "host" , "client_certificate" , "client_key" , "cluster_ca_certificate" , "username" , "password" },
107+ },
108+ "config_context" : {
109+ Type : schema .TypeString ,
110+ Optional : true ,
111+ DefaultFunc : schema .EnvDefaultFunc ("KUBE_CTX" , nil ),
112+ Description : "Context to choose from the kube config file. " ,
113+ ConflictsWith : []string {"exec" , "token" , "client_certificate" , "client_key" , "username" , "password" },
114+ // TODO: enable this when AtLeastOneOf works with optional attributes.
115+ // AtLeastOneOf: []string{"config_path", "config_paths"},
90116 },
91117 "config_context_auth_info" : {
92- Type : schema .TypeString ,
93- Optional : true ,
94- DefaultFunc : schema .EnvDefaultFunc ("KUBE_CTX_AUTH_INFO" , "" ),
95- Description : "" ,
118+ Type : schema .TypeString ,
119+ Optional : true ,
120+ DefaultFunc : schema .EnvDefaultFunc ("KUBE_CTX_AUTH_INFO" , nil ),
121+ Description : "Authentication info context of the kube config (name of the kubeconfig user, --user flag in kubectl)." ,
122+ ConflictsWith : []string {"exec" , "token" , "client_certificate" , "client_key" , "username" , "password" },
123+ // TODO: enable this when AtLeastOneOf works with optional attributes.
124+ // AtLeastOneOf: []string{"config_path", "config_paths"},
96125 },
97126 "config_context_cluster" : {
98- Type : schema .TypeString ,
99- Optional : true ,
100- DefaultFunc : schema .EnvDefaultFunc ("KUBE_CTX_CLUSTER" , "" ),
101- Description : "" ,
127+ Type : schema .TypeString ,
128+ Optional : true ,
129+ DefaultFunc : schema .EnvDefaultFunc ("KUBE_CTX_CLUSTER" , nil ),
130+ Description : "Cluster context of the kube config (name of the kubeconfig cluster, --cluster flag in kubectl)." ,
131+ ConflictsWith : []string {"exec" , "token" , "client_certificate" , "client_key" , "username" , "password" },
132+ // TODO: enable this when AtLeastOneOf works with optional attributes.
133+ // AtLeastOneOf: []string{"config_path", "config_paths"},
102134 },
103135 "token" : {
104- Type : schema .TypeString ,
105- Optional : true ,
106- DefaultFunc : schema .EnvDefaultFunc ("KUBE_TOKEN" , "" ),
107- Description : "Token to authenticate an service account" ,
136+ Type : schema .TypeString ,
137+ Optional : true ,
138+ DefaultFunc : schema .EnvDefaultFunc ("KUBE_TOKEN" , nil ),
139+ Description : "Bearer token for authenticating the Kubernetes API." ,
140+ ConflictsWith : []string {"config_path" , "config_paths" , "exec" , "client_certificate" , "client_key" , "username" , "password" },
141+ RequiredWith : []string {"host" },
108142 },
109143 "exec" : {
110144 Type : schema .TypeList ,
@@ -132,7 +166,9 @@ func Provider() *schema.Provider {
132166 },
133167 },
134168 },
135- Description : "" ,
169+ Description : "Configuration block to use an exec-based credential plugin, e.g. call an external command to receive user credentials." ,
170+ ConflictsWith : []string {"config_path" , "config_paths" , "token" , "client_certificate" , "client_key" , "username" , "password" , "insecure" },
171+ RequiredWith : []string {"host" , "cluster_ca_certificate" },
136172 },
137173 },
138174
@@ -194,6 +230,22 @@ func Provider() *schema.Provider {
194230 return p
195231}
196232
233+ // configPathsEnv fetches the value of the environment variable KUBE_CONFIG_PATHS, if defined.
234+ func configPathsEnv () (interface {}, error ) {
235+ value , exists := os .LookupEnv ("KUBE_CONFIG_PATHS" )
236+ if exists {
237+ log .Print ("[DEBUG] using environment variable KUBE_CONFIG_PATHS to define config_paths" )
238+ log .Printf ("[DEBUG] value of KUBE_CONFIG_PATHS: %v" , value )
239+ pathList := filepath .SplitList (value )
240+ configPaths := new ([]interface {})
241+ for _ , p := range pathList {
242+ * configPaths = append (* configPaths , p )
243+ }
244+ return * configPaths , nil
245+ }
246+ return nil , nil
247+ }
248+
197249type KubeClientsets interface {
198250 MainClientset () (* kubernetes.Clientset , error )
199251 AggregatorClientset () (* aggregator.Clientset , error )
@@ -212,6 +264,10 @@ func (k kubeClientsets) MainClientset() (*kubernetes.Clientset, error) {
212264 return k .mainClientset , nil
213265 }
214266
267+ if err := checkConfigurationValid (k .configData ); err != nil {
268+ return nil , err
269+ }
270+
215271 if k .config != nil {
216272 kc , err := kubernetes .NewForConfig (k .config )
217273 if err != nil {
@@ -236,6 +292,52 @@ func (k kubeClientsets) AggregatorClientset() (*aggregator.Clientset, error) {
236292 return k .aggregatorClientset , nil
237293}
238294
295+ var apiTokenMountPath = "/var/run/secrets/kubernetes.io/serviceaccount"
296+
297+ func inCluster () bool {
298+ host , port := os .Getenv ("KUBERNETES_SERVICE_HOST" ), os .Getenv ("KUBERNETES_SERVICE_PORT" )
299+ if host == "" || port == "" {
300+ return false
301+ }
302+
303+ if _ , err := os .Stat (apiTokenMountPath ); err != nil {
304+ return false
305+ }
306+ return true
307+ }
308+
309+ var authDocumentationURL = "https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs#authentication"
310+
311+ func checkConfigurationValid (d * schema.ResourceData ) error {
312+ if inCluster () {
313+ log .Printf ("[DEBUG] Terraform appears to be running inside the Kubernetes cluster" )
314+ return nil
315+ }
316+
317+ if os .Getenv ("KUBE_CONFIG_PATHS" ) != "" {
318+ return nil
319+ }
320+
321+ atLeastOneOf := []string {
322+ "host" ,
323+ "config_path" ,
324+ "config_paths" ,
325+ "client_certificate" ,
326+ "token" ,
327+ "exec" ,
328+ }
329+ for _ , a := range atLeastOneOf {
330+ if _ , ok := d .GetOk (a ); ok {
331+ return nil
332+ }
333+ }
334+
335+ return fmt .Errorf (`provider not configured: you must configure a path to your kubeconfig
336+ or explicitly supply credentials via the provider block or environment variables.
337+
338+ See our documentation at: %s` , authDocumentationURL )
339+ }
340+
239341func providerConfigure (ctx context.Context , d * schema.ResourceData , terraformVersion string ) (interface {}, diag.Diagnostics ) {
240342 // Config initialization
241343 cfg , err := initializeConfiguration (d )
@@ -270,7 +372,9 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData, terraformVer
270372
271373func initializeConfiguration (d * schema.ResourceData ) (* restclient.Config , error ) {
272374 overrides := & clientcmd.ConfigOverrides {}
273- loader := & clientcmd.ClientConfigLoadingRules {}
375+ loader := & clientcmd.ClientConfigLoadingRules {
376+ WarnIfAllMissing : true ,
377+ }
274378
275379 configPaths := []string {}
276380
@@ -280,10 +384,6 @@ func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, error)
280384 for _ , p := range v {
281385 configPaths = append (configPaths , p .(string ))
282386 }
283- } else if v := os .Getenv ("KUBE_CONFIG_PATHS" ); v != "" {
284- // NOTE we have to do this here because the schema
285- // does not yet allow you to set a default for a TypeList
286- configPaths = filepath .SplitList (v )
287387 }
288388
289389 if len (configPaths ) > 0 {
@@ -310,7 +410,7 @@ func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, error)
310410 authInfo , authInfoOk := d .GetOk ("config_context_auth_info" )
311411 cluster , clusterOk := d .GetOk ("config_context_cluster" )
312412 if ctxOk || authInfoOk || clusterOk {
313- ctxSuffix = "; overriden context"
413+ ctxSuffix = "; overridden context"
314414 if ctxOk {
315415 overrides .CurrentContext = kubectx .(string )
316416 ctxSuffix += fmt .Sprintf ("; config ctx: %s" , overrides .CurrentContext )
@@ -331,16 +431,16 @@ func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, error)
331431 }
332432
333433 // Overriding with static configuration
334- if v , ok := d .GetOk ("insecure" ); ok {
434+ if v , ok := d .GetOk ("insecure" ); ok && v != "" {
335435 overrides .ClusterInfo .InsecureSkipTLSVerify = v .(bool )
336436 }
337- if v , ok := d .GetOk ("cluster_ca_certificate" ); ok {
437+ if v , ok := d .GetOk ("cluster_ca_certificate" ); ok && v != "" {
338438 overrides .ClusterInfo .CertificateAuthorityData = bytes .NewBufferString (v .(string )).Bytes ()
339439 }
340- if v , ok := d .GetOk ("client_certificate" ); ok {
440+ if v , ok := d .GetOk ("client_certificate" ); ok && v != "" {
341441 overrides .AuthInfo .ClientCertificateData = bytes .NewBufferString (v .(string )).Bytes ()
342442 }
343- if v , ok := d .GetOk ("host" ); ok {
443+ if v , ok := d .GetOk ("host" ); ok && v != "" {
344444 // Server has to be the complete address of the kubernetes cluster (scheme://hostname:port), not just the hostname,
345445 // because `overrides` are processed too late to be taken into account by `defaultServerUrlFor()`.
346446 // This basically replicates what defaultServerUrlFor() does with config but for overrides,
@@ -355,16 +455,16 @@ func initializeConfiguration(d *schema.ResourceData) (*restclient.Config, error)
355455
356456 overrides .ClusterInfo .Server = host .String ()
357457 }
358- if v , ok := d .GetOk ("username" ); ok {
458+ if v , ok := d .GetOk ("username" ); ok && v != "" {
359459 overrides .AuthInfo .Username = v .(string )
360460 }
361- if v , ok := d .GetOk ("password" ); ok {
461+ if v , ok := d .GetOk ("password" ); ok && v != "" {
362462 overrides .AuthInfo .Password = v .(string )
363463 }
364- if v , ok := d .GetOk ("client_key" ); ok {
464+ if v , ok := d .GetOk ("client_key" ); ok && v != "" {
365465 overrides .AuthInfo .ClientKeyData = bytes .NewBufferString (v .(string )).Bytes ()
366466 }
367- if v , ok := d .GetOk ("token" ); ok {
467+ if v , ok := d .GetOk ("token" ); ok && v != "" {
368468 overrides .AuthInfo .Token = v .(string )
369469 }
370470
0 commit comments