diff --git a/env0/resource_environment.go b/env0/resource_environment.go index 677c5619..82d0e694 100644 --- a/env0/resource_environment.go +++ b/env0/resource_environment.go @@ -170,7 +170,7 @@ func resourceEnvironment() *schema.Resource { }, "template_id": { Type: schema.TypeString, - Description: "the template id the environment is to be created from.\nImportant note: the template must first be assigned to the same project as the environment (project_id). Use 'env0_template_project_assignment' to assign the template to the project. In addition, be sure to leverage 'depends_on' if applicable.", + Description: "the template id the environment is to be created from.\nImportant note: the template must first be assigned to the same project as the environment (project_id). Use 'env0_template_project_assignment' to assign the template to the project. In addition, be sure to leverage 'depends_on' if applicable.\nImportant note: After the environment is created, this field cannot be modified.", Optional: true, ExactlyOneOf: []string{"without_template_settings", "template_id"}, }, @@ -330,14 +330,12 @@ func resourceEnvironment() *schema.Resource { Default: false, }, }, - - CustomizeDiff: customdiff.ForceNewIf("template_id", func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool { - // For templateless: any changes in template_id, no need to do anything (template id can't change). - // This is done due to historical bugs/issues. - if _, ok := d.GetOk("without_template_settings.0"); ok { - return false + CustomizeDiff: customdiff.ValidateChange("template_id", func(ctx context.Context, oldValue, newValue, meta interface{}) error { + if oldValue != "" && oldValue != newValue { + return errors.New("template_id may not be modified, create a new environment instead") } - return true + + return nil }), } } diff --git a/env0/resource_environment_test.go b/env0/resource_environment_test.go index 2f81892d..660b292f 100644 --- a/env0/resource_environment_test.go +++ b/env0/resource_environment_test.go @@ -185,6 +185,68 @@ func TestUnitEnvironmentResource(t *testing.T) { }) }) + t.Run("avoid modifying template id", func(t *testing.T) { + templateId := "template-id" + newTemplateId := "new-template-id" + + environment := client.Environment{ + Id: uuid.New().String(), + Name: "name", + ProjectId: "project-id", + LatestDeploymentLog: client.DeploymentLog{ + BlueprintId: templateId, + }, + } + + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "name": environment.Name, + "project_id": environment.ProjectId, + "template_id": templateId, + "force_destroy": true, + }), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "id", environment.Id), + resource.TestCheckResourceAttr(accessor, "name", environment.Name), + resource.TestCheckResourceAttr(accessor, "project_id", environment.ProjectId), + resource.TestCheckResourceAttr(accessor, "template_id", templateId), + ), + }, + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "name": environment.Name, + "project_id": environment.ProjectId, + "template_id": newTemplateId, + "force_destroy": true, + }), + PlanOnly: true, + ExpectError: regexp.MustCompile("template_id may not be modified, create a new environment instead"), + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + gomock.InOrder( + mock.EXPECT().Template(environment.LatestDeploymentLog.BlueprintId).Times(1).Return(template, nil), + mock.EXPECT().EnvironmentCreate(client.EnvironmentCreate{ + Name: environment.Name, + ProjectId: environment.ProjectId, + + DeployRequest: &client.DeployRequest{ + BlueprintId: templateId, + }, + }).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().Environment(environment.Id).Times(1).Return(environment, nil), + mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id).Times(1).Return(client.ConfigurationChanges{}, nil), + mock.EXPECT().EnvironmentDestroy(environment.Id).Times(1), + ) + }) + }) + t.Run("remote apply is enabled", func(t *testing.T) { templateId := "template-id"