Skip to content

Commit

Permalink
Prevent duplicate clusters ending up in Terraform state (#264)
Browse files Browse the repository at this point in the history
  • Loading branch information
onematchfox committed Apr 25, 2023
1 parent 50f550a commit cb8528d
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 102 deletions.
12 changes: 8 additions & 4 deletions argocd/resource_argocd_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,20 @@ func resourceArgoCDClusterCreate(ctx context.Context, d *schema.ResourceData, me

client := *server.ClusterClient

// Need a full lock here to avoid race conditions between List existing clusters and creating a new one
tokenMutexClusters.Lock()

// Cluster are unique by "server address" so we should check there is no existing cluster with this address before
tokenMutexClusters.RLock()
existingClusters, err := client.List(ctx, &clusterClient.ClusterQuery{
Id: &clusterClient.ClusterID{
Type: "server",
Value: cluster.Server, // TODO: not used by backend, upstream bug ?
},
})
tokenMutexClusters.RUnlock()

if err != nil {
tokenMutexClusters.Unlock()

return []diag.Diagnostic{
{
Severity: diag.Error,
Expand All @@ -115,6 +118,8 @@ func resourceArgoCDClusterCreate(ctx context.Context, d *schema.ResourceData, me
if len(existingClusters.Items) > 0 {
for _, existingCluster := range existingClusters.Items {
if rtrimmedServer == strings.TrimRight(existingCluster.Server, "/") {
tokenMutexClusters.Unlock()

return []diag.Diagnostic{
{
Severity: diag.Error,
Expand All @@ -125,9 +130,8 @@ func resourceArgoCDClusterCreate(ctx context.Context, d *schema.ResourceData, me
}
}

tokenMutexClusters.Lock()
c, err := client.Create(ctx, &clusterClient.ClusterCreateRequest{
Cluster: cluster, Upsert: true})
Cluster: cluster, Upsert: false})
tokenMutexClusters.Unlock()

if err != nil {
Expand Down
117 changes: 19 additions & 98 deletions argocd/resource_argocd_cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,99 +273,22 @@ func TestAccArgoCDCluster_metadata(t *testing.T) {
})
}

func TestAccArgoCDCluster_uniqueByServerEvenWithDifferentNames(t *testing.T) {
name := acctest.RandString(10)

func TestAccArgoCDCluster_invalidSameServer(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t); testAccPreCheckFeatureSupported(t, featureProjectScopedClusters) },
ProviderFactories: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccArgoCDClusterTwiceWithSameServer(name, name+"d"),
Config: testAccArgoCDClusterTwiceWithSameServer(),
ExpectError: regexp.MustCompile("cluster with server address .* already exists"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"argocd_cluster.cluster_one",
"info.0.connection_state.0.status",
"Successful",
),
resource.TestCheckResourceAttr(
"argocd_cluster.cluster_one",
"config.0.tls_client_config.0.insecure",
"true",
),
resource.TestCheckResourceAttr(
"argocd_cluster.cluster_one",
"name",
name,
),
),
},
},
})
}

func TestAccArgoCDCluster_uniqueByServer(t *testing.T) {
name := acctest.RandString(10)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t); testAccPreCheckFeatureSupported(t, featureProjectScopedClusters) },
ProviderFactories: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccArgoCDClusterTwiceWithSameServerNoNames(),
ExpectError: regexp.MustCompile("cluster with server address .* already exists"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"argocd_cluster.cluster_one",
"info.0.connection_state.0.status",
"Successful",
),
resource.TestCheckResourceAttr(
"argocd_cluster.cluster_one",
"config.0.tls_client_config.0.insecure",
"true",
),
resource.TestCheckResourceAttr(
"argocd_cluster.cluster_one",
"name",
name,
),
),
},
},
})
}

