Skip to content

Commit d16a009

Browse files
authored
feat(provider): allow to override Kubernetes parameters in client (argoproj-labs#100)
Signed-off-by: Raphaël Pinson <[email protected]>
1 parent ec966d2 commit d16a009

File tree

1 file changed

+224
-0
lines changed

1 file changed

+224
-0
lines changed

argocd/provider.go

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
package argocd
22

33
import (
4+
"bytes"
45
"context"
6+
"fmt"
7+
"log"
58
"sync"
69

710
"github.com/argoproj/argo-cd/v2/pkg/apiclient"
811
"github.com/argoproj/argo-cd/v2/pkg/apiclient/session"
912
"github.com/argoproj/argo-cd/v2/util/io"
1013
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
14+
"k8s.io/client-go/rest"
15+
"k8s.io/client-go/tools/clientcmd"
16+
17+
apimachineryschema "k8s.io/apimachinery/pkg/runtime/schema"
18+
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
1119
)
1220

1321
var apiClientConnOpts apiclient.ClientOptions
@@ -103,6 +111,13 @@ func Provider() *schema.Provider {
103111
Optional: true,
104112
DefaultFunc: schema.EnvDefaultFunc("ARGOCD_INSECURE", false),
105113
},
114+
"kubernetes": {
115+
Type: schema.TypeList,
116+
MaxItems: 1,
117+
Optional: true,
118+
Description: "Kubernetes configuration.",
119+
Elem: kubernetesResource(),
120+
},
106121
},
107122

