Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: helm template support + helm repo vcs option support #658

Merged
merged 3 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions client/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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 {
Expand All @@ -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"`
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down
46 changes: 35 additions & 11 deletions env0/resource_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"log"
"sort"
"strings"

"github.com/env0/terraform-provider-env0/client"
Expand All @@ -21,6 +20,7 @@ var allowedTemplateTypes = []string{
"k8s",
"workflow",
"cloudformation",
"helm",
}

func getTemplateSchema(prefix string) map[string]*schema.Schema {
Expand All @@ -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
}
Expand Down Expand Up @@ -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": {
Expand All @@ -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,
Expand All @@ -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": {
tomporat247 marked this conversation as resolved.
Show resolved Hide resolved
tomporat247 marked this conversation as resolved.
Show resolved Hide resolved
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"),
tomporat247 marked this conversation as resolved.
Show resolved Hide resolved
},
}

if prefix == "" {
Expand Down
69 changes: 67 additions & 2 deletions env0/resource_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
tomporat247 marked this conversation as resolved.
Show resolved Hide resolved
}
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,
Expand Down Expand Up @@ -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)
}
Expand All @@ -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")
Expand All @@ -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),
Expand All @@ -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)),
)
}

Expand All @@ -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) {
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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" {
Expand Down
17 changes: 17 additions & 0 deletions tests/integration/004_template/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down