Skip to content

Commit 89a6cff

Browse files
Enhanced/improved provider configuration options and documentation (#268)
* fix(provider): implement `kubernetes.config_context` options These were available on the provider configuration but not actually used, * fix(provider): remove unused `kubernetes` configuration options These cannot be used by the provider so there's no point in having them here. * fix(provider): mark `server_addr` as optional This does not need to be supplied when port forwarding or using local config. * feat(provider): add `core` to provider configuration * fix(provider): fix description of `context` option * fix(provider): improve descriptions for port forwarding configuration * docs(provider): improve overview and add examples for common provider configuration * docs: improve wording on port forwarding example Co-authored-by: Olivier Boukili <[email protected]> * chore: drop `ARGOCD_CONTEXT` var from local test env vars --------- Co-authored-by: Olivier Boukili <[email protected]>
1 parent 8e40130 commit 89a6cff

File tree

8 files changed

+551
-44
lines changed

8 files changed

+551
-44
lines changed

GNUmakefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ ARGOCD_INSECURE?=true
44
ARGOCD_SERVER?=127.0.0.1:8080
55
ARGOCD_AUTH_USERNAME?=admin
66
ARGOCD_AUTH_PASSWORD?=acceptancetesting
7-
ARGOCD_CONTEXT?=kind-argocd
87
ARGOCD_VERSION?=v2.6.7
98

109
export

argocd/provider.go

Lines changed: 98 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import (
44
"bytes"
55
"context"
66
"fmt"
7-
"log"
87
"net/url"
98
"sync"
109

10+
"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/headless"
1111
"github.com/argoproj/argo-cd/v2/pkg/apiclient"
1212
"github.com/argoproj/argo-cd/v2/pkg/apiclient/session"
1313
"github.com/argoproj/argo-cd/v2/util/io"
@@ -17,6 +17,7 @@ import (
1717
"k8s.io/client-go/tools/clientcmd"
1818

1919
apimachineryschema "k8s.io/apimachinery/pkg/runtime/schema"
20+
"k8s.io/apimachinery/pkg/util/runtime"
2021
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
2122

2223
// Import to initialize client auth plugins.
@@ -32,25 +33,34 @@ var tokenMutexClusters = &sync.RWMutex{}
3233
// Used to handle concurrent access to each ArgoCD project
3334
var tokenMutexProjectMap = make(map[string]*sync.RWMutex, 0)
3435

36+
var runtimeErrorHandlers []func(error)
37+
3538
func Provider() *schema.Provider {
3639
return &schema.Provider{
3740
Schema: map[string]*schema.Schema{
3841
"server_addr": {
3942
Type: schema.TypeString,
40-
Required: true,
43+
Optional: true,
4144
DefaultFunc: schema.EnvDefaultFunc("ARGOCD_SERVER", nil),
4245
Description: "ArgoCD server address with port. Can be set through the `ARGOCD_SERVER` environment variable.",
46+
AtLeastOneOf: []string{
47+
"core",
48+
"port_forward",
49+
"port_forward_with_namespace",
50+
"use_local_config",
51+
},
4352
},
4453
"auth_token": {
4554
Type: schema.TypeString,
4655
Optional: true,
4756
DefaultFunc: schema.EnvDefaultFunc("ARGOCD_AUTH_TOKEN", nil),
4857
Description: "ArgoCD authentication token, takes precedence over `username`/`password`. Can be set through the `ARGOCD_AUTH_TOKEN` environment variable.",
4958
ConflictsWith: []string{
50-
"username",
59+
"config_path",
60+
"core",
5161
"password",
5262
"use_local_config",
53-
"config_path",
63+
"username",
5464
},
5565
},
5666
"username": {
@@ -60,10 +70,12 @@ func Provider() *schema.Provider {
6070
Description: "Authentication username. Can be set through the `ARGOCD_AUTH_USERNAME` environment variable.",
6171
ConflictsWith: []string{
6272
"auth_token",
63-
"use_local_config",
6473
"config_path",
74+
"core",
75+
"use_local_config",
6576
},
6677
AtLeastOneOf: []string{
78+
"core",
6779
"password",
6880
"auth_token",
6981
"use_local_config",
@@ -76,11 +88,13 @@ func Provider() *schema.Provider {
7688
Description: "Authentication password. Can be set through the `ARGOCD_AUTH_PASSWORD` environment variable.",
7789
ConflictsWith: []string{
7890
"auth_token",
79-
"use_local_config",
8091
"config_path",
92+
"core",
93+
"use_local_config",
8194
},
8295
AtLeastOneOf: []string{
8396
"username",
97+
"core",
8498
"auth_token",
8599
"use_local_config",
86100
},
@@ -110,12 +124,36 @@ func Provider() *schema.Provider {
110124
Type: schema.TypeString,
111125
Optional: true,
112126
DefaultFunc: schema.EnvDefaultFunc("ARGOCD_CONTEXT", nil),
113-
Description: "Kubernetes context to load from an existing `.kube/config` file. Can be set through `ARGOCD_CONTEXT` environment variable.",
127+
Description: "Context to choose when using a local ArgoCD config file. Only relevant when `use_local_config`. Can be set through `ARGOCD_CONTEXT` environment variable.",
128+
ConflictsWith: []string{
129+
"core",
130+
"username",
131+
"password",
132+
"auth_token",
133+
},
114134
},
115135
"user_agent": {
116136
Type: schema.TypeString,
117137
Optional: true,
118138
},
139+
"core": {
140+
Type: schema.TypeBool,
141+
Optional: true,
142+
Description: "Configure direct access using Kubernetes API server.\n\n " +
143+
"**Warning**: this feature works by starting a local ArgoCD API server that talks directly to the Kubernetes API using the **current context " +
144+
"in the default kubeconfig** (`~/.kube/config`). This behavior cannot be overridden using either environment variables or the `kubernetes` block " +
145+
"in the provider configuration at present).\n\n If the server fails to start (e.g. your kubeconfig is misconfigured) then the provider will " +
146+
"fail as a result of the `argocd` module forcing it to exit and no logs will be available to help you debug this. The error message will be " +
147+
"similar to\n > `The plugin encountered an error, and failed to respond to the plugin.(*GRPCProvider).ReadResource call. The plugin logs may " +
148+
"contain more details.`\n\n To debug this, you will need to login via the ArgoCD CLI using `argocd login --core` and then running an operation. " +
149+
"E.g. `argocd app list`.",
150+
ConflictsWith: []string{
151+
"auth_token",
152+
"use_local_config",
153+
"password",
154+
"username",
155+
},
156+
},
119157
"grpc_web": {
120158
Type: schema.TypeBool,
121159
Optional: true,
@@ -131,9 +169,10 @@ func Provider() *schema.Provider {
131169
Optional: true,
132170
Description: "Use the authentication settings found in the local config file. Useful when you have previously logged in using SSO. Conflicts with `auth_token`, `username` and `password`.",
133171
ConflictsWith: []string{
134-
"username",
135-
"password",
136172
"auth_token",
173+
"core",
174+
"password",
175+
"username",
137176
},
138177
},
139178
"config_path": {
@@ -142,18 +181,21 @@ func Provider() *schema.Provider {
142181
DefaultFunc: schema.EnvDefaultFunc("ARGOCD_CONFIG_PATH", nil),
143182
Description: "Override the default config path of `$HOME/.config/argocd/config`. Only relevant when `use_local_config`. Can be set through the `ARGOCD_CONFIG_PATH` environment variable.",
144183
ConflictsWith: []string{
145-
"username",
146-
"password",
147184
"auth_token",
185+
"core",
186+
"password",
187+
"username",
148188
},
149189
},
150190
"port_forward": {
151-
Type: schema.TypeBool,
152-
Optional: true,
191+
Type: schema.TypeBool,
192+
Description: "Connect to a random argocd-server port using port forwarding.",
193+
Optional: true,
153194
},
154195
"port_forward_with_namespace": {
155-
Type: schema.TypeString,
156-
Optional: true,
196+
Type: schema.TypeString,
197+
Description: "Namespace name which should be used for port forwarding.",
198+
Optional: true,
157199
},
158200
"headers": {
159201
Type: schema.TypeSet,
@@ -171,7 +213,7 @@ func Provider() *schema.Provider {
171213
Type: schema.TypeList,
172214
MaxItems: 1,
173215
Optional: true,
174-
Description: "Kubernetes configuration.",
216+
Description: "Kubernetes configuration overrides. Only relevant when `port_forward = true` or `port_forward_with_namespace = \"foo\"`. The kubeconfig file that is used can be overridden using the [`KUBECONFIG` environment variable](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable)).",
175217
Elem: kubernetesResource(),
176218
},
177219
},
@@ -267,6 +309,7 @@ func initApiClient(ctx context.Context, d *schema.ResourceData) (apiClient apicl
267309

268310
if _, ok := d.GetOk("kubernetes"); ok {
269311
opts.KubeOverrides = &clientcmd.ConfigOverrides{}
312+
270313
if v, ok := k8sGetOk(d, "insecure"); ok {
271314
opts.KubeOverrides.ClusterInfo.InsecureSkipTLSVerify = v.(bool)
272315
}
@@ -279,6 +322,25 @@ func initApiClient(ctx context.Context, d *schema.ResourceData) (apiClient apicl
279322
opts.KubeOverrides.AuthInfo.ClientCertificateData = bytes.NewBufferString(v.(string)).Bytes()
280323
}
281324

325+
kubectx, ctxOk := k8sGetOk(d, "config_context")
326+
authInfo, authInfoOk := k8sGetOk(d, "config_context_auth_info")
327+
cluster, clusterOk := k8sGetOk(d, "config_context_cluster")
328+
329+
if ctxOk || authInfoOk || clusterOk {
330+
if ctxOk {
331+
opts.KubeOverrides.CurrentContext = kubectx.(string)
332+
}
333+
334+
opts.KubeOverrides.Context = clientcmdapi.Context{}
335+
if authInfoOk {
336+
opts.KubeOverrides.Context.AuthInfo = authInfo.(string)
337+
}
338+
339+
if clusterOk {
340+
opts.KubeOverrides.Context.Cluster = cluster.(string)
341+
}
342+
}
343+
282344
if v, ok := k8sGetOk(d, "host"); ok {
283345
// Server has to be the complete address of the kubernetes cluster (scheme://hostname:port), not just the hostname,
284346
// because `overrides` are processed too late to be taken into account by `defaultServerUrlFor()`.
@@ -326,7 +388,6 @@ func initApiClient(ctx context.Context, d *schema.ResourceData) (apiClient apicl
326388
exec.Env = append(exec.Env, clientcmdapi.ExecEnvVar{Name: kk, Value: vv.(string)})
327389
}
328390
} else {
329-
log.Printf("[ERROR] Failed to parse exec")
330391
return nil, fmt.Errorf("failed to parse exec")
331392
}
332393

@@ -369,6 +430,26 @@ func initApiClient(ctx context.Context, d *schema.ResourceData) (apiClient apicl
369430
}
370431
}
371432

433+
if v, ok := d.Get("core").(bool); ok && v {
434+
opts.ServerAddr = "kubernetes"
435+
opts.Core = true
436+
437+
// HACK: `headless.StartLocalServer` manipulates this global variable
438+
// when starting the local server without checking it's length/contents
439+
// which leads to a panic if called multiple times. So, we need to
440+
// ensure we "reset" it before calling the method.
441+
if runtimeErrorHandlers == nil {
442+
runtimeErrorHandlers = runtime.ErrorHandlers
443+
} else {
444+
runtime.ErrorHandlers = runtimeErrorHandlers
445+
}
446+
447+
err := headless.StartLocalServer(ctx, &opts, "", nil, nil)
448+
if err != nil {
449+
return nil, fmt.Errorf("failed to start local server: %w", err)
450+
}
451+
}
452+
372453
return apiclient.NewClient(&opts)
373454
}
374455

@@ -417,19 +498,6 @@ func kubernetesResource() *schema.Resource {
417498
DefaultFunc: schema.EnvDefaultFunc("KUBE_CLUSTER_CA_CERT_DATA", ""),
418499
Description: "PEM-encoded root certificates bundle for TLS authentication. Can be sourced from `KUBE_CLUSTER_CA_CERT_DATA`.",
419500
},
420-
"config_paths": {
421-
Type: schema.TypeList,
422-
Elem: &schema.Schema{Type: schema.TypeString},
423-
Optional: true,
424-
Description: "A list of paths to the kube config files. Can be sourced from `KUBE_CONFIG_PATHS`.",
425-
},
426-
"config_path": {
427-
Type: schema.TypeString,
428-
Optional: true,
429-
DefaultFunc: schema.EnvDefaultFunc("KUBE_CONFIG_PATH", nil),
430-
Description: "Path to the kube config file. Can be sourced from `KUBE_CONFIG_PATH`.",
431-
ConflictsWith: []string{"kubernetes.0.config_paths"},
432-
},
433501
"config_context": {
434502
Type: schema.TypeString,
435503
Optional: true,

docs/index.md

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,82 @@ The ArgoCD Provider provides lifecycle management of
1111

1212
**NB**: The provider is not concerned with the installation/configuration of
1313
ArgoCD itself. To make use of the provider, you will need to have an existing
14-
ArgoCD installation and, the ArgoCD API server must be
15-
[accessible](https://argo-cd.readthedocs.io/en/stable/getting_started/#3-access-the-argo-cd-api-server)
16-
from where you are running Terraform.
14+
ArgoCD installation.
15+
16+
The correct provider configuration largely depends on whether or not your
17+
ArgoCD API server is exposed or not.
18+
19+
If your ArgoCD API server is exposed, then:
20+
- use `server_addr` along with a `username`/`password` or `auth_token`.
21+
- use `use_local_config` if you have (pre)authenticated via the ArgoCD CLI (E.g.
22+
via SSO using `argocd login --sso`.
23+
24+
If you have not exposed your ArgoCD API server or have not deployed the API
25+
server ([ArgoCD
26+
core](https://argo-cd.readthedocs.io/en/stable/operator-manual/installation/#core)),
27+
see below for options. **Note**: in both these cases, you need sufficient access
28+
to the Kubernetes API to perform any actions.
29+
- use `port_forward_with_namespace` and optionally `kubernetes` configuration
30+
(to temporarily expose the ArgoCD API server using port forwarding) along with
31+
a `username`/`password` or `auth_token`.
32+
- use `core` to run a local ArgoCD API server that communicates directly with
33+
the Kubernetes API. **NB**: When using `core`, take note of the warning in
34+
the docs below.
35+
36+
If you are struggling to determine the correct configuration for the provider or
37+
the provider is behaving strangely and failing to connect for whatever reason,
38+
then we would suggest that you first figure out what combination of parameters
39+
work to log in using the ArgoCD CLI (`argocd login`) and then set the provider
40+
configuration to match what you used in the CLI. See also the ArgoCD [Getting
41+
Started](https://argo-cd.readthedocs.io/en/stable/getting_started/#3-access-the-argo-cd-api-server)
42+
docs.
1743

1844
## Example Usage
1945

2046
```terraform
47+
# Exposed ArgoCD API - authenticated using authentication token.
2148
provider "argocd" {
2249
server_addr = "argocd.local:443"
2350
auth_token = "1234..."
2451
}
52+
53+
# Exposed ArgoCD API - authenticated using `username`/`password`
54+
provider "argocd" {
55+
server_addr = "argocd.local:443"
56+
username = "foo"
57+
password = local.password
58+
}
59+
60+
# Exposed ArgoCD API - (pre)authenticated using local ArgoCD config (e.g. when
61+
# you have previously logged in using SSO).
62+
provider "argocd" {
63+
use_local_config = true
64+
# context = "foo" # Use explicit context from ArgoCD config instead of `current-context`.
65+
}
66+
67+
# Unexposed ArgoCD API - using the current Kubernetes context and
68+
# port-forwarding to temporarily expose ArgoCD API and authenticating using
69+
# `auth_token`.
70+
provider "argocd" {
71+
auth_token = "1234..."
72+
port_forward = true
73+
}
74+
75+
# Unexposed ArgoCD API - using port-forwarding to temporarily expose ArgoCD API
76+
# whilst overriding the current context in kubeconfig.
77+
provider "argocd" {
78+
auth_token = "1234..."
79+
port_forward_with_namespace = "custom-argocd-namespace"
80+
kubernetes {
81+
config_context = "kind-argocd"
82+
}
83+
}
84+
85+
# Unexposed ArgoCD API - using `core` to run ArgoCD server locally and
86+
# communicate directly with the Kubernetes API.
87+
provider "argocd" {
88+
core = true
89+
}
2590
```
2691

2792
<!-- schema generated by tfplugindocs -->
@@ -34,16 +99,24 @@ provider "argocd" {
3499
- `client_cert_file` (String) Client certificate.
35100
- `client_cert_key` (String) Client certificate key.
36101
- `config_path` (String) Override the default config path of `$HOME/.config/argocd/config`. Only relevant when `use_local_config`. Can be set through the `ARGOCD_CONFIG_PATH` environment variable.
37-
- `context` (String) Kubernetes context to load from an existing `.kube/config` file. Can be set through `ARGOCD_CONTEXT` environment variable.
102+
- `context` (String) Context to choose when using a local ArgoCD config file. Only relevant when `use_local_config`. Can be set through `ARGOCD_CONTEXT` environment variable.
103+
- `core` (Boolean) Configure direct access using Kubernetes API server.
104+
105+
**Warning**: this feature works by starting a local ArgoCD API server that talks directly to the Kubernetes API using the **current context in the default kubeconfig** (`~/.kube/config`). This behavior cannot be overridden using either environment variables or the `kubernetes` block in the provider configuration at present).
106+
107+
If the server fails to start (e.g. your kubeconfig is misconfigured) then the provider will fail as a result of the `argocd` module forcing it to exit and no logs will be available to help you debug this. The error message will be similar to
108+
> `The plugin encountered an error, and failed to respond to the plugin.(*GRPCProvider).ReadResource call. The plugin logs may contain more details.`
109+
110+
To debug this, you will need to login via the ArgoCD CLI using `argocd login --core` and then running an operation. E.g. `argocd app list`.
38111
- `grpc_web` (Boolean) Whether to use gRPC web proxy client. Useful if Argo CD server is behind proxy which does not support HTTP2.
39112
- `grpc_web_root_path` (String) Use the gRPC web proxy client and set the web root, e.g. `argo-cd`. Useful if the Argo CD server is behind a proxy at a non-root path.
40113
- `headers` (Set of String) Additional headers to add to each request to the ArgoCD server.
41114
- `insecure` (Boolean) Whether to skip TLS server certificate. Can be set through the `ARGOCD_INSECURE` environment variable.
42-
- `kubernetes` (Block List, Max: 1) Kubernetes configuration. (see [below for nested schema](#nestedblock--kubernetes))
115+
- `kubernetes` (Block List, Max: 1) Kubernetes configuration overrides. Only relevant when `port_forward = true` or `port_forward_with_namespace = "foo"`. The kubeconfig file that is used can be overridden using the [`KUBECONFIG` environment variable](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable)). (see [below for nested schema](#nestedblock--kubernetes))
43116
- `password` (String) Authentication password. Can be set through the `ARGOCD_AUTH_PASSWORD` environment variable.
44117
- `plain_text` (Boolean) Whether to initiate an unencrypted connection to ArgoCD server.
45-
- `port_forward` (Boolean)
46-
- `port_forward_with_namespace` (String)
118+
- `port_forward` (Boolean) Connect to a random argocd-server port using port forwarding.
119+
- `port_forward_with_namespace` (String) Namespace name which should be used for port forwarding.
47120
- `server_addr` (String) ArgoCD server address with port. Can be set through the `ARGOCD_SERVER` environment variable.
48121
- `use_local_config` (Boolean) Use the authentication settings found in the local config file. Useful when you have previously logged in using SSO. Conflicts with `auth_token`, `username` and `password`.
49122
- `user_agent` (String)
@@ -60,8 +133,6 @@ Optional:
60133
- `config_context` (String) Context to choose from the config file. Can be sourced from `KUBE_CTX`.
61134
- `config_context_auth_info` (String)
62135
- `config_context_cluster` (String)
63-
- `config_path` (String) Path to the kube config file. Can be sourced from `KUBE_CONFIG_PATH`.
64-
- `config_paths` (List of String) A list of paths to the kube config files. Can be sourced from `KUBE_CONFIG_PATHS`.
65136
- `exec` (Block List, Max: 1) 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. (see [below for nested schema](#nestedblock--kubernetes--exec))
66137
- `host` (String) The hostname (in form of URI) of the Kubernetes API. Can be sourced from `KUBE_HOST`.
67138
- `insecure` (Boolean) Whether server should be accessed without verifying the TLS certificate. Can be sourced from `KUBE_INSECURE`.

0 commit comments

Comments
 (0)