108123
ResourcesMap: map[string]*schema.Resource{
@@ -160,6 +175,61 @@ func initApiClient(d *schema.ResourceData) (
160175
if v, ok := d.GetOk("headers"); ok {
161176
opts.Headers = v.([]string)
162177
}
178+
if _, ok := d.GetOk("kubernetes"); ok {
179+
opts.KubeOverrides = &clientcmd.ConfigOverrides{}
180+
if v, ok := k8sGetOk(d, "insecure"); ok {
181+
opts.KubeOverrides.ClusterInfo.InsecureSkipTLSVerify = v.(bool)
182+
}
183+
if v, ok := k8sGetOk(d, "cluster_ca_certificate"); ok {
184+
opts.KubeOverrides.ClusterInfo.CertificateAuthorityData = bytes.NewBufferString(v.(string)).Bytes()
185+
}
186+
if v, ok := k8sGetOk(d, "client_certificate"); ok {
187+
opts.KubeOverrides.AuthInfo.ClientCertificateData = bytes.NewBufferString(v.(string)).Bytes()
188+
}
189+
if v, ok := k8sGetOk(d, "host"); ok {
190+
// Server has to be the complete address of the kubernetes cluster (scheme://hostname:port), not just the hostname,
191+
// because `overrides` are processed too late to be taken into account by `defaultServerUrlFor()`.
192+
// This basically replicates what defaultServerUrlFor() does with config but for overrides,
193+
// see https://github.com/kubernetes/client-go/blob/v12.0.0/rest/url_utils.go#L85-L87
194+
hasCA := len(opts.KubeOverrides.ClusterInfo.CertificateAuthorityData) != 0
195+
hasCert := len(opts.KubeOverrides.AuthInfo.ClientCertificateData) != 0
196+
defaultTLS := hasCA || hasCert || opts.KubeOverrides.ClusterInfo.InsecureSkipTLSVerify
197+
host, _, err := rest.DefaultServerURL(v.(string), "", apimachineryschema.GroupVersion{}, defaultTLS)
198+
if err != nil {
199+
return nil, err
200+
}
201+
202+
opts.KubeOverrides.ClusterInfo.Server = host.String()
203+
}
204+
if v, ok := k8sGetOk(d, "username"); ok {
205+
opts.KubeOverrides.AuthInfo.Username = v.(string)
206+
}
207+
if v, ok := k8sGetOk(d, "password"); ok {
208+
opts.KubeOverrides.AuthInfo.Password = v.(string)
209+
}
210+
if v, ok := k8sGetOk(d, "client_key"); ok {
211+
opts.KubeOverrides.AuthInfo.ClientKeyData = bytes.NewBufferString(v.(string)).Bytes()
212+
}
213+
if v, ok := k8sGetOk(d, "token"); ok {
214+
opts.KubeOverrides.AuthInfo.Token = v.(string)
215+
}
216+
217+
if v, ok := k8sGetOk(d, "exec"); ok {
218+
exec := &clientcmdapi.ExecConfig{}
219+
if spec, ok := v.([]interface{})[0].(map[string]interface{}); ok {
220+
exec.APIVersion = spec["api_version"].(string)
221+
exec.Command = spec["command"].(string)
222+
exec.Args = expandStringSlice(spec["args"].([]interface{}))
223+
for kk, vv := range spec["env"].(map[string]interface{}) {
224+
exec.Env = append(exec.Env, clientcmdapi.ExecEnvVar{Name: kk, Value: vv.(string)})
225+
}
226+
} else {
227+
log.Printf("[ERROR] Failed to parse exec")
228+
return nil, fmt.Errorf("failed to parse exec")
229+
}
230+
opts.KubeOverrides.AuthInfo.Exec = exec
231+
}
232+
}
163233

164234
// Export provider API client connections options for use in other spawned api clients
165235
apiClientConnOpts = opts
@@ -194,3 +264,157 @@ func initApiClient(d *schema.ResourceData) (
194264
}
195265
return apiclient.NewClient(&opts)
196266
}
267+
268+
func kubernetesResource() *schema.Resource {
269+
return &schema.Resource{
270+
Schema: map[string]*schema.Schema{
271+
"host": {
272+
Type: schema.TypeString,
273+
Optional: true,
274+
DefaultFunc: schema.EnvDefaultFunc("KUBE_HOST", ""),
275+
Description: "The hostname (in form of URI) of Kubernetes master.",
276+
},
277+
"username": {
278+
Type: schema.TypeString,
279+
Optional: true,
280+
DefaultFunc: schema.EnvDefaultFunc("KUBE_USER", ""),
281+
Description: "The username to use for HTTP basic authentication when accessing the Kubernetes master endpoint.",
282+
},
283+
"password": {
284+
Type: schema.TypeString,
285+
Optional: true,
286+
DefaultFunc: schema.EnvDefaultFunc("KUBE_PASSWORD", ""),
287+
Description: "The password to use for HTTP basic authentication when accessing the Kubernetes master endpoint.",
288+
},
289+
"insecure": {
290+
Type: schema.TypeBool,
291+
Optional: true,
292+
DefaultFunc: schema.EnvDefaultFunc("KUBE_INSECURE", false),
293+
Description: "Whether server should be accessed without verifying the TLS certificate.",
294+
},
295+
"client_certificate": {
296+
Type: schema.TypeString,
297+
Optional: true,
298+
DefaultFunc: schema.EnvDefaultFunc("KUBE_CLIENT_CERT_DATA", ""),
299+
Description: "PEM-encoded client certificate for TLS authentication.",
300+
},
301+
"client_key": {
302+
Type: schema.TypeString,
303+
Optional: true,
304+
DefaultFunc: schema.EnvDefaultFunc("KUBE_CLIENT_KEY_DATA", ""),
305+
Description: "PEM-encoded client certificate key for TLS authentication.",
306+
},
307+
"cluster_ca_certificate": {
308+
Type: schema.TypeString,
309+
Optional: true,
310+
DefaultFunc: schema.EnvDefaultFunc("KUBE_CLUSTER_CA_CERT_DATA", ""),
311+
Description: "PEM-encoded root certificates bundle for TLS authentication.",
312+
},
313+
"config_paths": {
314+
Type: schema.TypeList,
315+
Elem: &schema.Schema{Type: schema.TypeString},
316+
Optional: true,
317+
Description: "A list of paths to kube config files. Can be set with KUBE_CONFIG_PATHS environment variable.",
318+
},
319+
"config_path": {
320+
Type: schema.TypeString,
321+
Optional: true,
322+
DefaultFunc: schema.EnvDefaultFunc("KUBE_CONFIG_PATH", nil),
323+
Description: "Path to the kube config file. Can be set with KUBE_CONFIG_PATH.",
324+
ConflictsWith: []string{"kubernetes.0.config_paths"},
325+
},
326+
"config_context": {
327+
Type: schema.TypeString,
328+
Optional: true,
329+
DefaultFunc: schema.EnvDefaultFunc("KUBE_CTX", ""),
330+
},
331+
"config_context_auth_info": {
332+
Type: schema.TypeString,
333+
Optional: true,
334+
DefaultFunc: schema.EnvDefaultFunc("KUBE_CTX_AUTH_INFO", ""),
335+
Description: "",
336+
},
337+
"config_context_cluster": {
338+
Type: schema.TypeString,
339+
Optional: true,
340+
DefaultFunc: schema.EnvDefaultFunc("KUBE_CTX_CLUSTER", ""),
341+
Description: "",
342+
},
343+
"token": {
344+
Type: schema.TypeString,
345+
Optional: true,
346+
DefaultFunc: schema.EnvDefaultFunc("KUBE_TOKEN", ""),
347+
Description: "Token to authenticate an service account",
348+
},
349+
"exec": {
350+
Type: schema.TypeList,
351+
Optional: true,
352+
MaxItems: 1,
353+
Elem: &schema.Resource{
354+
Schema: map[string]*schema.Schema{
355+
"api_version": {
356+
Type: schema.TypeString,
357+
Required: true,
358+
},
359+
"command": {
360+
Type: schema.TypeString,
361+
Required: true,
362+
},
363+
"env": {
364+
Type: schema.TypeMap,
365+
Optional: true,
366+
Elem: &schema.Schema{Type: schema.TypeString},
367+
},
368+
"args": {
369+
Type: schema.TypeList,
370+
Optional: true,
371+
Elem: &schema.Schema{Type: schema.TypeString},
372+
},
373+
},
374+
},
375+
Description: "",
376+
},
377+
},
378+
}
379+
}
380+
381+
func k8sGetOk(d *schema.ResourceData, key string) (interface{}, bool) {
382+
var k8sPrefix = "kubernetes.0."
383+
value, ok := d.GetOk(k8sPrefix + key)
384+
385+
// For boolean attributes the zero value is Ok
386+
switch value.(type) {
387+
case bool:
388+
// TODO: replace deprecated GetOkExists with SDK v2 equivalent
389+
// https://github.com/hashicorp/terraform-plugin-sdk/pull/350
390+
value, ok = d.GetOkExists(k8sPrefix + key)
391+
}
392+
393+
// fix: DefaultFunc is not being triggered on TypeList
394+
s := kubernetesResource().Schema[key]
395+
if !ok && s.DefaultFunc != nil {
396+
value, _ = s.DefaultFunc()
397+
398+
switch v := value.(type) {
399+
case string:
400+
ok = len(v) != 0
401+
case bool:
402+
ok = v
403+
}
404+
}
405+
406+
return value, ok
407+
}
408+
409+
func expandStringSlice(s []interface{}) []string {
410+
result := make([]string, len(s), len(s))
411+
for k, v := range s {
412+
// Handle the Terraform parser bug which turns empty strings in lists to nil.
413+
if v == nil {
414+
result[k] = ""
415+
} else {
416+
result[k] = v.(string)
417+
}
418+
}
419+
return result
420+
}

0 commit comments

Comments
 (0)