Skip to content

Commit bc2aeab

Browse files
chore: migrate repository resource (#685)
* chore: migrate repository resource An attempt to migrate the repository resource to terraform-plugin-framework. Signed-off-by: Blake Pettersson <[email protected]> * fix(test): use mixed provider factory Signed-off-by: Blake Pettersson <[email protected]> * chore: move repository credentials to new framework Can't see any other good way to gradually just move repositories, it seems like it's a package deal with repository credentials. Signed-off-by: Blake Pettersson <[email protected]> * chore: move repository credentials to new framework Can't see any other good way to gradually just move repositories, it seems like it's a package deal with repository credentials. Signed-off-by: Blake Pettersson <[email protected]> * chore: wip Signed-off-by: Blake Pettersson <[email protected]> * chore: wip Signed-off-by: Blake Pettersson <[email protected]> * chore: wip Signed-off-by: Blake Pettersson <[email protected]> * chore: wip Signed-off-by: Blake Pettersson <[email protected]> * chore: lint Signed-off-by: Blake Pettersson <[email protected]> * chore: docs Signed-off-by: Blake Pettersson <[email protected]> * chore: wip Signed-off-by: Blake Pettersson <[email protected]> * chore: wip Signed-off-by: Blake Pettersson <[email protected]> * chore: wip Signed-off-by: Blake Pettersson <[email protected]> * chore: lint Signed-off-by: Blake Pettersson <[email protected]> * chore: lint Signed-off-by: Blake Pettersson <[email protected]> --------- Signed-off-by: Blake Pettersson <[email protected]>
1 parent 24174ae commit bc2aeab

40 files changed

+2662
-1364
lines changed

argocd/model_provider.go

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
package argocd
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
"net/url"
8+
9+
"github.com/argoproj-labs/terraform-provider-argocd/internal/diagnostics"
10+
"github.com/argoproj/argo-cd/v3/cmd/argocd/commands/headless"
11+
"github.com/argoproj/argo-cd/v3/pkg/apiclient"
12+
"github.com/argoproj/argo-cd/v3/pkg/apiclient/session"
13+
"github.com/argoproj/argo-cd/v3/util/io"
14+
"github.com/argoproj/argo-cd/v3/util/localconfig"
15+
"github.com/hashicorp/terraform-plugin-framework/diag"
16+
"github.com/hashicorp/terraform-plugin-framework/types"
17+
apimachineryschema "k8s.io/apimachinery/pkg/runtime/schema"
18+
"k8s.io/apimachinery/pkg/util/runtime"
19+
"k8s.io/client-go/rest"
20+
"k8s.io/client-go/tools/clientcmd"
21+
"k8s.io/client-go/tools/clientcmd/api"
22+
)
23+
24+
type ArgoCDProviderConfig struct {
25+
// Configuration for standard login using either with username/password or auth_token
26+
AuthToken types.String `tfsdk:"auth_token"`
27+
Username types.String `tfsdk:"username"`
28+
Password types.String `tfsdk:"password"`
29+
30+
// When using standard login either server address or port forwarding must be used
31+
ServerAddr types.String `tfsdk:"server_addr"`
32+
PortForward types.Bool `tfsdk:"port_forward"`
33+
PortForwardWithNamespace types.String `tfsdk:"port_forward_with_namespace"`
34+
Kubernetes []Kubernetes `tfsdk:"kubernetes"`
35+
36+
// Run ArgoCD API server locally
37+
Core types.Bool `tfsdk:"core"`
38+
39+
// Login using credentials from local ArgoCD config file
40+
UseLocalConfig types.Bool `tfsdk:"use_local_config"`
41+
ConfigPath types.String `tfsdk:"config_path"`
42+
Context types.String `tfsdk:"context"`
43+
44+
// Other configuration
45+
CertFile types.String `tfsdk:"cert_file"`
46+
ClientCertFile types.String `tfsdk:"client_cert_file"`
47+
ClientCertKey types.String `tfsdk:"client_cert_key"`
48+
GRPCWeb types.Bool `tfsdk:"grpc_web"`
49+
GRPCWebRootPath types.String `tfsdk:"grpc_web_root_path"`
50+
Headers types.Set `tfsdk:"headers"`
51+
Insecure types.Bool `tfsdk:"insecure"`
52+
PlainText types.Bool `tfsdk:"plain_text"`
53+
UserAgent types.String `tfsdk:"user_agent"`
54+
}
55+
56+
func (p ArgoCDProviderConfig) getApiClientOptions(ctx context.Context) (*apiclient.ClientOptions, diag.Diagnostics) {
57+
var diags diag.Diagnostics
58+
59+
opts := &apiclient.ClientOptions{
60+
AuthToken: getDefaultString(p.AuthToken, "ARGOCD_AUTH_TOKEN"),
61+
CertFile: p.CertFile.ValueString(),
62+
ClientCertFile: p.ClientCertFile.ValueString(),
63+
ClientCertKeyFile: p.ClientCertKey.ValueString(),
64+
GRPCWeb: p.GRPCWeb.ValueBool(),
65+
GRPCWebRootPath: p.GRPCWebRootPath.ValueString(),
66+
Insecure: getDefaultBool(ctx, p.Insecure, "ARGOCD_INSECURE"),
67+
PlainText: p.PlainText.ValueBool(),
68+
PortForward: p.PortForward.ValueBool(),
69+
PortForwardNamespace: p.PortForwardWithNamespace.ValueString(),
70+
ServerAddr: getDefaultString(p.ServerAddr, "ARGOCD_SERVER"),
71+
UserAgent: p.Username.ValueString(),
72+
}
73+
74+
if !p.Headers.IsNull() {
75+
var h []string
76+
77+
diags.Append(p.Headers.ElementsAs(ctx, &h, false)...)
78+
79+
opts.Headers = h
80+
}
81+
82+
coreEnabled, d := p.setCoreOpts(opts)
83+
84+
diags.Append(d...)
85+
86+
localConfigEnabled, d := p.setLocalConfigOpts(opts)
87+
88+
diags.Append(d...)
89+
90+
portForwardingEnabled, d := p.setPortForwardingOpts(ctx, opts)
91+
92+
diags.Append(d...)
93+
94+
username := getDefaultString(p.Username, "ARGOCD_AUTH_USERNAME")
95+
password := getDefaultString(p.Password, "ARGOCD_AUTH_PASSWORD")
96+
97+
usernameAndPasswordSet := username != "" && password != ""
98+
99+
switch {
100+
// Provider configuration errors
101+
case !coreEnabled && !portForwardingEnabled && !localConfigEnabled && opts.ServerAddr == "":
102+
diags.Append(diagnostics.Error("invalid provider configuration: one of `core,port_forward,port_forward_with_namespace,use_local_config,server_addr` must be specified", nil)...)
103+
case portForwardingEnabled && opts.AuthToken == "" && !usernameAndPasswordSet:
104+
diags.Append(diagnostics.Error("invalid provider configuration: either `username/password` or `auth_token` must be specified when port forwarding is enabled", nil)...)
105+
case opts.ServerAddr != "" && !coreEnabled && opts.AuthToken == "" && !usernameAndPasswordSet:
106+
diags.Append(diagnostics.Error("invalid provider configuration: either `username/password` or `auth_token` must be specified if `server_addr` is specified", nil)...)
107+
}
108+
109+
if diags.HasError() {
110+
return nil, diags
111+
}
112+
113+
switch {
114+
// Handle "special" configuration use-cases
115+
case coreEnabled:
116+
// HACK: `headless.StartLocalServer` manipulates this global variable
117+
// when starting the local server without checking it's length/contents
118+
// which leads to a panic if called multiple times. So, we need to
119+
// ensure we "reset" it before calling the method.
120+
if runtimeErrorHandlers == nil {
121+
runtimeErrorHandlers = runtime.ErrorHandlers
122+
} else {
123+
runtime.ErrorHandlers = runtimeErrorHandlers
124+
}
125+
126+
err := headless.MaybeStartLocalServer(ctx, opts, "", nil, nil, nil)
127+
if err != nil {
128+
diags.Append(diagnostics.Error("failed to start local server", err)...)
129+
return nil, diags
130+
}
131+
case opts.ServerAddr != "" && opts.AuthToken == "" && usernameAndPasswordSet:
132+
apiClient, err := apiclient.NewClient(opts)
133+
if err != nil {
134+
diags.Append(diagnostics.Error("failed to create new API client", err)...)
135+
return nil, diags
136+
}
137+
138+
closer, sc, err := apiClient.NewSessionClient()
139+
if err != nil {
140+
diags.Append(diagnostics.Error("failed to create new session client", err)...)
141+
return nil, diags
142+
}
143+
144+
defer io.Close(closer)
145+
146+
sessionOpts := session.SessionCreateRequest{
147+
Username: username,
148+
Password: password,
149+
}
150+
151+
resp, err := sc.Create(ctx, &sessionOpts)
152+
if err != nil {
153+
diags.Append(diagnostics.Error("failed to create new session", err)...)
154+
return nil, diags
155+
}
156+
157+
opts.AuthToken = resp.Token
158+
}
159+
160+
return opts, diags
161+
}
162+
163+
func (p ArgoCDProviderConfig) setCoreOpts(opts *apiclient.ClientOptions) (bool, diag.Diagnostics) {
164+
var diags diag.Diagnostics
165+
166+
coreEnabled := p.Core.ValueBool()
167+
if coreEnabled {
168+
if opts.ServerAddr != "" {
169+
diags.AddWarning("`server_addr` is ignored by the provider and overwritten when `core = true`.", "")
170+
}
171+
172+
opts.ServerAddr = "kubernetes"
173+
opts.Core = true
174+
175+
if !p.Username.IsNull() {
176+
diags.AddWarning("`username` is ignored when `core = true`.", "")
177+
}
178+
}
179+
180+
return coreEnabled, diags
181+
}
182+
183+
func (p ArgoCDProviderConfig) setLocalConfigOpts(opts *apiclient.ClientOptions) (bool, diag.Diagnostics) {
184+
var diags diag.Diagnostics
185+
186+
useLocalConfig := p.UseLocalConfig.ValueBool()
187+
switch useLocalConfig {
188+
case true:
189+
if opts.ServerAddr != "" {
190+
diags.AddWarning("setting `server_addr` alongside `use_local_config = true` is unnecessary and not recommended as this will overwrite the address retrieved from the local ArgoCD context.", "")
191+
}
192+
193+
if !p.Username.IsNull() {
194+
diags.AddWarning("`username` is ignored when `use_local_config = true`.", "")
195+
}
196+
197+
opts.Context = getDefaultString(p.Context, "ARGOCD_CONTEXT")
198+
199+
cp := getDefaultString(p.ConfigPath, "ARGOCD_CONFIG_PATH")
200+
201+
if cp != "" {
202+
opts.ConfigPath = p.ConfigPath.ValueString()
203+
break
204+
}
205+
206+
cp, err := localconfig.DefaultLocalConfigPath()
207+
if err == nil {
208+
opts.ConfigPath = cp
209+
break
210+
}
211+
212+
diags.Append(diagnostics.Error("failed to find default ArgoCD config path", err)...)
213+
case false:
214+
// Log warnings if explicit configuration has been provided for local config when `use_local_config` is not enabled.
215+
if !p.ConfigPath.IsNull() {
216+
diags.AddWarning("`config_path` is ignored by provider unless `use_local_config = true`.", "")
217+
}
218+
219+
if !p.Context.IsNull() {
220+
diags.AddWarning("`context` is ignored by provider unless `use_local_config = true`.", "")
221+
}
222+
}
223+
224+
return useLocalConfig, diags
225+
}
226+
227+
func (p ArgoCDProviderConfig) setPortForwardingOpts(ctx context.Context, opts *apiclient.ClientOptions) (bool, diag.Diagnostics) {
228+
var diags diag.Diagnostics
229+
230+
portForwardingEnabled := opts.PortForward || opts.PortForwardNamespace != ""
231+
switch portForwardingEnabled {
232+
case true:
233+
if opts.ServerAddr != "" {
234+
diags.AddWarning("`server_addr` is ignored by the provider and overwritten when port forwarding is enabled.", "")
235+
}
236+
237+
opts.ServerAddr = "localhost" // will be overwritten by ArgoCD module when we initialize the API client but needs to be set here to ensure we
238+
opts.ServerName = "argocd-server"
239+
240+
if opts.PortForwardNamespace == "" {
241+
opts.PortForwardNamespace = "argocd"
242+
}
243+
244+
if p.Kubernetes == nil {
245+
break
246+
}
247+
248+
k := p.Kubernetes[0]
249+
opts.KubeOverrides = &clientcmd.ConfigOverrides{
250+
AuthInfo: api.AuthInfo{
251+
ClientCertificateData: bytes.NewBufferString(getDefaultString(k.ClientCertificate, "KUBE_CLIENT_CERT_DATA")).Bytes(),
252+
Username: getDefaultString(k.Username, "KUBE_USER"),
253+
Password: getDefaultString(k.Password, "KUBE_PASSWORD"),
254+
ClientKeyData: bytes.NewBufferString(getDefaultString(k.ClientKey, "KUBE_CLIENT_KEY_DATA")).Bytes(),
255+
Token: getDefaultString(k.Token, "KUBE_TOKEN"),
256+
},
257+
ClusterInfo: api.Cluster{
258+
InsecureSkipTLSVerify: getDefaultBool(ctx, k.Insecure, "KUBE_INSECURE"),
259+
CertificateAuthorityData: bytes.NewBufferString(getDefaultString(k.ClusterCACertificate, "KUBE_CLUSTER_CA_CERT_DATA")).Bytes(),
260+
},
261+
CurrentContext: getDefaultString(k.ConfigContext, "KUBE_CTX"),
262+
Context: api.Context{
263+
AuthInfo: getDefaultString(k.ConfigContextAuthInfo, "KUBE_CTX_AUTH_INFO"),
264+
Cluster: getDefaultString(k.ConfigContextCluster, "KUBE_CTX_CLUSTER"),
265+
},
266+
}
267+
268+
h := getDefaultString(k.Host, "KUBE_HOST")
269+
if h != "" {
270+
// Server has to be the complete address of the Kubernetes cluster (scheme://hostname:port), not just the hostname,
271+
// because `overrides` are processed too late to be taken into account by `defaultServerUrlFor()`.
272+
// This basically replicates what defaultServerUrlFor() does with config but for overrides,
273+
// see https://github.com/Kubernetes/client-go/blob/v12.0.0/rest/url_utils.go#L85-L87
274+
hasCA := len(opts.KubeOverrides.ClusterInfo.CertificateAuthorityData) != 0
275+
hasCert := len(opts.KubeOverrides.AuthInfo.ClientCertificateData) != 0
276+
defaultTLS := hasCA || hasCert || opts.KubeOverrides.ClusterInfo.InsecureSkipTLSVerify
277+
278+
var host *url.URL
279+
280+
host, _, err := rest.DefaultServerURL(h, "", apimachineryschema.GroupVersion{}, defaultTLS)
281+
if err == nil {
282+
opts.KubeOverrides.ClusterInfo.Server = host.String()
283+
} else {
284+
diags.Append(diagnostics.Error(fmt.Sprintf("failed to extract default server URL for host %s", h), err)...)
285+
}
286+
}
287+
288+
if k.Exec == nil {
289+
break
290+
}
291+
292+
e := k.Exec[0]
293+
exec := &api.ExecConfig{
294+
InteractiveMode: api.IfAvailableExecInteractiveMode,
295+
APIVersion: e.APIVersion.ValueString(),
296+
Command: e.Command.ValueString(),
297+
}
298+
299+
var a []string
300+
301+
diags.Append(e.Args.ElementsAs(ctx, &a, false)...)
302+
exec.Args = a
303+
304+
var env map[string]string
305+
306+
diags.Append(e.Env.ElementsAs(ctx, &env, false)...)
307+
308+
for k, v := range env {
309+
exec.Env = append(exec.Env, api.ExecEnvVar{Name: k, Value: v})
310+
}
311+
312+
opts.KubeOverrides.AuthInfo.Exec = exec
313+
case false:
314+
if p.Kubernetes != nil {
315+
diags.AddWarning("`Kubernetes` configuration block is ignored by provider unless `port_forward` or `port_forward_with_namespace` are configured.", "")
316+
}
317+
}
318+
319+
return portForwardingEnabled, diags
320+
}
321+
322+
type Kubernetes struct {
323+
Host types.String `tfsdk:"host"`
324+
Username types.String `tfsdk:"username"`
325+
Password types.String `tfsdk:"password"`
326+
Insecure types.Bool `tfsdk:"insecure"`
327+
ClientCertificate types.String `tfsdk:"client_certificate"`
328+
ClientKey types.String `tfsdk:"client_key"`
329+
ClusterCACertificate types.String `tfsdk:"cluster_ca_certificate"`
330+
ConfigContext types.String `tfsdk:"config_context"`
331+
ConfigContextAuthInfo types.String `tfsdk:"config_context_auth_info"`
332+
ConfigContextCluster types.String `tfsdk:"config_context_cluster"`
333+
Token types.String `tfsdk:"token"`
334+
Exec []KubernetesExec `tfsdk:"exec"`
335+
}
336+
337+
type KubernetesExec struct {
338+
APIVersion types.String `tfsdk:"api_version"`
339+
Command types.String `tfsdk:"command"`
340+
Env types.Map `tfsdk:"env"`
341+
Args types.List `tfsdk:"args"`
342+
}

0 commit comments

Comments
 (0)