func TestAccArgoCDCluster_uniqueByServerTrimmed(t *testing.T) {
name := acctest.RandString(10)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t); testAccPreCheckFeatureSupported(t, featureProjectScopedClusters) },
ProviderFactories: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccArgoCDClusterTwiceWithSameServerNoNamesTrimmed(
"https://kubernetes.default.svc.cluster.local",
"https://kubernetes.default.svc.cluster.local/"),
Config: testAccArgoCDClusterTwiceWithSameLogicalServer(),
ExpectError: regexp.MustCompile("cluster with server address .* already exists"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"argocd_cluster.cluster_one",
"info.0.connection_state.0.status",
"Successful",
),
resource.TestCheckResourceAttr(
"argocd_cluster.cluster_one",
"config.0.tls_client_config.0.insecure",
"true",
),
resource.TestCheckResourceAttr(
"argocd_cluster.cluster_one",
"name",
name,
),
),
},
},
})
Expand Down Expand Up @@ -486,11 +409,11 @@ resource "argocd_cluster" "cluster_metadata" {
`
}

func testAccArgoCDClusterTwiceWithSameServer(clusterName, clusterName2 string) string {
return fmt.Sprintf(`
resource "argocd_cluster" "cluster_one" {
func testAccArgoCDClusterTwiceWithSameServer() string {
return `
resource "argocd_cluster" "cluster_one_same_server" {
server = "https://kubernetes.default.svc.cluster.local"
name = "%s"
name = "foo"
config {
# Uses Kind's bootstrap token whose ttl is 24 hours after cluster bootstrap.
bearer_token = "abcdef.0123456789abcdef"
Expand All @@ -499,23 +422,22 @@ resource "argocd_cluster" "cluster_one" {
}
}
}
resource "argocd_cluster" "cluster_two" {
resource "argocd_cluster" "cluster_two_same_server" {
server = "https://kubernetes.default.svc.cluster.local"
name = "%s"
name = "bar"
config {
# Uses Kind's bootstrap token whose ttl is 24 hours after cluster bootstrap.
bearer_token = "abcdef.0123456789abcdef"
tls_client_config {
insecure = true
}
}
}
`, clusterName, clusterName2)
}`
}

func testAccArgoCDClusterTwiceWithSameServerNoNames() string {
return `
resource "argocd_cluster" "cluster_one" {
resource "argocd_cluster" "cluster_one_no_name" {
server = "https://kubernetes.default.svc.cluster.local"
config {
# Uses Kind's bootstrap token whose ttl is 24 hours after cluster bootstrap.
Expand All @@ -525,7 +447,7 @@ resource "argocd_cluster" "cluster_one" {
}
}
}
resource "argocd_cluster" "cluster_two" {
resource "argocd_cluster" "cluster_two_no_name" {
server = "https://kubernetes.default.svc.cluster.local"
config {
# Uses Kind's bootstrap token whose ttl is 24 hours after cluster bootstrap.
Expand All @@ -538,11 +460,11 @@ resource "argocd_cluster" "cluster_two" {
`
}

func testAccArgoCDClusterTwiceWithSameServerNoNamesTrimmed(server, server2 string) string {
return fmt.Sprintf(`
resource "argocd_cluster" "cluster_one" {
func testAccArgoCDClusterTwiceWithSameLogicalServer() string {
return `
resource "argocd_cluster" "cluster_with_trailing_slash" {
name = "server"
server = "%s"
server = "https://kubernetes.default.svc.cluster.local/"
config {
# Uses Kind's bootstrap token whose ttl is 24 hours after cluster bootstrap.
bearer_token = "abcdef.0123456789abcdef"
Expand All @@ -551,18 +473,17 @@ resource "argocd_cluster" "cluster_one" {
}
}
}
resource "argocd_cluster" "cluster_two" {
resource "argocd_cluster" "cluster_with_no_trailing_slash" {
name = "server"
server = "%s"
server = "https://kubernetes.default.svc.cluster.local"
config {
# Uses Kind's bootstrap token whose ttl is 24 hours after cluster bootstrap.
bearer_token = "abcdef.0123456789abcdef"
tls_client_config {
insecure = true
}
}
}
`, server, server2)
}`
}

func testAccArgoCDClusterMetadata_addLabels(clusterName string) string {
Expand Down

0 comments on commit cb8528d

Please sign in to comment.