diff --git a/client/template.go b/client/template.go index 56c6fa28..a026f8f2 100644 --- a/client/template.go +++ b/client/template.go @@ -37,7 +37,7 @@ type Template struct { Name string `json:"name"` Description string `json:"description"` OrganizationId string `json:"organizationId"` - Path string `json:"path"` + Path string `json:"path,omitempty" tfschema:",omitempty"` Revision string `json:"revision"` ProjectId string `json:"projectId"` ProjectIds []string `json:"projectIds"` @@ -58,6 +58,8 @@ type Template struct { FileName string `json:"fileName,omitempty" tfschema:",omitempty"` IsTerragruntRunAll bool `json:"isTerragruntRunAll"` IsAzureDevOps bool `json:"isAzureDevOps" tfschema:"is_azure_devops"` + IsHelmRepository bool `json:"isHelmRepository"` + HelmChartName string `json:"helmChartName,omitempty" tfschema:",omitempty"` } type TemplateCreatePayload struct { @@ -67,7 +69,7 @@ type TemplateCreatePayload struct { Description string `json:"description"` Name string `json:"name"` Repository string `json:"repository"` - Path string `json:"path"` + Path string `json:"path,omitempty"` IsGitLab bool `json:"isGitLab"` TokenName string `json:"tokenName"` TokenId string `json:"tokenId,omitempty"` @@ -84,6 +86,8 @@ type TemplateCreatePayload struct { FileName string `json:"fileName,omitempty"` IsTerragruntRunAll bool `json:"isTerragruntRunAll"` IsAzureDevOps bool `json:"isAzureDevOps" tfschema:"is_azure_devops"` + IsHelmRepository bool `json:"isHelmRepository"` + HelmChartName string `json:"helmChartName,omitempty"` } type TemplateAssignmentToProjectPayload struct { @@ -140,6 +144,20 @@ func (payload TemplateCreatePayload) Validate() error { return fmt.Errorf("file_name cannot be set when template type is: %s", payload.Type) } + if payload.IsHelmRepository || payload.HelmChartName != "" { + if payload.Type != "helm" { + return errors.New(`can't set is_helm_repository to "true" for non-helm template`) + } + + if payload.HelmChartName == "" { + return errors.New("helm_chart_name is required with helm repository") + } + + if !payload.IsHelmRepository { + return errors.New(`is_helm_repositroy set to "true" is required with "helm_chart_name"`) + } + } + return nil } diff --git a/env0/resource_template.go b/env0/resource_template.go index 076ff00f..78facf3a 100644 --- a/env0/resource_template.go +++ b/env0/resource_template.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "log" - "sort" "strings" "github.com/env0/terraform-provider-env0/client" @@ -21,6 +20,7 @@ var allowedTemplateTypes = []string{ "k8s", "workflow", "cloudformation", + "helm", } func getTemplateSchema(prefix string) map[string]*schema.Schema { @@ -33,14 +33,24 @@ func getTemplateSchema(prefix string) map[string]*schema.Schema { "is_bitbucket_server", "is_github_enterprise", "is_azure_devops", + "helm_chart_name", + "is_helm_repository", + "path", } allVCSAttributesBut := func(strs ...string) []string { - sort.Strings(strs) butAttrs := []string{} for _, attr := range allVCSAttributes { - if sort.SearchStrings(strs, attr) >= len(strs) { + var found bool + for _, str := range strs { + if str == attr { + found = true + break + } + } + + if !found { if prefix != "" { attr = prefix + attr } @@ -134,19 +144,19 @@ func getTemplateSchema(prefix string) map[string]*schema.Schema { Type: schema.TypeInt, Description: "the env0 application installation id on the relevant github repository", Optional: true, - ConflictsWith: allVCSAttributesBut("github_installation_id"), + ConflictsWith: allVCSAttributesBut("github_installation_id", "path"), }, "token_id": { Type: schema.TypeString, Description: "the git token id to be used", Optional: true, - ConflictsWith: allVCSAttributesBut("token_id", "gitlab_project_id", "is_azure_devops"), + ConflictsWith: allVCSAttributesBut("token_id", "gitlab_project_id", "is_azure_devops", "path"), }, "gitlab_project_id": { Type: schema.TypeInt, Description: "the project id of the relevant repository", Optional: true, - ConflictsWith: allVCSAttributesBut("token_id", "gitlab_project_id"), + ConflictsWith: allVCSAttributesBut("token_id", "gitlab_project_id", "path"), RequiredWith: requiredWith("token_id"), }, "terraform_version": { @@ -167,27 +177,27 @@ func getTemplateSchema(prefix string) map[string]*schema.Schema { Description: "true if this template uses gitlab enterprise repository", Optional: true, Default: "false", - ConflictsWith: allVCSAttributesBut("is_gitlab_enterprise"), + ConflictsWith: allVCSAttributesBut("is_gitlab_enterprise", "path"), }, "bitbucket_client_key": { Type: schema.TypeString, Description: "the bitbucket client key used for integration", Optional: true, - ConflictsWith: allVCSAttributesBut("bitbucket_client_key"), + ConflictsWith: allVCSAttributesBut("bitbucket_client_key", "path"), }, "is_bitbucket_server": { Type: schema.TypeBool, Description: "true if this template uses bitbucket server repository", Optional: true, Default: "false", - ConflictsWith: allVCSAttributesBut("is_bitbucket_server"), + ConflictsWith: allVCSAttributesBut("is_bitbucket_server", "path"), }, "is_github_enterprise": { Type: schema.TypeBool, Description: "true if this template uses github enterprise repository", Optional: true, Default: "false", - ConflictsWith: allVCSAttributesBut("is_github_enterprise"), + ConflictsWith: allVCSAttributesBut("is_github_enterprise", "path"), }, "file_name": { Type: schema.TypeString, @@ -205,9 +215,23 @@ func getTemplateSchema(prefix string) map[string]*schema.Schema { Optional: true, Description: "true if this template integrates with azure dev ops", Default: "false", - ConflictsWith: allVCSAttributesBut("is_azure_devops", "token_id"), + ConflictsWith: allVCSAttributesBut("is_azure_devops", "token_id", "path"), RequiredWith: requiredWith("token_id"), }, + "helm_chart_name": { + Type: schema.TypeString, + Optional: true, + Description: "the helm chart name. Required if is_helm_repository is set to 'true'", + ConflictsWith: allVCSAttributesBut("helm_chart_name", "is_helm_repository"), + }, + "is_helm_repository": { + Type: schema.TypeBool, + Optional: true, + Description: "true if this template integrates with a helm repository", + Default: "false", + ConflictsWith: allVCSAttributesBut("helm_chart_name", "is_helm_repository"), + RequiredWith: requiredWith("helm_chart_name"), + }, } if prefix == "" { diff --git a/env0/resource_template_test.go b/env0/resource_template_test.go index 3f77c58e..ca08ac90 100644 --- a/env0/resource_template_test.go +++ b/env0/resource_template_test.go @@ -365,6 +365,48 @@ func TestUnitTemplateResource(t *testing.T) { TerraformVersion: "0.15.1", IsAzureDevOps: true, } + + helmTemplate := client.Template{ + Id: "helmTemplate", + Name: "template0", + Description: "description0", + Repository: "env0/repo", + Type: "helm", + HelmChartName: "chart1", + IsHelmRepository: true, + Retry: client.TemplateRetry{ + OnDeploy: &client.TemplateRetryOn{ + Times: 2, + ErrorRegex: "RetryMeForDeploy.*", + }, + OnDestroy: &client.TemplateRetryOn{ + Times: 1, + ErrorRegex: "RetryMeForDestroy.*", + }, + }, + TerraformVersion: "0.12.24", + } + helmUpdatedTemplate := client.Template{ + Id: helmTemplate.Id, + Name: "new-name", + Description: "new-description", + Repository: "env0/repo-new", + Type: "helm", + HelmChartName: "chart1", + IsHelmRepository: true, + Retry: client.TemplateRetry{ + OnDeploy: &client.TemplateRetryOn{ + Times: 2, + ErrorRegex: "RetryMeForDeploy.*", + }, + OnDestroy: &client.TemplateRetryOn{ + Times: 1, + ErrorRegex: "RetryMeForDestroy.*", + }, + }, + TerraformVersion: "0.12.24", + } + fullTemplateResourceConfig := func(resourceType string, resourceName string, template client.Template) string { templateAsDictionary := map[string]interface{}{ "name": template.Name, @@ -435,6 +477,12 @@ func TestUnitTemplateResource(t *testing.T) { if template.IsAzureDevOps { templateAsDictionary["is_azure_devops"] = true } + if template.IsHelmRepository { + templateAsDictionary["is_helm_repository"] = true + } + if template.HelmChartName != "" { + templateAsDictionary["helm_chart_name"] = template.HelmChartName + } return resourceConfigCreate(resourceType, resourceName, templateAsDictionary) } @@ -453,6 +501,11 @@ func TestUnitTemplateResource(t *testing.T) { tokenIdAssertion = resource.TestCheckNoResourceAttr(resourceFullName, "token_id") } + helmChartNameAssertion := resource.TestCheckResourceAttr(resourceFullName, "helm_chart_name", template.HelmChartName) + if template.HelmChartName == "" { + helmChartNameAssertion = resource.TestCheckNoResourceAttr(resourceFullName, "helm_chart_name") + } + filenameAssertion := resource.TestCheckResourceAttr(resourceFullName, "file_name", template.FileName) if template.FileName == "" { filenameAssertion = resource.TestCheckNoResourceAttr(resourceFullName, "file_name") @@ -468,12 +521,16 @@ func TestUnitTemplateResource(t *testing.T) { githubInstallationIdAssertion = resource.TestCheckNoResourceAttr(resourceFullName, "github_installation_id") } + pathAssertion := resource.TestCheckResourceAttr(resourceFullName, "path", template.Path) + if template.Path == "" { + pathAssertion = resource.TestCheckNoResourceAttr(resourceFullName, "path") + } + return resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceFullName, "id", template.Id), resource.TestCheckResourceAttr(resourceFullName, "name", template.Name), resource.TestCheckResourceAttr(resourceFullName, "description", template.Description), resource.TestCheckResourceAttr(resourceFullName, "repository", template.Repository), - resource.TestCheckResourceAttr(resourceFullName, "path", template.Path), resource.TestCheckResourceAttr(resourceFullName, "type", template.Type), resource.TestCheckResourceAttr(resourceFullName, "retries_on_deploy", strconv.Itoa(template.Retry.OnDeploy.Times)), resource.TestCheckResourceAttr(resourceFullName, "retry_on_deploy_only_when_matches_regex", template.Retry.OnDeploy.ErrorRegex), @@ -485,9 +542,12 @@ func TestUnitTemplateResource(t *testing.T) { gitlabProjectIdAssertion, terragruntVersionAssertion, githubInstallationIdAssertion, + helmChartNameAssertion, + pathAssertion, resource.TestCheckResourceAttr(resourceFullName, "terraform_version", template.TerraformVersion), resource.TestCheckResourceAttr(resourceFullName, "is_terragrunt_run_all", strconv.FormatBool(template.IsTerragruntRunAll)), resource.TestCheckResourceAttr(resourceFullName, "is_azure_devops", strconv.FormatBool(template.IsAzureDevOps)), + resource.TestCheckResourceAttr(resourceFullName, "is_helm_repository", strconv.FormatBool(template.IsHelmRepository)), ) } @@ -504,6 +564,7 @@ func TestUnitTemplateResource(t *testing.T) { {"Bitbucket Server", bitbucketServerTemplate, bitbucketServerUpdatedTemplate}, {"Cloudformation", cloudformationTemplate, cloudformationUpdatedTemplate}, {"Azure DevOps", azureDevOpsTemplate, azureDevOpsUpdatedTemplate}, + {"Helm Chart", helmTemplate, helmUpdatedTemplate}, } for _, templateUseCase := range templateUseCases { t.Run("Full "+templateUseCase.vcs+" template (without SSH keys)", func(t *testing.T) { @@ -526,7 +587,7 @@ func TestUnitTemplateResource(t *testing.T) { TokenId: templateUseCase.template.TokenId, Path: templateUseCase.template.Path, Revision: templateUseCase.template.Revision, - Type: "terraform", + Type: templateUseCase.template.Type, Retry: templateUseCase.template.Retry, TerraformVersion: templateUseCase.template.TerraformVersion, BitbucketClientKey: templateUseCase.template.BitbucketClientKey, @@ -536,6 +597,8 @@ func TestUnitTemplateResource(t *testing.T) { TerragruntVersion: templateUseCase.template.TerragruntVersion, IsTerragruntRunAll: templateUseCase.template.IsTerragruntRunAll, IsAzureDevOps: templateUseCase.template.IsAzureDevOps, + IsHelmRepository: templateUseCase.template.IsHelmRepository, + HelmChartName: templateUseCase.template.HelmChartName, } updateTemplateCreateTemplate := client.TemplateCreatePayload{ Name: templateUseCase.updatedTemplate.Name, @@ -558,6 +621,8 @@ func TestUnitTemplateResource(t *testing.T) { TerragruntVersion: templateUseCase.updatedTemplate.TerragruntVersion, IsTerragruntRunAll: templateUseCase.updatedTemplate.IsTerragruntRunAll, IsAzureDevOps: templateUseCase.updatedTemplate.IsAzureDevOps, + IsHelmRepository: templateUseCase.template.IsHelmRepository, + HelmChartName: templateUseCase.template.HelmChartName, } if templateUseCase.vcs == "Cloudformation" { diff --git a/tests/integration/004_template/main.tf b/tests/integration/004_template/main.tf index e5f9936d..f0207371 100644 --- a/tests/integration/004_template/main.tf +++ b/tests/integration/004_template/main.tf @@ -80,6 +80,23 @@ resource "env0_template" "github_template_source_code" { terraform_version = "0.15.1" } +resource "env0_template" "helm_template" { + name = "helm-${random_string.random.result}-1" + description = "Template description helm" + repository = "https://github.com/env0/templates" + path = "misc/helm/dummy" + type = "helm" +} + +resource "env0_template" "helm_template_repo" { + name = "helm-${random_string.random.result}-2" + description = "Template description helm repo" + repository = "https://charts.bitnami.com/bitnami" + type = "helm" + helm_chart_name = "nginx" + is_helm_repository = true +} + data "env0_source_code_variables" "variables" { template_id = env0_template.github_template_source_code.id }