diff --git a/client/template.go b/client/template.go
index 9f9e8691..94c08a65 100644
--- a/client/template.go
+++ b/client/template.go
@@ -63,6 +63,7 @@ type Template struct {
HelmChartName string `json:"helmChartName" tfschema:",omitempty"`
IsGitLab bool `json:"isGitLab" tfschema:"is_gitlab"`
TerragruntTfBinary string `json:"terragruntTfBinary" tfschema:",omitempty"`
+ TokenName string `json:"tokenName" tfschema:",omitempty"`
}
type TemplateCreatePayload struct {
@@ -74,7 +75,7 @@ type TemplateCreatePayload struct {
Repository string `json:"repository"`
Path string `json:"path,omitempty"`
IsGitLab bool `json:"isGitLab"`
- TokenName string `json:"tokenName"`
+ TokenName string `json:"tokenName,omitempty"`
TokenId string `json:"tokenId,omitempty"`
GithubInstallationId int `json:"githubInstallationId,omitempty"`
GitlabProjectId int `json:"gitlabProjectId,omitempty"`
diff --git a/docs/data-sources/template.md b/docs/data-sources/template.md
index 99579c82..d9c3a308 100644
--- a/docs/data-sources/template.md
+++ b/docs/data-sources/template.md
@@ -23,21 +23,18 @@ data "env0_template" "example" {
### Optional
-- `bitbucket_client_key` (String) the bitbucket client key used for integration
-- `github_installation_id` (Number) The env0 application installation id on the relevant github repository
- `id` (String) id of the template
-- `is_azure_devops` (Boolean) true if this template integrates with azure dev ops
-- `is_bitbucket_server` (Boolean) true if this template uses bitbucket server repository
-- `is_github_enterprise` (Boolean) true if this template uses github enterprise repository
-- `is_gitlab_enterprise` (Boolean) Does this template use gitlab enterprise repository?
- `name` (String) the name of the template
-- `ssh_keys` (Block List) an array of references to 'data_ssh_key' to use when accessing git over ssh (see [below for nested schema](#nestedblock--ssh_keys))
-- `terragrunt_version` (String) terragrunt version to use
-- `token_id` (String) The token id used for private git repos or for integration with GitLab
### Read-Only
+- `bitbucket_client_key` (String) the bitbucket client key used for integration
- `description` (String) description for the template
+- `github_installation_id` (Number) The env0 application installation id on the relevant github repository
+- `is_azure_devops` (Boolean) true if this template integrates with azure dev ops
+- `is_bitbucket_server` (Boolean) true if this template uses bitbucket server repository
+- `is_github_enterprise` (Boolean) true if this template uses github enterprise repository
+- `is_gitlab_enterprise` (Boolean) Does this template use gitlab enterprise repository?
- `path` (String) terraform / terrgrunt folder inside source code repository
- `project_ids` (List of String) which projects may access this template (id of project)
- `repository` (String) template source code repository url
@@ -46,13 +43,17 @@ data "env0_template" "example" {
- `retry_on_deploy_only_when_matches_regex` (String) if specified, will only retry (on deploy) if error matches specified regex
- `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
+- `ssh_keys` (List of Object) an array of references to 'data_ssh_key' to use when accessing git over ssh (see [below for nested schema](#nestedatt--ssh_keys))
- `terraform_version` (String) terraform version to use
+- `terragrunt_version` (String) terragrunt version to use
+- `token_id` (String) The token id used for private git repos or for integration with GitLab
+- `token_name` (String) the token name used for integration with GitLab
- `type` (String) template type (allowed values: terraform, terragrunt, pulumi, k8s, workflow, cloudformation, helm, opentofu)
-
+
### Nested Schema for `ssh_keys`
-Required:
+Read-Only:
-- `id` (String) ssh key id
-- `name` (String) ssh key name
+- `id` (String)
+- `name` (String)
diff --git a/docs/resources/environment.md b/docs/resources/environment.md
index a30f7a3e..0779d069 100644
--- a/docs/resources/environment.md
+++ b/docs/resources/environment.md
@@ -84,7 +84,7 @@ Important note: the template must first be assigned to the same project as the e
Important note: After the environment is created, this field cannot be modified.
- `terragrunt_working_directory` (String) The working directory path to be used by a Terragrunt template. If left empty '/' is used. Note: modifying this field destroys the current environment and creates a new one
- `ttl` (String) the date the environment should be destroyed at (iso format). omitting this attribute will result in infinite ttl.
-- `variable_sets` (List of String) a list of variable set to assign to this environment. Note: must not be used with 'env0_variable_set_assignment'
+- `variable_sets` (List of String) a list of IDs of variable sets to assign to this environment. Note: must not be used with 'env0_variable_set_assignment'
- `vcs_commands_alias` (String) set an alias for this environment in favor of running VCS commands using PR comments against it. Additional details: https://docs.env0.com/docs/plan-and-apply-from-pr-comments
- `vcs_pr_comments_enabled` (Boolean) set to 'true' to enable running VCS PR plan/apply commands using PR comments. This can be set to 'true' (enabled) without setting alias in 'vcs_commands_alias'. Additional details: https://docs.env0.com/docs/plan-and-apply-from-pr-comments#configuration
- `without_template_settings` (Block List, Max: 1) settings for creating an environment without a template (see [below for nested schema](#nestedblock--without_template_settings))
@@ -188,6 +188,7 @@ Optional:
- `terragrunt_tf_binary` (String) the binary to use if the template type is 'terragrunt'. Valid values 'opentofu' and 'terraform'. For new templates defaults to 'opentofu'
- `terragrunt_version` (String) the Terragrunt version to use (example: 0.36.5)
- `token_id` (String) the git token id to be used
+- `token_name` (String) token name for Gitlab
- `type` (String) template type (allowed values: terraform, terragrunt, pulumi, k8s, workflow, cloudformation, helm, opentofu)
Read-Only:
diff --git a/docs/resources/module.md b/docs/resources/module.md
index 7b4a081f..f999e31a 100644
--- a/docs/resources/module.md
+++ b/docs/resources/module.md
@@ -28,6 +28,7 @@ resource "env0_module" "example" {
### Required
- `module_name` (String) name of the module (Match pattern: ^[0-9A-Za-z](?:[0-9A-Za-z-_]{0,62}[0-9A-Za-z])?$)
+- `module_provider` (String) the provider name in the module source (Match pattern: ^[0-9a-z]{0,64}$)
- `repository` (String) the repository containing the module files
### Optional
@@ -36,7 +37,6 @@ resource "env0_module" "example" {
- `description` (String) description of the module
- `github_installation_id` (Number) the env0 application installation id on the relevant Github repository
- `is_azure_devops` (Boolean) true if this module integrates with azure dev ops
-- `module_provider` (String) the provider name in the module source (Match pattern: ^[0-9a-z]{0,64}$)
- `module_test_enabled` (Boolean) set to 'true' to enable module test (defaults to 'false')
- `opentofu_version` (String) the opentofu version to use, Can only be set if 'module_test_enabled' is enabled
- `path` (String) the folder in the repository to create the module from
diff --git a/docs/resources/template.md b/docs/resources/template.md
index 273365c2..bb3030f8 100644
--- a/docs/resources/template.md
+++ b/docs/resources/template.md
@@ -87,6 +87,7 @@ resource "env0_template_project_assignment" "assignment" {
- `terragrunt_tf_binary` (String) the binary to use if the template type is 'terragrunt'. Valid values 'opentofu' and 'terraform'. For new templates defaults to 'opentofu'
- `terragrunt_version` (String) the Terragrunt version to use (example: 0.36.5)
- `token_id` (String) the git token id to be used
+- `token_name` (String) token name for Gitlab
- `type` (String) template type (allowed values: terraform, terragrunt, pulumi, k8s, workflow, cloudformation, helm, opentofu)
### Read-Only
diff --git a/env0/data_template.go b/env0/data_template.go
index db1a2933..0ca36aba 100644
--- a/env0/data_template.go
+++ b/env0/data_template.go
@@ -84,12 +84,12 @@ func dataTemplate() *schema.Resource {
"github_installation_id": {
Type: schema.TypeInt,
Description: "The env0 application installation id on the relevant github repository",
- Optional: true,
+ Computed: true,
},
"token_id": {
Type: schema.TypeString,
Description: "The token id used for private git repos or for integration with GitLab",
- Optional: true,
+ Computed: true,
},
"terraform_version": {
Type: schema.TypeString,
@@ -100,58 +100,56 @@ func dataTemplate() *schema.Resource {
Type: schema.TypeString,
Description: "terragrunt version to use",
Computed: true,
- Optional: true,
},
"is_gitlab_enterprise": {
Type: schema.TypeBool,
Description: "Does this template use gitlab enterprise repository?",
- Optional: true,
Computed: true,
},
"bitbucket_client_key": {
Type: schema.TypeString,
Description: "the bitbucket client key used for integration",
- Optional: true,
Computed: true,
},
"is_bitbucket_server": {
Type: schema.TypeBool,
Description: "true if this template uses bitbucket server repository",
- Optional: true,
Computed: true,
},
"is_github_enterprise": {
Type: schema.TypeBool,
Description: "true if this template uses github enterprise repository",
- Optional: true,
Computed: true,
},
"is_azure_devops": {
Type: schema.TypeBool,
Description: "true if this template integrates with azure dev ops",
- Optional: true,
Computed: true,
},
"ssh_keys": {
Type: schema.TypeList,
Description: "an array of references to 'data_ssh_key' to use when accessing git over ssh",
- Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Description: "ssh key id",
- Required: true,
+ Computed: true,
},
"name": {
Type: schema.TypeString,
Description: "ssh key name",
- Required: true,
+ Computed: true,
},
},
},
},
+ "token_name": {
+ Type: schema.TypeString,
+ Description: "the token name used for integration with GitLab",
+ Computed: true,
+ },
},
}
}
diff --git a/env0/data_template_test.go b/env0/data_template_test.go
index d931b7d6..4f477307 100644
--- a/env0/data_template_test.go
+++ b/env0/data_template_test.go
@@ -42,6 +42,7 @@ func TestUnitTemplateData(t *testing.T) {
},
IsBitbucketServer: true,
IsAzureDevOps: true,
+ TokenName: "tokenname",
}
getValidTestCase := func(input map[string]interface{}) resource.TestCase {
@@ -69,6 +70,7 @@ func TestUnitTemplateData(t *testing.T) {
resource.TestCheckResourceAttr(resourceFullName, "ssh_keys.0.name", template.SshKeys[0].Name),
resource.TestCheckResourceAttr(resourceFullName, "is_bitbucket_server", "true"),
resource.TestCheckResourceAttr(resourceFullName, "is_azure_devops", "true"),
+ resource.TestCheckResourceAttr(resourceFullName, "token_name", template.TokenName),
),
},
},
diff --git a/env0/resource_environment.go b/env0/resource_environment.go
index 2446553c..f7f8dfb1 100644
--- a/env0/resource_environment.go
+++ b/env0/resource_environment.go
@@ -362,7 +362,7 @@ func resourceEnvironment() *schema.Resource {
},
"variable_sets": {
Type: schema.TypeList,
- Description: "a list of variable set to assign to this environment. Note: must not be used with 'env0_variable_set_assignment'",
+ Description: "a list of IDs of variable sets to assign to this environment. Note: must not be used with 'env0_variable_set_assignment'",
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
@@ -446,7 +446,34 @@ func setEnvironmentSchema(ctx context.Context, d *schema.ResourceData, environme
setEnvironmentConfigurationSchema(ctx, d, configurationVariables)
if d.Get("variable_sets") != nil {
- if err := d.Set("variable_sets", variableSetsIds); err != nil {
+ // To avoid drifts keep the schema order as much as possible.
+ variableSetsFromSchema := getEnvironmentVariableSetIdsFromSchema(d)
+ sortedVariablesSet := []string{}
+
+ for _, schemav := range variableSetsFromSchema {
+ for _, newv := range variableSetsIds {
+ if schemav == newv {
+ sortedVariablesSet = append(sortedVariablesSet, schemav)
+ break
+ }
+ }
+ }
+
+ for _, newv := range variableSetsIds {
+ found := false
+ for _, sortedv := range sortedVariablesSet {
+ if newv == sortedv {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ sortedVariablesSet = append(sortedVariablesSet, newv)
+ }
+ }
+
+ if err := d.Set("variable_sets", sortedVariablesSet); err != nil {
return fmt.Errorf("failed to set variable_sets value: %w", err)
}
}
@@ -732,6 +759,12 @@ func shouldUpdateTemplate(d *schema.ResourceData) bool {
}
func shouldDeploy(d *schema.ResourceData) bool {
+ if _, ok := d.GetOk("without_template_settings.0"); ok {
+ if d.HasChange("without_template_settings.0.revision") {
+ return true
+ }
+ }
+
return d.HasChanges("revision", "configuration", "sub_environment_configuration", "variable_sets")
}
@@ -1126,8 +1159,7 @@ func getDeployPayload(d *schema.ResourceData, apiClient client.ApiClientInterfac
var err error
if isTemplateless(d) {
- templateId, ok := d.GetOk("without_template_settings.0.id")
- if ok {
+ if templateId, ok := d.GetOk("without_template_settings.0.id"); ok {
payload.BlueprintId = templateId.(string)
}
} else {
@@ -1139,6 +1171,10 @@ func getDeployPayload(d *schema.ResourceData, apiClient client.ApiClientInterfac
}
if isRedeploy {
+ if revision, ok := d.GetOk("without_template_settings.0.revision"); ok {
+ payload.BlueprintRevision = revision.(string)
+ }
+
if configuration, ok := d.GetOk("configuration"); ok && isRedeploy {
configurationChanges := getConfigurationVariablesFromSchema(configuration.([]interface{}))
scope := client.ScopeEnvironment
@@ -1340,6 +1376,7 @@ func getEnvironmentById(environmentId string, meta interface{}) (client.Environm
func resourceEnvironmentImporter(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
id := d.Id()
var getErr diag.Diagnostics
+
var environment client.Environment
_, err := uuid.Parse(id)
if err == nil {
@@ -1350,6 +1387,11 @@ func resourceEnvironmentImporter(ctx context.Context, d *schema.ResourceData, me
environment, getErr = getEnvironmentByName(meta, id, "", false)
}
+
+ if getErr != nil {
+ return nil, errors.New(getErr[0].Summary)
+ }
+
apiClient := meta.(client.ApiClientInterface)
d.SetId(environment.Id)
@@ -1406,9 +1448,5 @@ func resourceEnvironmentImporter(ctx context.Context, d *schema.ResourceData, me
d.Set("vcs_pr_comments_enabled", environment.VcsCommandsAlias != "" || environment.VcsPrCommentsEnabled)
- if getErr != nil {
- return nil, errors.New(getErr[0].Summary)
- } else {
- return []*schema.ResourceData{d}, nil
- }
+ return []*schema.ResourceData{d}, nil
}
diff --git a/env0/resource_environment_test.go b/env0/resource_environment_test.go
index e7ac6e1f..7bd060d9 100644
--- a/env0/resource_environment_test.go
+++ b/env0/resource_environment_test.go
@@ -371,6 +371,18 @@ func TestUnitEnvironmentResource(t *testing.T) {
}
updatedConfigurationSets := []client.ConfigurationSet{
+ {
+ Id: "id2",
+ AssignmentScope: "ENVIRONMENT",
+ },
+ {
+ Id: "id3",
+ AssignmentScope: "ENVIRONMENT",
+ },
+ }
+
+ // Same as updatedConfigurationSets, but in a different order.
+ updatedConfigurationSets2 := []client.ConfigurationSet{
{
Id: "id3",
AssignmentScope: "ENVIRONMENT",
@@ -398,7 +410,7 @@ func TestUnitEnvironmentResource(t *testing.T) {
environmentDeployRequest := client.DeployRequest{
BlueprintId: environment.LatestDeploymentLog.BlueprintId,
ConfigurationSetChanges: &client.ConfigurationSetChanges{
- Assign: []string{updatedConfigurationSets[0].Id},
+ Assign: []string{updatedConfigurationSets[1].Id},
Unassign: []string{configurationSets[0].Id},
},
}
@@ -465,7 +477,7 @@ func TestUnitEnvironmentResource(t *testing.T) {
mock.EXPECT().Environment(environment.Id).Times(1).Return(environment, nil),
mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id).Times(1).Return(client.ConfigurationChanges{}, nil),
- mock.EXPECT().ConfigurationSetsAssignments("ENVIRONMENT", environment.Id).Times(1).Return(updatedConfigurationSets, nil),
+ mock.EXPECT().ConfigurationSetsAssignments("ENVIRONMENT", environment.Id).Times(1).Return(updatedConfigurationSets2, nil),
mock.EXPECT().EnvironmentDestroy(environment.Id).Times(1),
)
@@ -744,6 +756,44 @@ func TestUnitEnvironmentResource(t *testing.T) {
})
})
+ t.Run("Import By Id - not found", func(t *testing.T) {
+ testCase := resource.TestCase{
+ Steps: []resource.TestStep{
+ {
+ Config: createEnvironmentResourceConfig(environment),
+ },
+ {
+ ResourceName: resourceNameImport,
+ ImportState: true,
+ ImportStateId: environment.Id,
+ ExpectError: regexp.MustCompile("Could not find environment: error"),
+ },
+ },
+ }
+
+ runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {
+ mock.EXPECT().Template(environment.LatestDeploymentLog.BlueprintId).Times(1).Return(template, nil)
+ mock.EXPECT().EnvironmentCreate(client.EnvironmentCreate{
+ Name: environment.Name,
+ ProjectId: environment.ProjectId,
+ WorkspaceName: environment.WorkspaceName,
+ AutoDeployByCustomGlob: autoDeployByCustomGlobDefault,
+ TerragruntWorkingDirectory: environment.TerragruntWorkingDirectory,
+ VcsCommandsAlias: environment.VcsCommandsAlias,
+ DeployRequest: &client.DeployRequest{
+ BlueprintId: templateId,
+ },
+ IsRemoteBackend: &isRemoteBackendFalse,
+ K8sNamespace: environment.K8sNamespace,
+ }).Times(1).Return(environment, nil)
+ mock.EXPECT().Environment(environment.Id).Times(1).Return(environment, nil)
+ mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id).Times(1).Return(client.ConfigurationChanges{}, nil)
+ mock.EXPECT().ConfigurationSetsAssignments("ENVIRONMENT", environment.Id).Times(1).Return(nil, nil)
+ mock.EXPECT().Environment(environment.Id).Times(1).Return(client.Environment{}, errors.New("error"))
+ mock.EXPECT().EnvironmentDestroy(environment.Id).Times(1)
+ })
+ })
+
t.Run("Success create and remove drift cron", func(t *testing.T) {
testCase := resource.TestCase{
Steps: []resource.TestStep{
@@ -2503,6 +2553,13 @@ func TestUnitEnvironmentWithoutTemplateResource(t *testing.T) {
// Update
mock.EXPECT().TemplateUpdate(template.Id, templateUpdatePayload).Times(1).Return(updatedTemplate, nil),
+ mock.EXPECT().ConfigurationSetsAssignments("ENVIRONMENT", environment.Id).Times(1).Return(nil, nil),
+ mock.EXPECT().EnvironmentDeploy(environment.Id, client.DeployRequest{
+ BlueprintId: template.Id,
+ BlueprintRevision: updatedTemplate.Revision,
+ }).Times(1).Return(client.EnvironmentDeployResponse{
+ Id: environment.Id,
+ }, nil),
// Read
mock.EXPECT().Environment(environment.Id).Times(1).Return(environment, nil),
diff --git a/env0/resource_module.go b/env0/resource_module.go
index 097c52a8..6a0adae2 100644
--- a/env0/resource_module.go
+++ b/env0/resource_module.go
@@ -36,7 +36,7 @@ func resourceModule() *schema.Resource {
"module_provider": {
Type: schema.TypeString,
Description: "the provider name in the module source (Match pattern: ^[0-9a-z]{0,64}$)",
- Optional: true,
+ Required: true,
ValidateDiagFunc: NewRegexValidator(`^[0-9a-z]{0,64}$`),
},
"repository": {
diff --git a/env0/resource_template.go b/env0/resource_template.go
index 52eb2338..80e39c3e 100644
--- a/env0/resource_template.go
+++ b/env0/resource_template.go
@@ -244,6 +244,11 @@ func getTemplateSchema(prefix string) map[string]*schema.Schema {
Description: "the binary to use if the template type is 'terragrunt'. Valid values 'opentofu' and 'terraform'. For new templates defaults to 'opentofu'",
ValidateDiagFunc: NewStringInValidator([]string{"opentofu", "terraform"}),
},
+ "token_name": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "token name for Gitlab",
+ },
}
if prefix == "" {
diff --git a/env0/resource_template_test.go b/env0/resource_template_test.go
index 384377de..02307106 100644
--- a/env0/resource_template_test.go
+++ b/env0/resource_template_test.go
@@ -84,6 +84,7 @@ func TestUnitTemplateResource(t *testing.T) {
Type: "terraform",
TokenId: "1",
TerraformVersion: "0.12.24",
+ TokenName: "token_name",
}
gitlabTemplateProjectId := 10
gitlabUpdatedTemplate := client.Template{
@@ -107,6 +108,7 @@ func TestUnitTemplateResource(t *testing.T) {
TerragruntVersion: "0.26.1",
TokenId: "2",
TerraformVersion: "0.15.1",
+ TokenName: "token_name2",
}
gitlabTemplateUpdatedProjectId := 15
githubTemplate := client.Template{
@@ -487,6 +489,9 @@ func TestUnitTemplateResource(t *testing.T) {
if template.TokenId != "" {
templateAsDictionary["token_id"] = template.TokenId
}
+ if template.TokenName != "" {
+ templateAsDictionary["token_name"] = template.TokenName
+ }
if template.Id == gitlabTemplate.Id {
if template.Name == gitlabUpdatedTemplate.Name {
templateAsDictionary["gitlab_project_id"] = gitlabTemplateUpdatedProjectId
@@ -575,6 +580,11 @@ func TestUnitTemplateResource(t *testing.T) {
pathAssertion = resource.TestCheckNoResourceAttr(resourceFullName, "path")
}
+ tokenNameAssertion := resource.TestCheckResourceAttr(resourceFullName, "token_name", template.TokenName)
+ if template.TokenName == "" {
+ tokenNameAssertion = resource.TestCheckNoResourceAttr(resourceFullName, "token_name")
+ }
+
return resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(resourceFullName, "id", template.Id),
resource.TestCheckResourceAttr(resourceFullName, "name", template.Name),
@@ -598,6 +608,7 @@ func TestUnitTemplateResource(t *testing.T) {
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)),
+ tokenNameAssertion,
)
}
@@ -651,6 +662,7 @@ func TestUnitTemplateResource(t *testing.T) {
IsHelmRepository: templateUseCase.template.IsHelmRepository,
HelmChartName: templateUseCase.template.HelmChartName,
OpentofuVersion: templateUseCase.template.OpentofuVersion,
+ TokenName: templateUseCase.template.TokenName,
}
updateTemplateCreateTemplate := client.TemplateCreatePayload{
@@ -677,6 +689,7 @@ func TestUnitTemplateResource(t *testing.T) {
IsHelmRepository: templateUseCase.updatedTemplate.IsHelmRepository,
HelmChartName: templateUseCase.updatedTemplate.HelmChartName,
OpentofuVersion: templateUseCase.updatedTemplate.OpentofuVersion,
+ TokenName: templateUseCase.updatedTemplate.TokenName,
}
if templateUseCase.template.Type == "terragrunt" {