From 1917df8e6e7ff1dc9f271ee98d6bebac6f71c00e Mon Sep 17 00:00:00 2001 From: MrLuje Date: Sun, 3 Jul 2022 20:08:57 +0200 Subject: [PATCH] feat(application): add SkipCrds to helm source (#169) * feat(application): add SkipCrds to helm source * feat: add StateUpgrader for skip_crds default value * test: add importer tests * add check featureApplicationHelmSkipCrds + v2.3.0 in test matrix * doc: add application helm skip_crds * test: missing 2.3.0 test condition * fix old redis image * add comment + rename test * fix(test): increase "go test" timeout from 5m to 8m --- .github/workflows/tests.yml | 2 +- argocd/features.go | 2 + argocd/provider_test.go | 20 + argocd/resource_argocd_application.go | 64 ++- argocd/resource_argocd_application_test.go | 239 ++++++++++ argocd/resource_argocd_cluster.go | 1 - argocd/resource_argocd_cluster_test.go | 14 + argocd/resource_argocd_project.go | 1 - argocd/resource_argocd_project_test.go | 10 + argocd/resource_argocd_repository.go | 1 - .../resource_argocd_repository_credentials.go | 1 - ...urce_argocd_repository_credentials_test.go | 6 + argocd/resource_argocd_repository_test.go | 16 + argocd/schema_application.go | 447 +++++++++++++++++- argocd/schema_application_test.go | 74 +++ argocd/structure_application.go | 4 + docs/resources/application.md | 1 + scripts/testacc.sh | 2 +- 18 files changed, 895 insertions(+), 10 deletions(-) create mode 100644 argocd/schema_application_test.go diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 329f9782..34e352c2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - argocd_version: ["v2.2.5", "v2.1.10"] + argocd_version: ["v2.3.0", "v2.2.5", "v2.1.10"] steps: - uses: actions/checkout@v2 - uses: actions/setup-go@v1 diff --git a/argocd/features.go b/argocd/features.go index e8892135..825a68cc 100644 --- a/argocd/features.go +++ b/argocd/features.go @@ -27,6 +27,7 @@ const ( featureProjectScopedClusters featureClusterMetadata featureRepositoryCertificates + featureApplicationHelmSkipCrds ) var ( @@ -38,6 +39,7 @@ var ( featureProjectScopedClusters: semver.MustParse("2.2.0"), featureClusterMetadata: semver.MustParse("2.2.0"), featureRepositoryCertificates: semver.MustParse("1.2.0"), + featureApplicationHelmSkipCrds: semver.MustParse("2.3.0"), } ) diff --git a/argocd/provider_test.go b/argocd/provider_test.go index 3624817f..f1d99455 100644 --- a/argocd/provider_test.go +++ b/argocd/provider_test.go @@ -43,6 +43,7 @@ func testAccPreCheck(t *testing.T) { } } +// Skip test if feature is not supported func testAccPreCheckFeatureSupported(t *testing.T, feature int) { v := os.Getenv("ARGOCD_VERSION") if v == "" { @@ -60,3 +61,22 @@ func testAccPreCheckFeatureSupported(t *testing.T, feature int) { t.Skipf("version %s does not support feature", v) } } + +// Skip test if feature is supported +func testAccPreCheckFeatureNotSupported(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_application.go b/argocd/resource_argocd_application.go index a2eda3b3..34afe944 100644 --- a/argocd/resource_argocd_application.go +++ b/argocd/resource_argocd_application.go @@ -3,10 +3,11 @@ package argocd import ( "context" "fmt" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "strings" "time" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + applicationClient "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" application "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/argoproj/gitops-engine/pkg/health" @@ -21,13 +22,12 @@ func resourceArgoCDApplication() *schema.Resource { ReadContext: resourceArgoCDApplicationRead, UpdateContext: resourceArgoCDApplicationUpdate, DeleteContext: resourceArgoCDApplicationDelete, - // TODO: add importer acceptance tests Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, Schema: map[string]*schema.Schema{ "metadata": metadataSchema("applications.argoproj.io"), - "spec": applicationSpecSchema(), + "spec": applicationSpecSchemaV1(), "wait": { Type: schema.TypeBool, Description: "Upon application creation or update, wait for application health/sync status to be healthy/Synced, upon application deletion, wait for application to be removed, when set to true.", @@ -41,6 +41,14 @@ func resourceArgoCDApplication() *schema.Resource { Default: true, }, }, + SchemaVersion: 1, + StateUpgraders: []schema.StateUpgrader{ + { + Type: resourceArgoCDApplicationV0().CoreConfigSchema().ImpliedType(), + Upgrade: resourceArgoCDApplicationStateUpgradeV0, + Version: 0, + }, + }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(5 * time.Minute), Update: schema.DefaultTimeout(5 * time.Minute), @@ -140,6 +148,31 @@ func resourceArgoCDApplicationCreate(ctx context.Context, d *schema.ResourceData } } + featureApplicationHelmSkipCrdsSupported, err := server.isFeatureSupported(featureApplicationHelmSkipCrds) + if err != nil { + return []diag.Diagnostic{ + { + Severity: diag.Error, + Summary: "feature not supported", + Detail: err.Error(), + }, + } + } + + if !featureApplicationHelmSkipCrdsSupported { + _, skipCrdsOk := d.GetOk("spec.0.source.0.helm.0.skip_crds") + if skipCrdsOk { + return []diag.Diagnostic{ + { + Severity: diag.Error, + Summary: fmt.Sprintf( + "application helm skip_crds is only supported from ArgoCD %s onwards", + featureVersionConstraintsMap[featureApplicationHelmSkipCrds].String()), + }, + } + } + } + app, err = c.Create(ctx, &applicationClient.ApplicationCreateRequest{ Application: application.Application{ @@ -322,6 +355,31 @@ func resourceArgoCDApplicationUpdate(ctx context.Context, d *schema.ResourceData } } + featureApplicationHelmSkipCrdsSupported, err := server.isFeatureSupported(featureApplicationHelmSkipCrds) + if err != nil { + return []diag.Diagnostic{ + { + Severity: diag.Error, + Summary: "feature not supported", + Detail: err.Error(), + }, + } + } + + if !featureApplicationHelmSkipCrdsSupported { + _, skipCrdsOk := d.GetOk("spec.0.source.0.helm.0.skip_crds") + if skipCrdsOk { + return []diag.Diagnostic{ + { + Severity: diag.Error, + Summary: fmt.Sprintf( + "application helm skip_crds is only supported from ArgoCD %s onwards", + featureVersionConstraintsMap[featureApplicationHelmSkipCrds].String()), + }, + } + } + } + app, err := c.Get(ctx, &applicationClient.ApplicationQuery{ Name: &appName, }) diff --git a/argocd/resource_argocd_application_test.go b/argocd/resource_argocd_application_test.go index ea852760..4e73790b 100644 --- a/argocd/resource_argocd_application_test.go +++ b/argocd/resource_argocd_application_test.go @@ -42,6 +42,12 @@ ingress: "metadata.0.uid", ), }, + { + ResourceName: "argocd_application.simple", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"wait", "cascade"}, + }, // Check with the same name for rapid application recreation robustness { Config: testAccArgoCDApplicationSimple(commonName), @@ -92,6 +98,12 @@ ingress: ), ), }, + { + ResourceName: "argocd_application.helm", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"wait", "cascade", "metadata.0.generation", "metadata.0.resource_version"}, + }, { Config: testAccArgoCDApplicationKustomize( acctest.RandomWithPrefix("test-acc")), @@ -112,6 +124,12 @@ ingress: ), ), }, + { + ResourceName: "argocd_application.kustomize", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"wait", "cascade", "metadata.0.generation", "metadata.0.resource_version"}, + }, { Config: testAccArgoCDApplicationDirectory( acctest.RandomWithPrefix("test-acc")), @@ -172,6 +190,12 @@ ingress: ), ), }, + { + ResourceName: "argocd_application.directory", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"wait", "cascade", "metadata.0.generation", "metadata.0.resource_version"}, + }, { Config: testAccArgoCDApplicationSyncPolicy( acctest.RandomWithPrefix("test-acc")), @@ -217,6 +241,12 @@ ingress: ), ), }, + { + ResourceName: "argocd_application.sync_policy", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"wait", "cascade", "metadata.0.generation", "metadata.0.resource_version"}, + }, { Config: testAccArgoCDApplicationIgnoreDifferences( acctest.RandomWithPrefix("test-acc")), @@ -237,6 +267,12 @@ ingress: ), ), }, + { + ResourceName: "argocd_application.ignore_differences", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"wait", "cascade"}, + }, { SkipFunc: testAccSkipFeatureIgnoreDiffJQPathExpressions, Config: testAccArgoCDApplicationIgnoreDiffJQPathExpressions( @@ -258,6 +294,12 @@ ingress: ), ), }, + { + ResourceName: "argocd_application.ignore_differences_jqpe", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"wait", "cascade"}, + }, { Config: testAccArgoCDApplicationSimpleRevisionHistory(commonName, revisionHistoryLimit), Check: resource.ComposeTestCheckFunc( @@ -637,6 +679,128 @@ func TestProvider_headers(t *testing.T) { }) } +func TestAccArgoCDApplication_SkipCrds_NotSupported_On_OlderVersions(t *testing.T) { + name := acctest.RandomWithPrefix("test-acc-crds") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckFeatureNotSupported(t, featureApplicationHelmSkipCrds) }, + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + // Create tests + { + Config: testAccArgoCDApplicationSkipCrds(acctest.RandomWithPrefix("test-acc-crds"), true), + ExpectError: regexp.MustCompile("application helm skip_crds is only supported from ArgoCD"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "argocd_application.crds", + "metadata.0.uid", + ), + resource.TestCheckResourceAttr( + "argocd_application.crds", + "spec.0.source.0.helm.0.skip_crds", + "true", + ), + ), + }, + // Update tests + { + Config: testAccArgoCDApplicationSkipCrds_NoSkip(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "argocd_application.crds", + "metadata.0.uid", + ), + resource.TestCheckResourceAttr( + "argocd_application.crds", + "spec.0.source.0.helm.0.skip_crds", + "false", + ), + ), + }, + { + Config: testAccArgoCDApplicationSkipCrds(name, false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "argocd_application.crds", + "metadata.0.uid", + ), + resource.TestCheckResourceAttr( + "argocd_application.crds", + "spec.0.source.0.helm.0.skip_crds", + "false", + ), + ), + }, + { + Config: testAccArgoCDApplicationSkipCrds(name, true), + ExpectError: regexp.MustCompile("application helm skip_crds is only supported from ArgoCD"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "argocd_application.crds", + "metadata.0.uid", + ), + resource.TestCheckResourceAttr( + "argocd_application.crds", + "spec.0.source.0.helm.0.skip_crds", + "true", + ), + ), + }, + }}) +} + +func TestAccArgoCDApplication_SkipCrds(t *testing.T) { + name := acctest.RandomWithPrefix("test-acc-crds") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckFeatureSupported(t, featureApplicationHelmSkipCrds) }, + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccArgoCDApplicationSkipCrds_NoSkip(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "argocd_application.crds", + "metadata.0.uid", + ), + resource.TestCheckResourceAttr( + "argocd_application.crds", + "spec.0.source.0.helm.0.skip_crds", + "false", + ), + ), + }, + { + Config: testAccArgoCDApplicationSkipCrds(name, true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "argocd_application.crds", + "metadata.0.uid", + ), + resource.TestCheckResourceAttr( + "argocd_application.crds", + "spec.0.source.0.helm.0.skip_crds", + "true", + ), + ), + }, + { + Config: testAccArgoCDApplicationSkipCrds(name, false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "argocd_application.crds", + "metadata.0.uid", + ), + resource.TestCheckResourceAttr( + "argocd_application.crds", + "spec.0.source.0.helm.0.skip_crds", + "false", + ), + ), + }, + }}) +} + func testAccArgoCDApplicationSimple(name string) string { return fmt.Sprintf(` resource "argocd_application" "simple" { @@ -1420,6 +1584,81 @@ resource "argocd_application" "info" { `, name) } +func testAccArgoCDApplicationSkipCrds(name string, SkipCrds bool) string { + return fmt.Sprintf(` +resource "argocd_application" "crds" { + metadata { + name = "%s" + namespace = "argocd" + labels = { + acceptance = "true" + } + } + + spec { + source { + repo_url = "https://charts.bitnami.com/bitnami" + chart = "redis" + target_revision = "16.9.11" + helm { + parameter { + name = "image.tag" + value = "6.2.5" + } + parameter { + name = "architecture" + value = "standalone" + } + release_name = "testing" + skip_crds = %t + } + } + destination { + server = "https://kubernetes.default.svc" + namespace = "default" + } + } +} + `, name, SkipCrds) +} + +func testAccArgoCDApplicationSkipCrds_NoSkip(name string) string { + return fmt.Sprintf(` +resource "argocd_application" "crds" { + metadata { + name = "%s" + namespace = "argocd" + labels = { + acceptance = "true" + } + } + + spec { + source { + repo_url = "https://charts.bitnami.com/bitnami" + chart = "redis" + target_revision = "16.9.11" + helm { + parameter { + name = "image.tag" + value = "6.2.5" + } + parameter { + name = "architecture" + value = "standalone" + } + release_name = "testing" + } + } + destination { + server = "https://kubernetes.default.svc" + namespace = "default" + } + } +} + `, name) +} + func testAccSkipFeatureIgnoreDiffJQPathExpressions() (bool, error) { p, _ := testAccProviders["argocd"]() _ = p.Configure(context.Background(), &terraform.ResourceConfig{}) diff --git a/argocd/resource_argocd_cluster.go b/argocd/resource_argocd_cluster.go index 934bfec7..bb7a2589 100644 --- a/argocd/resource_argocd_cluster.go +++ b/argocd/resource_argocd_cluster.go @@ -16,7 +16,6 @@ func resourceArgoCDCluster() *schema.Resource { ReadContext: resourceArgoCDClusterRead, UpdateContext: resourceArgoCDClusterUpdate, DeleteContext: resourceArgoCDClusterDelete, - // TODO: add importer tests Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, diff --git a/argocd/resource_argocd_cluster_test.go b/argocd/resource_argocd_cluster_test.go index 984fef20..849db05a 100644 --- a/argocd/resource_argocd_cluster_test.go +++ b/argocd/resource_argocd_cluster_test.go @@ -48,6 +48,13 @@ func TestAccArgoCDCluster(t *testing.T) { ), ), }, + //TODO: not working on CI every time + // { + // ResourceName: "argocd_cluster.simple", + // ImportState: true, + // ImportStateVerify: true, + // ImportStateVerifyIgnore: []string{"config.0.bearer_token", "config.0.tls_client_config"}, + // }, { Config: testAccArgoCDClusterTLSCertificate(t, acctest.RandString(10)), Check: resource.ComposeTestCheckFunc( @@ -97,6 +104,13 @@ func TestAccArgoCDCluster_projectScope(t *testing.T) { ), ), }, + //TODO: not working on CI every time + // { + // ResourceName: "argocd_cluster.project_scope", + // ImportState: true, + // ImportStateVerify: true, + // ImportStateVerifyIgnore: []string{"config.0.bearer_token", "config.0.tls_client_config"}, + // }, }, }) } diff --git a/argocd/resource_argocd_project.go b/argocd/resource_argocd_project.go index f432dfd0..b7bdebfa 100644 --- a/argocd/resource_argocd_project.go +++ b/argocd/resource_argocd_project.go @@ -19,7 +19,6 @@ func resourceArgoCDProject() *schema.Resource { ReadContext: resourceArgoCDProjectRead, UpdateContext: resourceArgoCDProjectUpdate, DeleteContext: resourceArgoCDProjectDelete, - // TODO: add importer acceptance tests Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, diff --git a/argocd/resource_argocd_project_test.go b/argocd/resource_argocd_project_test.go index 6cfb0a66..69687c00 100644 --- a/argocd/resource_argocd_project_test.go +++ b/argocd/resource_argocd_project_test.go @@ -53,6 +53,11 @@ func TestAccArgoCDProject(t *testing.T) { "metadata.0.uid", ), }, + { + ResourceName: "argocd_project.simple", + ImportState: true, + ImportStateVerify: true, + }, // Check with the same name for rapid project recreation robustness { Config: testAccArgoCDProjectSimple(name), @@ -149,6 +154,11 @@ func TestAccArgoCDProjectUpdateAddRole(t *testing.T) { ), ), }, + { + ResourceName: "argocd_project.simple", + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccArgoCDProjectSimpleWithRole(name), Check: resource.ComposeTestCheckFunc( diff --git a/argocd/resource_argocd_repository.go b/argocd/resource_argocd_repository.go index ebac0422..0ebf904f 100644 --- a/argocd/resource_argocd_repository.go +++ b/argocd/resource_argocd_repository.go @@ -19,7 +19,6 @@ func resourceArgoCDRepository() *schema.Resource { ReadContext: resourceArgoCDRepositoryRead, UpdateContext: resourceArgoCDRepositoryUpdate, DeleteContext: resourceArgoCDRepositoryDelete, - // TODO: add importer acceptance tests Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, diff --git a/argocd/resource_argocd_repository_credentials.go b/argocd/resource_argocd_repository_credentials.go index 2a112cfe..6ebf611d 100644 --- a/argocd/resource_argocd_repository_credentials.go +++ b/argocd/resource_argocd_repository_credentials.go @@ -17,7 +17,6 @@ func resourceArgoCDRepositoryCredentials() *schema.Resource { ReadContext: resourceArgoCDRepositoryCredentialsRead, UpdateContext: resourceArgoCDRepositoryCredentialsUpdate, DeleteContext: resourceArgoCDRepositoryCredentialsDelete, - // TODO: add importer acceptance tests Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, diff --git a/argocd/resource_argocd_repository_credentials_test.go b/argocd/resource_argocd_repository_credentials_test.go index 8f5e0401..dccd09f6 100644 --- a/argocd/resource_argocd_repository_credentials_test.go +++ b/argocd/resource_argocd_repository_credentials_test.go @@ -32,6 +32,12 @@ func TestAccArgoCDRepositoryCredentials(t *testing.T) { "git", ), }, + { + ResourceName: "argocd_repository_credentials.simple", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"ssh_private_key"}, + }, { Config: testAccArgoCDRepositoryCredentialsRepositoryCoexistence(), Check: testCheckMultipleResourceAttr( diff --git a/argocd/resource_argocd_repository_test.go b/argocd/resource_argocd_repository_test.go index d28f55c5..d90af327 100644 --- a/argocd/resource_argocd_repository_test.go +++ b/argocd/resource_argocd_repository_test.go @@ -22,6 +22,11 @@ func TestAccArgoCDRepository(t *testing.T) { "Successful", ), }, + { + ResourceName: "argocd_repository.simple", + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccArgoCDRepositoryHelm(), Check: resource.TestCheckResourceAttr( @@ -30,6 +35,11 @@ func TestAccArgoCDRepository(t *testing.T) { "Successful", ), }, + { + ResourceName: "argocd_repository.helm", + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccArgoCDRepositoryPublicUsageInApplication(acctest.RandString(10)), Check: resource.TestCheckResourceAttrSet( @@ -52,6 +62,12 @@ func TestAccArgoCDRepository(t *testing.T) { ), ), }, + { + ResourceName: "argocd_repository.private", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"ssh_private_key"}, + }, { Config: testAccArgoCDRepositoryMultiplePrivateGitSSH(10), Check: testCheckMultipleResourceAttr( diff --git a/argocd/schema_application.go b/argocd/schema_application.go index adecff81..43b17351 100644 --- a/argocd/schema_application.go +++ b/argocd/schema_application.go @@ -1,10 +1,417 @@ package argocd import ( + "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func applicationSpecSchema() *schema.Schema { +func applicationSpecSchemaV0() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MinItems: 1, + MaxItems: 1, + Description: "ArgoCD App application resource specs. Required attributes: destination, source.", + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "destination": { + Type: schema.TypeSet, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "server": { + Type: schema.TypeString, + Optional: true, + }, + "namespace": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + Description: "Name of the destination cluster which can be used instead of server.", + }, + }, + }, + }, + "source": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "repo_url": { + Type: schema.TypeString, + Required: true, + }, + "path": { + Type: schema.TypeString, + Optional: true, + // TODO: add validator to test path is not absolute + }, + "target_revision": { + Type: schema.TypeString, + Optional: true, + }, + "chart": { + Type: schema.TypeString, + Optional: true, + }, + "helm": { + Type: schema.TypeList, + MaxItems: 1, + MinItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value_files": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "values": { + Type: schema.TypeString, + Optional: true, + }, + "parameter": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + }, + "value": { + Type: schema.TypeString, + Optional: true, + }, + "force_string": { + Type: schema.TypeBool, + Optional: true, + Description: "force_string determines whether to tell Helm to interpret booleans and numbers as strings", + }, + }, + }, + }, + "release_name": { + Type: schema.TypeString, + Description: "The Helm release name. If omitted it will use the application name", + Optional: true, + }, + }, + }, + }, + "kustomize": { + Type: schema.TypeList, + MaxItems: 1, + MinItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name_prefix": { + Type: schema.TypeString, + Optional: true, + }, + "name_suffix": { + Type: schema.TypeString, + Optional: true, + }, + "version": { + Type: schema.TypeString, + Optional: true, + }, + "images": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "common_labels": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + ValidateFunc: validateMetadataLabels, + }, + "common_annotations": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + ValidateFunc: validateMetadataAnnotations, + }, + }, + }, + }, + "ksonnet": { + Type: schema.TypeList, + MaxItems: 1, + MinItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "environment": { + Type: schema.TypeString, + Optional: true, + }, + "parameters": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "component": { + Type: schema.TypeString, + Optional: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "value": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + "directory": { + Type: schema.TypeList, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + // Avoid drift when recurse is explicitly set to false + // Also ignore the directory node if both recurse & jsonnet are not set or ignored + if k == "spec.0.source.0.directory.0.recurse" && old == "" && new == "false" { + return true + } + if k == "spec.0.source.0.directory.#" { + _, hasRecurse := d.GetOk("spec.0.source.0.directory.0.recurse") + _, hasJsonnet := d.GetOk("spec.0.source.0.directory.0.jsonnet") + + if !hasJsonnet && !hasRecurse { + return true + } + } + return false + }, + MaxItems: 1, + MinItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "recurse": { + Type: schema.TypeBool, + Optional: true, + }, + "jsonnet": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + MinItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ext_var": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + }, + "value": { + Type: schema.TypeString, + Optional: true, + }, + "code": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + "tla": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + }, + "value": { + Type: schema.TypeString, + Optional: true, + }, + "code": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "plugin": { + Type: schema.TypeList, + MaxItems: 1, + MinItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + }, + "env": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + }, + "value": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Description: "The application project, defaults to 'default'", + Default: "default", + }, + "sync_policy": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + MinItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "automated": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeBool}, + }, + "sync_options": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + // TODO: add a validator + }, + }, + "retry": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "limit": { + Type: schema.TypeString, + Description: "Max number of allowed sync retries, as a string", + Optional: true, + }, + "backoff": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + }, + }, + }, + "ignore_difference": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "group": { + Type: schema.TypeString, + Optional: true, + }, + "kind": { + Type: schema.TypeString, + Optional: true, + }, + "name": { + Type: schema.TypeString, + Optional: true, + }, + "namespace": { + Type: schema.TypeString, + Optional: true, + }, + "json_pointers": { + Type: schema.TypeSet, + Set: schema.HashString, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "jq_path_expressions": { + Type: schema.TypeSet, + Set: schema.HashString, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "info": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + }, + "value": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "revision_history_limit": { + Type: schema.TypeInt, + Optional: true, + Default: 10, + }, + }, + }, + } +} + +func applicationSpecSchemaV1() *schema.Schema { return &schema.Schema{ Type: schema.TypeList, MinItems: 1, @@ -105,6 +512,11 @@ func applicationSpecSchema() *schema.Schema { Description: "The Helm release name. If omitted it will use the application name", Optional: true, }, + "skip_crds": { + Type: schema.TypeBool, + Description: "Helm installs custom resource definitions in the crds folder by default if they are not existing. If needed, it is possible to skip the CRD installation step with this flag", + Optional: true, + }, }, }, }, @@ -409,3 +821,36 @@ func applicationSpecSchema() *schema.Schema { }, } } + +func resourceArgoCDApplicationV0() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "metadata": metadataSchema("appprojects.argoproj.io"), + "spec": applicationSpecSchemaV0(), + }, + } +} + +func resourceArgoCDApplicationStateUpgradeV0(_ context.Context, rawState map[string]interface{}, _ interface{}) (map[string]interface{}, error) { + _spec, ok := rawState["spec"].([]interface{}) + if !ok || len(_spec) == 0 { + return rawState, nil + } + + spec := _spec[0].(map[string]interface{}) + _source, ok := spec["source"].([]interface{}) + if !ok || len(_source) == 0 { + return rawState, nil + } + + source := _source[0].(map[string]interface{}) + _helm, ok := source["helm"].([]interface{}) + if !ok || len(_helm) == 0 { + return rawState, nil + } + + helm := _helm[0].(map[string]interface{}) + helm["skip_crds"] = false + + return rawState, nil +} diff --git a/argocd/schema_application_test.go b/argocd/schema_application_test.go new file mode 100644 index 00000000..261f10b5 --- /dev/null +++ b/argocd/schema_application_test.go @@ -0,0 +1,74 @@ +package argocd + +import ( + "context" + "reflect" + "testing" +) + +func TestUpgradeSchemaApplication_V0V1_Default_SkipCrds(t *testing.T) { + v0 := map[string]interface{}{ + "spec": []interface{}{map[string]interface{}{ + "source": []interface{}{map[string]interface{}{ + "repo_url": "https://charts.bitnami.com/bitnami", + "chart": "redis", + "target_revision": "16.9.11", + + "helm": []interface{}{map[string]interface{}{ + "release_name": "testing", + }}, + }}, + "destination": []interface{}{map[string]interface{}{ + "server": "https://kubernetes.default.svc", + "namespace": "default", + }}}, + }, + } + + v1 := map[string]interface{}{ + "spec": []interface{}{map[string]interface{}{ + "source": []interface{}{map[string]interface{}{ + "repo_url": "https://charts.bitnami.com/bitnami", + "chart": "redis", + "target_revision": "16.9.11", + + "helm": []interface{}{map[string]interface{}{ + "release_name": "testing", + "skip_crds": false, + }}, + }}, + "destination": []interface{}{map[string]interface{}{ + "server": "https://kubernetes.default.svc", + "namespace": "default", + }}}, + }, + } + + actual, _ := resourceArgoCDApplicationStateUpgradeV0(context.TODO(), v0, nil) + + if !reflect.DeepEqual(v1, actual) { + t.Fatalf("\n\nexpected:\n\n%#v\n\ngot:\n\n%#v\n\n", v1, actual) + } +} + +func TestUpgradeSchemaApplication_V0V1_Default_SkipCrds_NoChange(t *testing.T) { + v0 := map[string]interface{}{ + "spec": []interface{}{map[string]interface{}{ + "source": []interface{}{map[string]interface{}{ + "repo_url": "https://charts.bitnami.com/bitnami", + "chart": "redis", + "target_revision": "16.9.11", + }}, + "destination": []interface{}{map[string]interface{}{ + "server": "https://kubernetes.default.svc", + "namespace": "default", + }}}, + }, + } + + actual, _ := resourceArgoCDApplicationStateUpgradeV0(context.TODO(), v0, nil) + + if !reflect.DeepEqual(v0, actual) { + t.Fatalf("\n\nexpected:\n\n%#v\n\ngot:\n\n%#v\n\n", v0, actual) + } +} diff --git a/argocd/structure_application.go b/argocd/structure_application.go index 4f82e8f2..02253fc8 100644 --- a/argocd/structure_application.go +++ b/argocd/structure_application.go @@ -259,6 +259,9 @@ func expandApplicationSourceHelm(in []interface{}) *application.ApplicationSourc result.Parameters = append(result.Parameters, parameter) } } + if v, ok := a["skip_crds"]; ok { + result.SkipCrds = v.(bool) + } return result } @@ -672,6 +675,7 @@ func flattenApplicationSourceHelm(as []*application.ApplicationSourceHelm) ( result = append(result, map[string]interface{}{ "parameter": parameters, "release_name": a.ReleaseName, + "skip_crds": a.SkipCrds, "value_files": a.ValueFiles, "values": a.Values, }) diff --git a/docs/resources/application.md b/docs/resources/application.md index f8709db3..236e025f 100644 --- a/docs/resources/application.md +++ b/docs/resources/application.md @@ -197,6 +197,7 @@ The `helm` block has the following attributes: * `value_files` - (Optional) list of Helm value files to use when generating a template. * `values` - (Optional) Helm values, typically defined as a block. * `release_name` - (Optional) the Helm release name. If omitted it will use the application name. +* `skip_crds` - (Optional) Helm installs custom resource definitions in the crds folder by default if they are not existing. If needed, it is possible to skip the CRD installation step with this flag. * `parameter` - (Optional) parameter to the Helm template. Can be repeated multiple times. Structure is documented below. Each `helm/parameter` block has the following attributes: diff --git a/scripts/testacc.sh b/scripts/testacc.sh index ccdb6b41..488b03d0 100644 --- a/scripts/testacc.sh +++ b/scripts/testacc.sh @@ -5,4 +5,4 @@ ARGOCD_SERVER=127.0.0.1:8080 \ ARGOCD_AUTH_USERNAME=admin \ ARGOCD_AUTH_PASSWORD=acceptancetesting \ ARGOCD_CONTEXT=kind-argocd \ -TF_ACC=1 go test $(go list ./... |grep -v 'vendor') -v -timeout 5m +TF_ACC=1 go test $(go list ./... |grep -v 'vendor') -v -timeout 8m