Skip to content

Commit bc678d4

Browse files
add an argocd_cluster resource (argoproj-labs#64)
* fix: fix flaky semver features test * feat: added argocd_cluster resource Co-authored-by: Brian Fox <[email protected]>
1 parent 07f9eac commit bc678d4

15 files changed

+791
-73
lines changed

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,20 @@ provider "argocd" {
8484
insecure = false # env ARGOCD_INSECURE
8585
}
8686
87+
resource "argocd_cluster" "kubernetes" {
88+
server = "https://1.2.3.4:12345"
89+
name = "mycluster"
90+
91+
config {
92+
bearer_token = "eyJhbGciOiJSUzI..."
93+
94+
tls_client_config {
95+
ca_data = base64encode(file("path/to/ca.pem"))
96+
// insecure = true
97+
}
98+
}
99+
}
100+
87101
resource "argocd_repository_credentials" "private" {
88102
89103
username = "git"
@@ -178,7 +192,10 @@ resource "argocd_project" "myproject" {
178192
sync_window {
179193
kind = "deny"
180194
applications = ["foo"]
181-
clusters = ["in-cluster"]
195+
clusters = [
196+
"in-cluster",
197+
argocd_cluster.cluster.name,
198+
]
182199
namespaces = ["default"]
183200
duration = "12h"
184201
schedule = "22 1 5 * *"

argocd/features.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/Masterminds/semver"
66
"github.com/argoproj/argo-cd/pkg/apiclient"
77
"github.com/argoproj/argo-cd/pkg/apiclient/application"
8+
"github.com/argoproj/argo-cd/pkg/apiclient/cluster"
89
"github.com/argoproj/argo-cd/pkg/apiclient/project"
910
"github.com/argoproj/argo-cd/pkg/apiclient/repocreds"
1011
"github.com/argoproj/argo-cd/pkg/apiclient/repository"
@@ -28,6 +29,7 @@ var (
2829
type ServerInterface struct {
2930
ApiClient *apiclient.Client
3031
ApplicationClient *application.ApplicationServiceClient
32+
ClusterClient *cluster.ClusterServiceClient
3133
ProjectClient *project.ProjectServiceClient
3234
RepositoryClient *repository.RepositoryServiceClient
3335
RepoCredsClient *repocreds.RepoCredsServiceClient

argocd/features_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ func serverInterfaceTestData(t *testing.T, argocdVersion string, semverOperator
3737
case semverLess:
3838
v, err = semver.NewVersion(
3939
fmt.Sprintf("%d.%d.%d",
40-
mathutil.MinInt64(v.Major(), v.Major()-incMajor%v.Major()),
41-
mathutil.MinInt64(v.Minor(), v.Minor()-incMinor%v.Minor()),
42-
mathutil.MinInt64(v.Patch(), v.Patch()-incPatch%v.Patch()),
40+
mathutil.MaxInt64(v.Major()-incMajor, 0),
41+
mathutil.MaxInt64(v.Minor()-incMinor, 0),
42+
mathutil.MaxInt64(v.Patch()-incPatch, 0),
4343
))
4444
assert.NoError(t, err)
4545
default:

argocd/provider.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/Masterminds/semver"
77
"github.com/argoproj/argo-cd/pkg/apiclient"
88
"github.com/argoproj/argo-cd/pkg/apiclient/application"
9+
"github.com/argoproj/argo-cd/pkg/apiclient/cluster"
910
"github.com/argoproj/argo-cd/pkg/apiclient/project"
1011
"github.com/argoproj/argo-cd/pkg/apiclient/repocreds"
1112
"github.com/argoproj/argo-cd/pkg/apiclient/repository"
@@ -110,6 +111,7 @@ func Provider() terraform.ResourceProvider {
110111

111112
ResourcesMap: map[string]*schema.Resource{
112113
"argocd_application": resourceArgoCDApplication(),
114+
"argocd_cluster": resourceArgoCDCluster(),
113115
"argocd_project": resourceArgoCDProject(),
114116
"argocd_project_token": resourceArgoCDProjectToken(),
115117
"argocd_repository": resourceArgoCDRepository(),
@@ -120,16 +122,18 @@ func Provider() terraform.ResourceProvider {
120122
if err != nil {
121123
return nil, err
122124
}
123-
_, projectClient, err := apiClient.NewProjectClient()
125+
_, clusterClient, err := apiClient.NewClusterClient()
124126
if err != nil {
125127
return nil, err
126128
}
127-
128129
_, applicationClient, err := apiClient.NewApplicationClient()
129130
if err != nil {
130131
return nil, err
131132
}
132-
133+
_, projectClient, err := apiClient.NewProjectClient()
134+
if err != nil {
135+
return nil, err
136+
}
133137
_, repositoryClient, err := apiClient.NewRepoClient()
134138
if err != nil {
135139
return nil, err
@@ -141,8 +145,9 @@ func Provider() terraform.ResourceProvider {
141145
}
142146
return initServerInterface(
143147
apiClient,
144-
projectClient,
145148
applicationClient,
149+
clusterClient,
150+
projectClient,
146151
repositoryClient,
147152
repoCredsClient,
148153
)
@@ -152,8 +157,9 @@ func Provider() terraform.ResourceProvider {
152157

153158
func initServerInterface(
154159
apiClient apiclient.Client,
155-
projectClient project.ProjectServiceClient,
156160
applicationClient application.ApplicationServiceClient,
161+
clusterClient cluster.ClusterServiceClient,
162+
projectClient project.ProjectServiceClient,
157163
repositoryClient repository.RepositoryServiceClient,
158164
repoCredsClient repocreds.RepoCredsServiceClient,
159165
) (interface{}, error) {
@@ -178,6 +184,7 @@ func initServerInterface(
178184
return ServerInterface{
179185
&apiClient,
180186
&applicationClient,
187+
&clusterClient,
181188
&projectClient,
182189
&repositoryClient,
183190
&repoCredsClient,

argocd/resource_argocd_cluster.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package argocd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
clusterClient "github.com/argoproj/argo-cd/pkg/apiclient/cluster"
7+
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
8+
"strings"
9+
)
10+
11+
func resourceArgoCDCluster() *schema.Resource {
12+
return &schema.Resource{
13+
Create: resourceArgoCDClusterCreate,
14+
Read: resourceArgoCDClusterRead,
15+
Update: resourceArgoCDClusterUpdate,
16+
Delete: resourceArgoCDClusterDelete,
17+
// TODO: add importer tests
18+
Importer: &schema.ResourceImporter{
19+
State: schema.ImportStatePassthrough,
20+
},
21+
Schema: clusterSchema(),
22+
}
23+
}
24+
25+
func resourceArgoCDClusterCreate(d *schema.ResourceData, meta interface{}) error {
26+
server := meta.(ServerInterface)
27+
client := *server.ClusterClient
28+
cluster, err := expandCluster(d)
29+
if err != nil {
30+
return fmt.Errorf("could not expand cluster attributes: %s", err)
31+
}
32+
c, err := client.Create(context.Background(), &clusterClient.ClusterCreateRequest{
33+
Cluster: cluster, Upsert: false})
34+
if err != nil {
35+
return fmt.Errorf("something went wrong during cluster resource creation: %s", err)
36+
}
37+
if c.Name != "" {
38+
d.SetId(fmt.Sprintf("%s/%s", c.Server, c.Name))
39+
} else {
40+
d.SetId(c.Server)
41+
}
42+
return resourceArgoCDClusterRead(d, meta)
43+
}
44+
45+
func resourceArgoCDClusterRead(d *schema.ResourceData, meta interface{}) error {
46+
server := meta.(ServerInterface)
47+
client := *server.ClusterClient
48+
c, err := client.Get(context.Background(), getClusterQueryFromID(d))
49+
if err != nil {
50+
switch strings.Contains(err.Error(), "NotFound") {
51+
case true:
52+
d.SetId("")
53+
return nil
54+
default:
55+
return fmt.Errorf("could not get cluster information: %s", err)
56+
}
57+
}
58+
err = flattenCluster(c, d)
59+
return err
60+
}
61+
62+
func resourceArgoCDClusterUpdate(d *schema.ResourceData, meta interface{}) error {
63+
server := meta.(ServerInterface)
64+
client := *server.ClusterClient
65+
cluster, err := expandCluster(d)
66+
if err != nil {
67+
return fmt.Errorf("could not expand cluster attributes: %s", err)
68+
}
69+
_, err = client.Update(context.Background(), &clusterClient.ClusterUpdateRequest{Cluster: cluster})
70+
if err != nil {
71+
switch strings.Contains(err.Error(), "NotFound") {
72+
case true:
73+
d.SetId("")
74+
return nil
75+
default:
76+
return fmt.Errorf("something went wrong during cluster update: %s", err)
77+
}
78+
}
79+
return resourceArgoCDClusterRead(d, meta)
80+
}
81+
82+
func resourceArgoCDClusterDelete(d *schema.ResourceData, meta interface{}) error {
83+
server := meta.(ServerInterface)
84+
client := *server.ClusterClient
85+
_, err := client.Delete(context.Background(), getClusterQueryFromID(d))
86+
if err != nil {
87+
switch strings.Contains(err.Error(), "NotFound") {
88+
case true:
89+
d.SetId("")
90+
return nil
91+
default:
92+
return fmt.Errorf("something went wrong during cluster deletion: %s", err)
93+
}
94+
}
95+
d.SetId("")
96+
return nil
97+
}
98+
99+
func getClusterQueryFromID(d *schema.ResourceData) *clusterClient.ClusterQuery {
100+
id := strings.Split(strings.TrimPrefix(d.Id(), "https://"), "/")
101+
cq := &clusterClient.ClusterQuery{}
102+
if len(id) > 1 {
103+
cq.Name = id[len(id)-1]
104+
cq.Server = fmt.Sprintf("https://%s", strings.Join(id[:len(id)-1], "/"))
105+
} else {
106+
cq.Server = d.Id()
107+
}
108+
return cq
109+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package argocd
2+
3+
import (
4+
"fmt"
5+
"runtime"
6+
"testing"
7+
8+
"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
9+
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
10+
"k8s.io/client-go/rest"
11+
"k8s.io/client-go/tools/clientcmd"
12+
"k8s.io/client-go/util/homedir"
13+
)
14+
15+
func TestAccArgoCDCluster(t *testing.T) {
16+
resource.Test(t, resource.TestCase{
17+
PreCheck: func() { testAccPreCheck(t) },
18+
Providers: testAccProviders,
19+
Steps: []resource.TestStep{
20+
{
21+
Config: testAccArgoCDClusterBearerToken(acctest.RandString(10)),
22+
Check: resource.ComposeTestCheckFunc(
23+
resource.TestCheckResourceAttr(
24+
"argocd_cluster.simple",
25+
"info.0.connection_state.0.status",
26+
"Successful",
27+
),
28+
resource.TestCheckResourceAttr(
29+
"argocd_cluster.simple",
30+
"shard",
31+
"1",
32+
),
33+
resource.TestCheckResourceAttr(
34+
"argocd_cluster.simple",
35+
"info.0.server_version",
36+
"1.19",
37+
),
38+
resource.TestCheckResourceAttr(
39+
"argocd_cluster.simple",
40+
"info.0.applications_count",
41+
"0",
42+
),
43+
resource.TestCheckResourceAttr(
44+
"argocd_cluster.simple",
45+
"config.0.tls_client_config.0.insecure",
46+
"true",
47+
),
48+
),
49+
},
50+
{
51+
Config: testAccArgoCDClusterTLSCertificate(t, acctest.RandString(10)),
52+
Check: resource.ComposeTestCheckFunc(
53+
resource.TestCheckResourceAttr(
54+
"argocd_cluster.tls",
55+
"info.0.connection_state.0.status",
56+
"Successful",
57+
),
58+
resource.TestCheckResourceAttr(
59+
"argocd_cluster.tls",
60+
"info.0.server_version",
61+
"1.19",
62+
),
63+
resource.TestCheckResourceAttr(
64+
"argocd_cluster.tls",
65+
"config.0.tls_client_config.0.insecure",
66+
"false",
67+
),
68+
),
69+
},
70+
},
71+
})
72+
}
73+
74+
func testAccArgoCDClusterBearerToken(clusterName string) string {
75+
return fmt.Sprintf(`
76+
resource "argocd_cluster" "simple" {
77+
server = "https://kubernetes.default.svc.cluster.local"
78+
name = "%s"
79+
shard = "1"
80+
namespaces = ["default", "foo"]
81+
config {
82+
# Uses Kind's bootstrap token whose ttl is 24 hours after cluster bootstrap.
83+
bearer_token = "abcdef.0123456789abcdef"
84+
tls_client_config {
85+
insecure = true
86+
}
87+
}
88+
}
89+
`, clusterName)
90+
}
91+
92+
func testAccArgoCDClusterTLSCertificate(t *testing.T, clusterName string) string {
93+
rc, err := getInternalRestConfig()
94+
if err != nil {
95+
t.Error(err)
96+
}
97+
return fmt.Sprintf(`
98+
resource "argocd_cluster" "tls" {
99+
server = "https://kubernetes.default.svc.cluster.local"
100+
name = "%s"
101+
namespaces = ["bar", "baz"]
102+
config {
103+
tls_client_config {
104+
key_data = <<EOT
105+
%s
106+
EOT
107+
cert_data = <<EOT
108+
%s
109+
EOT
110+
ca_data = <<EOT
111+
%s
112+
EOT
113+
server_name = "%s"
114+
insecure = false
115+
}
116+
}
117+
}
118+
`, clusterName, rc.KeyData, rc.CertData, rc.CAData, rc.ServerName)
119+
}
120+
121+
// getInternalRestConfig returns the internal Kubernetes cluster REST config.
122+
func getInternalRestConfig() (*rest.Config, error) {
123+
rc := &rest.Config{}
124+
var kubeConfigFilePath string
125+
126+
switch runtime.GOOS {
127+
case "windows":
128+
kubeConfigFilePath = fmt.Sprintf("%s\\.kube\\config", homedir.HomeDir())
129+
default:
130+
kubeConfigFilePath = fmt.Sprintf("%s/.kube/config", homedir.HomeDir())
131+
}
132+
cfg, err := clientcmd.LoadFromFile(kubeConfigFilePath)
133+
if err != nil {
134+
return nil, err
135+
}
136+
for key, cluster := range cfg.Clusters {
137+
if key == "kind-argocd" {
138+
authInfo := cfg.AuthInfos[key]
139+
rc.Host = cluster.Server
140+
rc.ServerName = cluster.TLSServerName
141+
rc.TLSClientConfig.CAData = cluster.CertificateAuthorityData
142+
rc.TLSClientConfig.CertData = authInfo.ClientCertificateData
143+
rc.TLSClientConfig.KeyData = authInfo.ClientKeyData
144+
return rc, nil
145+
}
146+
}
147+
return nil, fmt.Errorf("could not find a kind-argocd cluster from the current ~/.kube/config file")
148+
}

0 commit comments

Comments
 (0)