|
1 | 1 | package argocd
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "bytes" |
4 | 5 | "context"
|
| 6 | + "fmt" |
| 7 | + "log" |
5 | 8 | "sync"
|
6 | 9 |
|
7 | 10 | "github.com/argoproj/argo-cd/v2/pkg/apiclient"
|
8 | 11 | "github.com/argoproj/argo-cd/v2/pkg/apiclient/session"
|
9 | 12 | "github.com/argoproj/argo-cd/v2/util/io"
|
10 | 13 | "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" |
11 | 19 | )
|
12 | 20 |
|
13 | 21 | var apiClientConnOpts apiclient.ClientOptions
|
@@ -103,6 +111,13 @@ func Provider() *schema.Provider {
|
103 | 111 | Optional: true,
|
104 | 112 | DefaultFunc: schema.EnvDefaultFunc("ARGOCD_INSECURE", false),
|
105 | 113 | },
|
| 114 | + "kubernetes": { |
| 115 | + Type: schema.TypeList, |
| 116 | + MaxItems: 1, |
| 117 | + Optional: true, |
| 118 | + Description: "Kubernetes configuration.", |
| 119 | + Elem: kubernetesResource(), |
| 120 | + }, |
106 | 121 | },
|
107 | 122 |
|
108 | 123 | ResourcesMap: map[string]*schema.Resource{
|
@@ -160,6 +175,61 @@ func initApiClient(d *schema.ResourceData) (
|
160 | 175 | if v, ok := d.GetOk("headers"); ok {
|
161 | 176 | opts.Headers = v.([]string)
|
162 | 177 | }
|
| 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 | + } |
163 | 233 |
|
164 | 234 | // Export provider API client connections options for use in other spawned api clients
|
165 | 235 | apiClientConnOpts = opts
|
@@ -194,3 +264,157 @@ func initApiClient(d *schema.ResourceData) (
|
194 | 264 | }
|
195 | 265 | return apiclient.NewClient(&opts)
|
196 | 266 | }
|
| 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