From ea8518f1ec44c069dd53858225f343799d4b3824 Mon Sep 17 00:00:00 2001 From: Tomer Heber Date: Sun, 15 Oct 2023 01:05:15 -0500 Subject: [PATCH 1/7] Feat: add opentofu support (#718) --- client/template.go | 9 +++ env0/resource_template.go | 7 ++ env0/resource_template_test.go | 94 +++++++++++++++++++++++++- tests/integration/004_template/main.tf | 13 ++++ 4 files changed, 121 insertions(+), 2 deletions(-) diff --git a/client/template.go b/client/template.go index e9ea258f..b2f07573 100644 --- a/client/template.go +++ b/client/template.go @@ -51,6 +51,7 @@ type Template struct { UpdatedAt string `json:"updatedAt"` TerraformVersion string `json:"terraformVersion" tfschema:",omitempty"` TerragruntVersion string `json:"terragruntVersion,omitempty" tfschema:",omitempty"` + OpentofuVersion string `json:"opentofuVersion,omitempty" tfschema:",omitempty"` IsDeleted bool `json:"isDeleted,omitempty"` BitbucketClientKey string `json:"bitbucketClientKey" tfschema:",omitempty"` IsGithubEnterprise bool `json:"isGitHubEnterprise"` @@ -80,6 +81,7 @@ type TemplateCreatePayload struct { OrganizationId string `json:"organizationId"` TerraformVersion string `json:"terraformVersion,omitempty"` TerragruntVersion string `json:"terragruntVersion,omitempty"` + OpentofuVersion string `json:"opentofuVersion,omitempty"` IsGitlabEnterprise bool `json:"isGitLabEnterprise"` BitbucketClientKey string `json:"bitbucketClientKey,omitempty"` IsGithubEnterprise bool `json:"isGitHubEnterprise"` @@ -122,6 +124,9 @@ func (payload *TemplateCreatePayload) Invalidate() error { if payload.Type == "terragrunt" && payload.TerragruntVersion == "" { return errors.New("must supply terragrunt version") } + if payload.Type == "opentofu" && payload.OpentofuVersion == "" { + return errors.New("must supply opentofu version") + } if payload.IsTerragruntRunAll { if payload.Type != "terragrunt" { @@ -163,6 +168,10 @@ func (payload *TemplateCreatePayload) Invalidate() error { payload.TerraformVersion = "" } + if payload.Type != "opentofu" { + payload.OpentofuVersion = "" + } + return nil } diff --git a/env0/resource_template.go b/env0/resource_template.go index 83da2e3c..33aa2998 100644 --- a/env0/resource_template.go +++ b/env0/resource_template.go @@ -21,6 +21,7 @@ var allowedTemplateTypes = []string{ "workflow", "cloudformation", "helm", + "opentofu", } func getTemplateSchema(prefix string) map[string]*schema.Schema { @@ -172,6 +173,12 @@ func getTemplateSchema(prefix string) map[string]*schema.Schema { ValidateDiagFunc: NewRegexValidator(`^[0-9]\.[0-9]{1,2}\.[0-9]{1,2}$`), Optional: true, }, + "opentofu_version": { + Type: schema.TypeString, + Description: "the Opentofu version to use (example: 0.36.5)", + ValidateDiagFunc: NewRegexValidator(`^(?:[0-9]\.[0-9]{1,2}\.[0-9]{1,2})|1\.6\.0-alpha$`), + Optional: true, + }, "is_gitlab_enterprise": { Type: schema.TypeBool, Description: "true if this template uses gitlab enterprise repository", diff --git a/env0/resource_template_test.go b/env0/resource_template_test.go index b2fa7273..7b02ac49 100644 --- a/env0/resource_template_test.go +++ b/env0/resource_template_test.go @@ -407,6 +407,45 @@ func TestUnitTemplateResource(t *testing.T) { TerraformVersion: "0.12.24", } + opentofuTemplate := client.Template{ + Id: "opentofu", + Name: "template0", + Description: "description0", + Repository: "env0/repo", + Type: "opentofu", + OpentofuVersion: "1.6.0-alpha", + TerraformVersion: "0.15.1", + Retry: client.TemplateRetry{ + OnDeploy: &client.TemplateRetryOn{ + Times: 2, + ErrorRegex: "RetryMeForDeploy.*", + }, + OnDestroy: &client.TemplateRetryOn{ + Times: 1, + ErrorRegex: "RetryMeForDestroy.*", + }, + }, + } + opentofuUpdatedTemplate := client.Template{ + Id: opentofuTemplate.Id, + Name: "new-name", + Description: "new-description", + Repository: "env0/repo-new", + Type: "opentofu", + OpentofuVersion: "1.7.0", + TerraformVersion: "0.15.1", + Retry: client.TemplateRetry{ + OnDeploy: &client.TemplateRetryOn{ + Times: 2, + ErrorRegex: "RetryMeForDeploy.*", + }, + OnDestroy: &client.TemplateRetryOn{ + Times: 1, + ErrorRegex: "RetryMeForDestroy.*", + }, + }, + } + fullTemplateResourceConfig := func(resourceType string, resourceName string, template client.Template) string { templateAsDictionary := map[string]interface{}{ "name": template.Name, @@ -440,6 +479,9 @@ func TestUnitTemplateResource(t *testing.T) { if template.TerraformVersion != "" { templateAsDictionary["terraform_version"] = template.TerraformVersion } + if template.OpentofuVersion != "" { + templateAsDictionary["opentofu_version"] = template.OpentofuVersion + } if template.TokenId != "" { templateAsDictionary["token_id"] = template.TokenId } @@ -516,6 +558,11 @@ func TestUnitTemplateResource(t *testing.T) { terragruntVersionAssertion = resource.TestCheckNoResourceAttr(resourceFullName, "terragrunt_version") } + opentofuVersionAssertion := resource.TestCheckResourceAttr(resourceFullName, "opentofu_version", template.OpentofuVersion) + if template.OpentofuVersion == "" { + opentofuVersionAssertion = resource.TestCheckNoResourceAttr(resourceFullName, "opentofu_version") + } + githubInstallationIdAssertion := resource.TestCheckResourceAttr(resourceFullName, "github_installation_id", strconv.Itoa(template.GithubInstallationId)) if template.GithubInstallationId == 0 { githubInstallationIdAssertion = resource.TestCheckNoResourceAttr(resourceFullName, "github_installation_id") @@ -544,6 +591,7 @@ func TestUnitTemplateResource(t *testing.T) { githubInstallationIdAssertion, helmChartNameAssertion, pathAssertion, + opentofuVersionAssertion, 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)), @@ -565,6 +613,7 @@ func TestUnitTemplateResource(t *testing.T) { {"Cloudformation", cloudformationTemplate, cloudformationUpdatedTemplate}, {"Azure DevOps", azureDevOpsTemplate, azureDevOpsUpdatedTemplate}, {"Helm Chart", helmTemplate, helmUpdatedTemplate}, + {"Opentofu", opentofuTemplate, opentofuUpdatedTemplate}, } for _, templateUseCase := range templateUseCases { t.Run("Full "+templateUseCase.vcs+" template (without SSH keys)", func(t *testing.T) { @@ -599,6 +648,7 @@ func TestUnitTemplateResource(t *testing.T) { IsAzureDevOps: templateUseCase.template.IsAzureDevOps, IsHelmRepository: templateUseCase.template.IsHelmRepository, HelmChartName: templateUseCase.template.HelmChartName, + OpentofuVersion: templateUseCase.template.OpentofuVersion, } updateTemplateCreateTemplate := client.TemplateCreatePayload{ @@ -622,8 +672,9 @@ 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, + IsHelmRepository: templateUseCase.updatedTemplate.IsHelmRepository, + HelmChartName: templateUseCase.updatedTemplate.HelmChartName, + OpentofuVersion: templateUseCase.updatedTemplate.OpentofuVersion, } if templateUseCase.template.Type != "terraform" && templateUseCase.template.Type != "terragrunt" { @@ -1110,6 +1161,45 @@ func TestUnitTemplateResource(t *testing.T) { runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {}) }) + t.Run("Invalid Opentofu Version", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "name": "template0", + "repository": "env0/repo", + "type": "opentofu", + "gitlab_project_id": 123456, + "token_id": "abcdefg", + "opentofu_version": "v0.20.1", + }), + ExpectError: regexp.MustCompile("must match pattern"), + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {}) + }) + + t.Run("Opentofu type with no Opentofu version", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "name": "template0", + "repository": "env0/repo", + "type": "opentofu", + "gitlab_project_id": 123456, + "token_id": "abcdefg", + }), + ExpectError: regexp.MustCompile("must supply opentofu version"), + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {}) + }) + t.Run("Cloudformation type with no file_name", func(t *testing.T) { testCase := resource.TestCase{ Steps: []resource.TestStep{ diff --git a/tests/integration/004_template/main.tf b/tests/integration/004_template/main.tf index ca7426ae..6a0fb76c 100644 --- a/tests/integration/004_template/main.tf +++ b/tests/integration/004_template/main.tf @@ -54,6 +54,19 @@ resource "env0_template" "template_tg" { terragrunt_version = "0.35.0" } +resource "env0_template" "template_opentofu" { + name = "Opentofu-${random_string.random.result}" + description = "Template description - OpenTofu and GitHub" + type = "opentofu" + repository = data.env0_template.github_template.repository + github_installation_id = data.env0_template.github_template.github_installation_id + path = "/misc/null-resource" + retries_on_deploy = 3 + retry_on_deploy_only_when_matches_regex = "abc" + retries_on_destroy = 1 + opentofu_version = "1.6.0" +} + resource "env0_configuration_variable" "in_a_template" { name = "fake_key" value = "fake value" From c4da1d5985af7b59238022d5ac920a2481e8b93b Mon Sep 17 00:00:00 2001 From: update generated docs action Date: Sun, 15 Oct 2023 06:05:41 +0000 Subject: [PATCH 2/7] Update docs --- docs/data-sources/template.md | 2 +- docs/resources/environment.md | 3 ++- docs/resources/template.md | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/data-sources/template.md b/docs/data-sources/template.md index 472b4f07..4d72a8e9 100644 --- a/docs/data-sources/template.md +++ b/docs/data-sources/template.md @@ -46,7 +46,7 @@ data "env0_template" "example" { - `retry_on_destroy_only_when_matches_regex` (String) if specified, will only retry (on destroy) if error matches specified regex - `revision` (String) source code revision (branch / tag) to use - `terraform_version` (String) terraform version to use -- `type` (String) template type (allowed values: terraform, terragrunt, pulumi, k8s, workflow, cloudformation, helm) +- `type` (String) template type (allowed values: terraform, terragrunt, pulumi, k8s, workflow, cloudformation, helm, opentofu) ### Nested Schema for `ssh_keys` diff --git a/docs/resources/environment.md b/docs/resources/environment.md index aa516780..5d043bbb 100644 --- a/docs/resources/environment.md +++ b/docs/resources/environment.md @@ -168,6 +168,7 @@ Optional: - `is_gitlab_enterprise` (Boolean) true if this template uses gitlab enterprise repository - `is_helm_repository` (Boolean) true if this template integrates with a helm repository - `is_terragrunt_run_all` (Boolean) true if this template should execute run-all commands on multiple modules (check https://terragrunt.gruntwork.io/docs/features/execute-terraform-commands-on-multiple-modules-at-once/#the-run-all-command for additional details). Can only be true with "terragrunt" template type and terragrunt version 0.28.1 and above +- `opentofu_version` (String) the Opentofu version to use (example: 0.36.5) - `path` (String) terraform / terragrunt file folder inside source code - `retries_on_deploy` (Number) number of times to retry when deploying an environment based on this template - `retries_on_destroy` (Number) number of times to retry when destroying an environment based on this template @@ -178,7 +179,7 @@ Optional: - `terraform_version` (String) the Terraform version to use (example: 0.15.1). Setting to `RESOLVE_FROM_TERRAFORM_CODE` defaults to the version of `terraform.required_version` during run-time (resolve from terraform code). Setting to `latest`, the version used will be the most recent one available for Terraform. - `terragrunt_version` (String) the Terragrunt version to use (example: 0.36.5) - `token_id` (String) the git token id to be used -- `type` (String) template type (allowed values: terraform, terragrunt, pulumi, k8s, workflow, cloudformation, helm) +- `type` (String) template type (allowed values: terraform, terragrunt, pulumi, k8s, workflow, cloudformation, helm, opentofu) Read-Only: diff --git a/docs/resources/template.md b/docs/resources/template.md index 8004a1f3..6e397f89 100644 --- a/docs/resources/template.md +++ b/docs/resources/template.md @@ -75,6 +75,7 @@ resource "env0_template_project_assignment" "assignment" { - `is_gitlab_enterprise` (Boolean) true if this template uses gitlab enterprise repository - `is_helm_repository` (Boolean) true if this template integrates with a helm repository - `is_terragrunt_run_all` (Boolean) true if this template should execute run-all commands on multiple modules (check https://terragrunt.gruntwork.io/docs/features/execute-terraform-commands-on-multiple-modules-at-once/#the-run-all-command for additional details). Can only be true with "terragrunt" template type and terragrunt version 0.28.1 and above +- `opentofu_version` (String) the Opentofu version to use (example: 0.36.5) - `path` (String) terraform / terragrunt file folder inside source code - `retries_on_deploy` (Number) number of times to retry when deploying an environment based on this template - `retries_on_destroy` (Number) number of times to retry when destroying an environment based on this template @@ -85,7 +86,7 @@ resource "env0_template_project_assignment" "assignment" { - `terraform_version` (String) the Terraform version to use (example: 0.15.1). Setting to `RESOLVE_FROM_TERRAFORM_CODE` defaults to the version of `terraform.required_version` during run-time (resolve from terraform code). Setting to `latest`, the version used will be the most recent one available for Terraform. - `terragrunt_version` (String) the Terragrunt version to use (example: 0.36.5) - `token_id` (String) the git token id to be used -- `type` (String) template type (allowed values: terraform, terragrunt, pulumi, k8s, workflow, cloudformation, helm) +- `type` (String) template type (allowed values: terraform, terragrunt, pulumi, k8s, workflow, cloudformation, helm, opentofu) ### Read-Only From fbe8ad05a0e591673862bccfcb310b23bb4c83df Mon Sep 17 00:00:00 2001 From: Tomer Heber Date: Wed, 18 Oct 2023 06:52:32 -0500 Subject: [PATCH 3/7] Fix: unable to unset auto_deploy_by_custom_glob property of environment resource back to empty string or null (#730) --- client/environment.go | 2 +- tests/integration/012_environment/main.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/environment.go b/client/environment.go index 9b9cb68e..1b78e9d1 100644 --- a/client/environment.go +++ b/client/environment.go @@ -177,7 +177,7 @@ func (create EnvironmentCreateWithoutTemplate) MarshalJSON() ([]byte, error) { type EnvironmentUpdate struct { Name string `json:"name,omitempty"` - AutoDeployByCustomGlob string `json:"autoDeployByCustomGlob,omitempty"` + AutoDeployByCustomGlob string `json:"autoDeployByCustomGlob"` TerragruntWorkingDirectory string `json:"terragruntWorkingDirectory,omitempty"` VcsCommandsAlias string `json:"vcsCommandsAlias,omitempty"` RequiresApproval *bool `json:"requiresApproval,omitempty" tfschema:"-"` diff --git a/tests/integration/012_environment/main.tf b/tests/integration/012_environment/main.tf index 614d3ba1..832bd437 100644 --- a/tests/integration/012_environment/main.tf +++ b/tests/integration/012_environment/main.tf @@ -34,7 +34,7 @@ resource "env0_environment" "auto_glob_envrironment" { name = "environment-auto-glob-${random_string.random.result}" project_id = env0_project.test_project.id template_id = env0_template.template.id - auto_deploy_by_custom_glob = var.second_run ? "" : "//*" + auto_deploy_by_custom_glob = var.second_run ? null : "//*" auto_deploy_on_path_changes_only = true approve_plan_automatically = true deploy_on_push = true From 4f391c74f31e8b4599329e70ed66eb995fed2c26 Mon Sep 17 00:00:00 2001 From: Tomer Heber Date: Wed, 18 Oct 2023 07:00:20 -0500 Subject: [PATCH 4/7] Fix: missing CUSTOM_ROLE roles (#729) --- env0/resource_custom_role.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/env0/resource_custom_role.go b/env0/resource_custom_role.go index a53111ed..a68a8355 100644 --- a/env0/resource_custom_role.go +++ b/env0/resource_custom_role.go @@ -41,6 +41,10 @@ func resourceCustomRole() *schema.Resource { "VIEW_AUDIT_LOGS", "MANAGE_ENVIRONMENT_LOCK", "CREATE_VCS_ENVIRONMENT", + "CREATE_AND_EDIT_PROVIDERS", + "VIEW_PROVIDERS", + "VIEW_ENVIRONMENT", + "ASSIGN_ROLE_ON_ENVIRONMENT", } allowedCustomRoleTypesStr := fmt.Sprintf("(allowed values: %s)", strings.Join(allowedCustomRoleTypes, ", ")) From d7bdf59c8cda2cc4ea855f15a4640f1a4a51a2d1 Mon Sep 17 00:00:00 2001 From: update generated docs action Date: Wed, 18 Oct 2023 12:01:34 +0000 Subject: [PATCH 5/7] Update docs --- docs/resources/custom_role.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/resources/custom_role.md b/docs/resources/custom_role.md index c674cafc..33bdc213 100644 --- a/docs/resources/custom_role.md +++ b/docs/resources/custom_role.md @@ -28,7 +28,7 @@ resource "env0_custom_role" "custom_role_example" { ### Required - `name` (String) The name of the custom role -- `permissions` (List of String) A list of permissions assigned to the role. Allowed values: (allowed values: VIEW_ORGANIZATION, EDIT_ORGANIZATION_SETTINGS, CREATE_AND_EDIT_TEMPLATES, CREATE_AND_EDIT_MODULES, CREATE_PROJECT, VIEW_PROJECT, EDIT_PROJECT_SETTINGS, MANAGE_PROJECT_TEMPLATES, EDIT_ENVIRONMENT_SETTINGS, ARCHIVE_ENVIRONMENT, OVERRIDE_MAX_TTL, CREATE_CROSS_PROJECT_ENVIRONMENTS, OVERRIDE_MAX_ENVIRONMENT_PROJECT_LIMITS, RUN_PLAN, RUN_APPLY, ABORT_DEPLOYMENT, RUN_TASK, CREATE_CUSTOM_ROLES, VIEW_DASHBOARD, VIEW_MODULES, READ_STATE, WRITE_STATE, FORCE_UNLOCK_WORKSPACE, MANAGE_BILLING, VIEW_AUDIT_LOGS, MANAGE_ENVIRONMENT_LOCK, CREATE_VCS_ENVIRONMENT) +- `permissions` (List of String) A list of permissions assigned to the role. Allowed values: (allowed values: VIEW_ORGANIZATION, EDIT_ORGANIZATION_SETTINGS, CREATE_AND_EDIT_TEMPLATES, CREATE_AND_EDIT_MODULES, CREATE_PROJECT, VIEW_PROJECT, EDIT_PROJECT_SETTINGS, MANAGE_PROJECT_TEMPLATES, EDIT_ENVIRONMENT_SETTINGS, ARCHIVE_ENVIRONMENT, OVERRIDE_MAX_TTL, CREATE_CROSS_PROJECT_ENVIRONMENTS, OVERRIDE_MAX_ENVIRONMENT_PROJECT_LIMITS, RUN_PLAN, RUN_APPLY, ABORT_DEPLOYMENT, RUN_TASK, CREATE_CUSTOM_ROLES, VIEW_DASHBOARD, VIEW_MODULES, READ_STATE, WRITE_STATE, FORCE_UNLOCK_WORKSPACE, MANAGE_BILLING, VIEW_AUDIT_LOGS, MANAGE_ENVIRONMENT_LOCK, CREATE_VCS_ENVIRONMENT, CREATE_AND_EDIT_PROVIDERS, VIEW_PROVIDERS, VIEW_ENVIRONMENT, ASSIGN_ROLE_ON_ENVIRONMENT) ### Optional From 35f5cd96fe3c1b2d1dda15204f74bcc41afdde10 Mon Sep 17 00:00:00 2001 From: Tomer Heber Date: Thu, 19 Oct 2023 16:40:55 -0500 Subject: [PATCH 6/7] Chore: add a manual approval step for a release (#725) --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 138b321a..fd2fd052 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,6 +20,7 @@ env: jobs: goreleaser: + environment: release runs-on: ubuntu-latest steps: - name: Checkout From 98e958587931c92b5eef502f59b96fc623e0ff8a Mon Sep 17 00:00:00 2001 From: Avner Sorek <53464600+avnerenv0@users.noreply.github.com> Date: Fri, 20 Oct 2023 16:46:32 +0300 Subject: [PATCH 7/7] Create .terraform-registry (#732) --- .terraform-registry | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .terraform-registry diff --git a/.terraform-registry b/.terraform-registry new file mode 100644 index 00000000..02861a31 --- /dev/null +++ b/.terraform-registry @@ -0,0 +1,3 @@ +Request: “remove from registry” +Registry Link: https://registry.terraform.io/providers/env0/env0/1.14.15 +Request by: avner.sorek@env0.com