From 647debc29b08aac702770955753d482662e41e17 Mon Sep 17 00:00:00 2001 From: Alexander Backlund Date: Fri, 25 Feb 2022 13:20:39 +0000 Subject: [PATCH] add: project scope support for clusters (#143) * tests: add precheck function for feature support Useful for acceptance tests that should only be run when specific features are supported by ArgoCD version. If a feature is not supported the test is skipped, not failed. For local testing, you now have to add the `ARGOCD_VERSION` environment variable until a better long-term solution is found. * add: project scope support for clusters --- .github/workflows/tests.yml | 2 ++ argocd/features.go | 2 ++ argocd/provider_test.go | 19 +++++++++++ argocd/resource_argocd_cluster.go | 46 ++++++++++++++++++++++++++ argocd/resource_argocd_cluster_test.go | 46 ++++++++++++++++++++++++++ argocd/schema_cluster.go | 5 +++ argocd/structure_cluster.go | 5 +++ docs/resources/cluster.md | 1 + 8 files changed, 126 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d7a0052c..329f9782 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -49,4 +49,6 @@ jobs: netstat -tulpn - name: Run acceptance tests + env: + ARGOCD_VERSION: ${{ matrix.argocd_version }} run: sh scripts/testacc.sh diff --git a/argocd/features.go b/argocd/features.go index c34cc851..c71ce6aa 100644 --- a/argocd/features.go +++ b/argocd/features.go @@ -23,6 +23,7 @@ const ( featureIgnoreDiffJQPathExpressions featureRepositoryGet featureTokenIDs + featureProjectScopedClusters ) var ( @@ -31,6 +32,7 @@ var ( featureIgnoreDiffJQPathExpressions: semver.MustParse("2.1.0"), featureRepositoryGet: semver.MustParse("1.6.0"), featureTokenIDs: semver.MustParse("1.5.3"), + featureProjectScopedClusters: semver.MustParse("2.2.0"), } ) diff --git a/argocd/provider_test.go b/argocd/provider_test.go index 70249cd3..3624817f 100644 --- a/argocd/provider_test.go +++ b/argocd/provider_test.go @@ -4,6 +4,7 @@ import ( "os" "testing" + "github.com/Masterminds/semver" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -41,3 +42,21 @@ func testAccPreCheck(t *testing.T) { t.Fatal("ARGOCD_INSECURE should be set for acceptance tests") } } + +func testAccPreCheckFeatureSupported(t *testing.T, feature int) { + v := os.Getenv("ARGOCD_VERSION") + if v == "" { + t.Skip("ARGOCD_VERSION must be set set for feature supported acceptance tests") + } + serverVersion, err := semver.NewVersion(v) + if err != nil { + t.Fatalf("could not parse ARGOCD_VERSION as semantic version: %s", v) + } + versionConstraint, ok := featureVersionConstraintsMap[feature] + if !ok { + t.Fatal("feature constraint is not handled by the provider") + } + if i := versionConstraint.Compare(serverVersion); i == 1 { + t.Skipf("version %s does not support feature", v) + } +} diff --git a/argocd/resource_argocd_cluster.go b/argocd/resource_argocd_cluster.go index 602616c2..ec5597b3 100644 --- a/argocd/resource_argocd_cluster.go +++ b/argocd/resource_argocd_cluster.go @@ -47,6 +47,29 @@ func resourceArgoCDClusterCreate(ctx context.Context, d *schema.ResourceData, me } } + + featureProjectScopedClustersSupported, err := server.isFeatureSupported(featureProjectScopedClusters) + if err != nil { + return []diag.Diagnostic{ + { + Severity: diag.Error, + Summary: "feature not supported", + Detail: err.Error(), + }, + } + } + if !featureProjectScopedClustersSupported && cluster.Project != "" { + return []diag.Diagnostic{ + { + Severity: diag.Error, + Summary: fmt.Sprintf( + "cluster project is only supported from ArgoCD %s onwards", + featureVersionConstraintsMap[featureProjectScopedClusters].String()), + Detail: "See https://argo-cd.readthedocs.io/en/stable/user-guide/projects/#project-scoped-repositories-and-clusters", + }, + } + } + c, err := client.Create(ctx, &clusterClient.ClusterCreateRequest{ Cluster: cluster, Upsert: true}) if err != nil { @@ -128,6 +151,29 @@ func resourceArgoCDClusterUpdate(ctx context.Context, d *schema.ResourceData, me }, } } + + featureProjectScopedClustersSupported, err := server.isFeatureSupported(featureProjectScopedClusters) + if err != nil { + return []diag.Diagnostic{ + { + Severity: diag.Error, + Summary: "feature not supported", + Detail: err.Error(), + }, + } + } + if !featureProjectScopedClustersSupported && cluster.Project != "" { + return []diag.Diagnostic{ + { + Severity: diag.Error, + Summary: fmt.Sprintf( + "cluster project is only supported from ArgoCD %s onwards", + featureVersionConstraintsMap[featureProjectScopedClusters].String()), + Detail: "See https://argo-cd.readthedocs.io/en/stable/user-guide/projects/#project-scoped-repositories-and-clusters", + }, + } + } + _, err = client.Update(ctx, &clusterClient.ClusterUpdateRequest{Cluster: cluster}) if err != nil { if strings.Contains(err.Error(), "NotFound") { diff --git a/argocd/resource_argocd_cluster_test.go b/argocd/resource_argocd_cluster_test.go index 6ce246a0..8d2608fb 100644 --- a/argocd/resource_argocd_cluster_test.go +++ b/argocd/resource_argocd_cluster_test.go @@ -71,6 +71,35 @@ func TestAccArgoCDCluster(t *testing.T) { }) } +func TestAccArgoCDCluster_projectScope(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckFeatureSupported(t, featureProjectScopedClusters) }, + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccArgoCDClusterProjectScope(acctest.RandString(10), "myproject1"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "argocd_cluster.project_scope", + "info.0.connection_state.0.status", + "Successful", + ), + resource.TestCheckResourceAttr( + "argocd_cluster.project_scope", + "config.0.tls_client_config.0.insecure", + "true", + ), + resource.TestCheckResourceAttr( + "argocd_cluster.project_scope", + "project", + "myproject1", + ), + ), + }, + }, + }) +} + func testAccArgoCDClusterBearerToken(clusterName string) string { return fmt.Sprintf(` resource "argocd_cluster" "simple" { @@ -118,6 +147,23 @@ EOT `, clusterName, rc.KeyData, rc.CertData, rc.CAData, rc.ServerName) } +func testAccArgoCDClusterProjectScope(clusterName, projectName string) string { + return fmt.Sprintf(` +resource "argocd_cluster" "project_scope" { + server = "https://kubernetes.default.svc.cluster.local" + name = "%s" + project = "%s" + config { + # Uses Kind's bootstrap token whose ttl is 24 hours after cluster bootstrap. + bearer_token = "abcdef.0123456789abcdef" + tls_client_config { + insecure = true + } + } +} +`, clusterName, projectName) +} + // getInternalRestConfig returns the internal Kubernetes cluster REST config. func getInternalRestConfig() (*rest.Config, error) { rc := &rest.Config{} diff --git a/argocd/schema_cluster.go b/argocd/schema_cluster.go index 5e7bb79e..8491b5c7 100644 --- a/argocd/schema_cluster.go +++ b/argocd/schema_cluster.go @@ -205,5 +205,10 @@ func clusterSchema() map[string]*schema.Schema { }, }, }, + "project": { + Type: schema.TypeString, + Description: "Add cluster scoped to project", + Optional: true, + }, } } diff --git a/argocd/structure_cluster.go b/argocd/structure_cluster.go index 015b16e2..41551b35 100644 --- a/argocd/structure_cluster.go +++ b/argocd/structure_cluster.go @@ -35,6 +35,10 @@ func expandCluster(d *schema.ResourceData) (*application.Cluster, error) { cluster.Annotations = m.Annotations cluster.Labels = m.Labels + if v, ok := d.GetOk("project"); ok { + cluster.Project = v.(string) + } + return cluster, err } @@ -113,6 +117,7 @@ func flattenCluster(cluster *application.Cluster, d *schema.ResourceData) error "namespaces": cluster.Namespaces, "info": flattenClusterInfo(cluster.Info), "config": flattenClusterConfig(cluster.Config, d), + "project": cluster.Project, } if cluster.Shard != nil { r["shard"] = convertInt64PointerToString(cluster.Shard) diff --git a/docs/resources/cluster.md b/docs/resources/cluster.md index 9a09765a..6244f3a9 100644 --- a/docs/resources/cluster.md +++ b/docs/resources/cluster.md @@ -121,6 +121,7 @@ resource "argocd_cluster" "eks" { * `namespaces` - (Optional) Holds list of namespaces which are accessible in that cluster. Cluster level resources would be ignored if namespace list is not empty.. * `config` - (Optional) The configuration specification, nested attributes are documented below. * `metadata` - (Optional) Cluster metadata, nested attributes are documented below. +* `project` - (Optional) Scope cluster to ArgoCD project. If omitted, cluster will be global. Requires ArgoCD 2.2.0 onwards. The `config` block can have the following attributes: