Skip to content

Commit

Permalink
feat(provider): allow to override Kubernetes parameters in client (#100)
Browse files Browse the repository at this point in the history
Signed-off-by: Raphaël Pinson <[email protected]>
  • Loading branch information
raphink authored Sep 28, 2021
1 parent ec966d2 commit d16a009
Showing 1 changed file with 224 additions and 0 deletions.
224 changes: 224 additions & 0 deletions argocd/provider.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
package argocd

import (
"bytes"
"context"
"fmt"
"log"
"sync"

"github.com/argoproj/argo-cd/v2/pkg/apiclient"
"github.com/argoproj/argo-cd/v2/pkg/apiclient/session"
"github.com/argoproj/argo-cd/v2/util/io"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"

apimachineryschema "k8s.io/apimachinery/pkg/runtime/schema"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)

var apiClientConnOpts apiclient.ClientOptions
Expand Down Expand Up @@ -103,6 +111,13 @@ func Provider() *schema.Provider {
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("ARGOCD_INSECURE", false),
},
"kubernetes": {
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Description: "Kubernetes configuration.",
Elem: kubernetesResource(),
},
},

ResourcesMap: map[string]*schema.Resource{
Expand Down Expand Up @@ -160,6 +175,61 @@ func initApiClient(d *schema.ResourceData) (
if v, ok := d.GetOk("headers"); ok {
opts.Headers = v.([]string)
}
if _, ok := d.GetOk("kubernetes"); ok {
opts.KubeOverrides = &clientcmd.ConfigOverrides{}
if v, ok := k8sGetOk(d, "insecure"); ok {
opts.KubeOverrides.ClusterInfo.InsecureSkipTLSVerify = v.(bool)
}
if v, ok := k8sGetOk(d, "cluster_ca_certificate"); ok {
opts.KubeOverrides.ClusterInfo.CertificateAuthorityData = bytes.NewBufferString(v.(string)).Bytes()
}
if v, ok := k8sGetOk(d, "client_certificate"); ok {
opts.KubeOverrides.AuthInfo.ClientCertificateData = bytes.NewBufferString(v.(string)).Bytes()
}
if v, ok := k8sGetOk(d, "host"); ok {
// Server has to be the complete address of the kubernetes cluster (scheme://hostname:port), not just the hostname,
// because `overrides` are processed too late to be taken into account by `defaultServerUrlFor()`.
// This basically replicates what defaultServerUrlFor() does with config but for overrides,
// see https://github.com/kubernetes/client-go/blob/v12.0.0/rest/url_utils.go#L85-L87
hasCA := len(opts.KubeOverrides.ClusterInfo.CertificateAuthorityData) != 0
hasCert := len(opts.KubeOverrides.AuthInfo.ClientCertificateData) != 0
defaultTLS := hasCA || hasCert || opts.KubeOverrides.ClusterInfo.InsecureSkipTLSVerify
host, _, err := rest.DefaultServerURL(v.(string), "", apimachineryschema.GroupVersion{}, defaultTLS)
if err != nil {
return nil, err
}

opts.KubeOverrides.ClusterInfo.Server = host.String()
}
if v, ok := k8sGetOk(d, "username"); ok {
opts.KubeOverrides.AuthInfo.Username = v.(string)
}
if v, ok := k8sGetOk(d, "password"); ok {
opts.KubeOverrides.AuthInfo.Password = v.(string)
}
if v, ok := k8sGetOk(d, "client_key"); ok {
opts.KubeOverrides.AuthInfo.ClientKeyData = bytes.NewBufferString(v.(string)).Bytes()
}
if v, ok := k8sGetOk(d, "token"); ok {
opts.KubeOverrides.AuthInfo.Token = v.(string)
}

if v, ok := k8sGetOk(d, "exec"); ok {
exec := &clientcmdapi.ExecConfig{}
if spec, ok := v.([]interface{})[0].(map[string]interface{}); ok {
exec.APIVersion = spec["api_version"].(string)
exec.Command = spec["command"].(string)
exec.Args = expandStringSlice(spec["args"].([]interface{}))
for kk, vv := range spec["env"].(map[string]interface{}) {
exec.Env = append(exec.Env, clientcmdapi.ExecEnvVar{Name: kk, Value: vv.(string)})
}
} else {
log.Printf("[ERROR] Failed to parse exec")
return nil, fmt.Errorf("failed to parse exec")
}
opts.KubeOverrides.AuthInfo.Exec = exec
}
}

// Export provider API client connections options for use in other spawned api clients
apiClientConnOpts = opts
Expand Down Expand Up @@ -194,3 +264,157 @@ func initApiClient(d *schema.ResourceData) (
}
return apiclient.NewClient(&opts)
}

func kubernetesResource() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"host": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_HOST", ""),
Description: "The hostname (in form of URI) of Kubernetes master.",
},
"username": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_USER", ""),
Description: "The username to use for HTTP basic authentication when accessing the Kubernetes master endpoint.",
},
"password": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_PASSWORD", ""),
Description: "The password to use for HTTP basic authentication when accessing the Kubernetes master endpoint.",
},
"insecure": {
Type: schema.TypeBool,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_INSECURE", false),
Description: "Whether server should be accessed without verifying the TLS certificate.",
},
"client_certificate": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_CLIENT_CERT_DATA", ""),
Description: "PEM-encoded client certificate for TLS authentication.",
},
"client_key": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_CLIENT_KEY_DATA", ""),
Description: "PEM-encoded client certificate key for TLS authentication.",
},
"cluster_ca_certificate": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_CLUSTER_CA_CERT_DATA", ""),
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": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_CONFIG_PATH", nil),
Description: "Path to the kube config file. Can be set with KUBE_CONFIG_PATH.",
ConflictsWith: []string{"kubernetes.0.config_paths"},
},
"config_context": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_CTX", ""),
},
"config_context_auth_info": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_CTX_AUTH_INFO", ""),
Description: "",
},
"config_context_cluster": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_CTX_CLUSTER", ""),
Description: "",
},
"token": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("KUBE_TOKEN", ""),
Description: "Token to authenticate an service account",
},
"exec": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"api_version": {
Type: schema.TypeString,
Required: true,
},
"command": {
Type: schema.TypeString,
Required: true,
},
"env": {
Type: schema.TypeMap,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"args": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
Description: "",
},
},
}
}

func k8sGetOk(d *schema.ResourceData, key string) (interface{}, bool) {
var k8sPrefix = "kubernetes.0."
value, ok := d.GetOk(k8sPrefix + key)

// For boolean attributes the zero value is Ok
switch value.(type) {
case bool:
// TODO: replace deprecated GetOkExists with SDK v2 equivalent
// https://github.com/hashicorp/terraform-plugin-sdk/pull/350
value, ok = d.GetOkExists(k8sPrefix + key)
}

// fix: DefaultFunc is not being triggered on TypeList
s := kubernetesResource().Schema[key]
if !ok && s.DefaultFunc != nil {
value, _ = s.DefaultFunc()

switch v := value.(type) {
case string:
ok = len(v) != 0
case bool:
ok = v
}
}

return value, ok
}

func expandStringSlice(s []interface{}) []string {
result := make([]string, len(s), len(s))
for k, v := range s {
// Handle the Terraform parser bug which turns empty strings in lists to nil.
if v == nil {
result[k] = ""
} else {
result[k] = v.(string)
}
}
return result
}

0 comments on commit d16a009

Please sign in to comment.