From e3f4b727c8156a9de5c7481c7295542632b9d590 Mon Sep 17 00:00:00 2001 From: SHMUELBRENER Date: Wed, 16 Feb 2022 22:12:56 +0200 Subject: [PATCH 01/11] add drift handling --- client/model.go | 1 + ...ce_cloud_credentials_project_assignment.go | 5 + ...oud_credentials_project_assignment_test.go | 42 +++++++-- env0/resource_environment.go | 6 ++ env0/resource_environment_test.go | 69 ++++++++++++++ env0/resource_project.go | 7 +- env0/resource_project_test.go | 72 ++++++++++++++ env0/resource_team_project_assignment.go | 7 ++ env0/resource_team_project_assignment_test.go | 93 +++++++++++++++---- ...source_template_project_assignment_test.go | 27 +++++- 10 files changed, 300 insertions(+), 29 deletions(-) diff --git a/client/model.go b/client/model.go index 1b3e9a71..40c6cc24 100644 --- a/client/model.go +++ b/client/model.go @@ -286,6 +286,7 @@ type Environment struct { LifespanEndAt string `json:"lifespanEndAt"` LatestDeploymentLogId string `json:"latestDeploymentLogId"` LatestDeploymentLog DeploymentLog `json:"latestDeploymentLog"` + IsArchived bool `json:"isArchived"` } type DeploymentLog struct { diff --git a/env0/resource_cloud_credentials_project_assignment.go b/env0/resource_cloud_credentials_project_assignment.go index 076d2cd0..f2c762de 100644 --- a/env0/resource_cloud_credentials_project_assignment.go +++ b/env0/resource_cloud_credentials_project_assignment.go @@ -60,6 +60,11 @@ func resourceCloudCredentialsProjectAssignmentRead(ctx context.Context, d *schem } } if !found { + + if !d.IsNewResource() { + d.SetId("") + return nil + } return diag.Errorf("could not find cloud credential project assignment.\n project id = %v, cloud credentials id = %v", projectId, credentialId) } diff --git a/env0/resource_cloud_credentials_project_assignment_test.go b/env0/resource_cloud_credentials_project_assignment_test.go index 3fda8c49..2ff21127 100644 --- a/env0/resource_cloud_credentials_project_assignment_test.go +++ b/env0/resource_cloud_credentials_project_assignment_test.go @@ -2,10 +2,12 @@ package env0 import ( "errors" - "github.com/env0/terraform-provider-env0/client" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "regexp" "testing" + + "github.com/env0/terraform-provider-env0/client" + "github.com/golang/mock/gomock" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) func TestUnitResourceCloudCredentialsProjectAssignmentResource(t *testing.T) { @@ -16,6 +18,11 @@ func TestUnitResourceCloudCredentialsProjectAssignmentResource(t *testing.T) { CredentialId: "cred-it", ProjectId: "proj-it", } + + assignmentForDrift := client.CloudCredentialsProjectAssignment{ + CredentialId: "cred-it", + ProjectId: "proj-it-update", + } stepConfig := resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ "credential_id": assignment.CredentialId, "project_id": assignment.ProjectId, @@ -90,20 +97,41 @@ func TestUnitResourceCloudCredentialsProjectAssignmentResource(t *testing.T) { mock.EXPECT().RemoveCloudCredentialsFromProject(assignment.ProjectId, assignment.CredentialId).Times(1).Return(nil) }) }) - t.Run("Read didnt api didnt return correct stuff", func(t *testing.T) { + t.Run("detect drift", func(t *testing.T) { testCase := resource.TestCase{ Steps: []resource.TestStep{ { - Config: stepConfig, - ExpectError: regexp.MustCompile(`(could not find cloud credential project assignment)`), + Config: stepConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "id", assignment.CredentialId+"|"+assignment.ProjectId), + resource.TestCheckResourceAttr(accessor, "credential_id", assignment.CredentialId), + resource.TestCheckResourceAttr(accessor, "project_id", assignment.ProjectId), + ), + }, + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "credential_id": assignmentForDrift.CredentialId, + "project_id": assignmentForDrift.ProjectId, + }), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "id", assignmentForDrift.CredentialId+"|"+assignmentForDrift.ProjectId), + resource.TestCheckResourceAttr(accessor, "credential_id", assignmentForDrift.CredentialId), + resource.TestCheckResourceAttr(accessor, "project_id", assignmentForDrift.ProjectId), + ), }, }, } runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { mock.EXPECT().AssignCloudCredentialsToProject(assignment.ProjectId, assignment.CredentialId).Times(1).Return(assignment, nil) - mock.EXPECT().CloudCredentialIdsInProject(assignment.ProjectId).Times(1).Return([]string{"1", "2"}, nil) - mock.EXPECT().RemoveCloudCredentialsFromProject(assignment.ProjectId, assignment.CredentialId).Times(1).Return(nil) + mock.EXPECT().AssignCloudCredentialsToProject(assignmentForDrift.ProjectId, assignmentForDrift.CredentialId).Times(1).Return(assignmentForDrift, nil) + mock.EXPECT().RemoveCloudCredentialsFromProject(assignmentForDrift.ProjectId, assignmentForDrift.CredentialId).Times(1).Return(nil) + gomock.InOrder( + mock.EXPECT().CloudCredentialIdsInProject(assignment.ProjectId).Times(1).Return([]string{assignment.CredentialId, "1", "2"}, nil), + mock.EXPECT().CloudCredentialIdsInProject(assignment.ProjectId).Times(1).Return([]string{"1", "2"}, nil), + mock.EXPECT().CloudCredentialIdsInProject(assignmentForDrift.ProjectId).Times(1).Return([]string{assignmentForDrift.CredentialId, + "1", "2"}, nil), + ) }) }) } diff --git a/env0/resource_environment.go b/env0/resource_environment.go index 6cb8a8f5..ffb593ca 100644 --- a/env0/resource_environment.go +++ b/env0/resource_environment.go @@ -254,6 +254,12 @@ func resourceEnvironmentRead(ctx context.Context, d *schema.ResourceData, meta i if err != nil { return diag.Errorf("could not get environment: %v", err) } + + if environment.IsArchived { + d.SetId("") + return nil + } + environmentConfigurationVariables, err := apiClient.ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id) if err != nil { return diag.Errorf("could not get environment configuration variables: %v", err) diff --git a/env0/resource_environment_test.go b/env0/resource_environment_test.go index 131a87ae..ac83318e 100644 --- a/env0/resource_environment_test.go +++ b/env0/resource_environment_test.go @@ -28,6 +28,17 @@ func TestUnitEnvironmentResource(t *testing.T) { }, } + driftEnvironment := client.Environment{ + IsArchived: true, + Id: "id0", + Name: "my-environment", + ProjectId: "project-id", + WorkspaceName: "workspace-name", + LatestDeploymentLog: client.DeploymentLog{ + BlueprintId: templateId, + }, + } + updatedEnvironment := client.Environment{ Id: environment.Id, Name: "my-updated-environment-name", @@ -884,4 +895,62 @@ func TestUnitEnvironmentResource(t *testing.T) { }) }) + t.Run("detect drift", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: createEnvironmentResourceConfig(environment), + 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), + resource.TestCheckResourceAttr(accessor, "workspace", environment.WorkspaceName), + ), + }, + { + Config: createEnvironmentResourceConfig(updatedEnvironment), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "id", updatedEnvironment.Id), + resource.TestCheckResourceAttr(accessor, "name", updatedEnvironment.Name), + resource.TestCheckResourceAttr(accessor, "project_id", updatedEnvironment.ProjectId), + resource.TestCheckResourceAttr(accessor, "template_id", templateId), + resource.TestCheckResourceAttr(accessor, "workspace", environment.WorkspaceName), + ), + }, + }, + } + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + mock.EXPECT().EnvironmentCreate(client.EnvironmentCreate{ + Name: environment.Name, + ProjectId: environment.ProjectId, + WorkspaceName: environment.WorkspaceName, + AutoDeployOnPathChangesOnly: &autoDeployOnPathChangesOnlyDefault, + AutoDeployByCustomGlob: autoDeployByCustomGlobDefault, + DeployRequest: &client.DeployRequest{ + BlueprintId: templateId, + }, + }).Times(1).Return(environment, nil) + + mock.EXPECT().EnvironmentCreate(client.EnvironmentCreate{ + Name: updatedEnvironment.Name, + ProjectId: updatedEnvironment.ProjectId, + WorkspaceName: updatedEnvironment.WorkspaceName, + AutoDeployOnPathChangesOnly: &autoDeployOnPathChangesOnlyDefault, + AutoDeployByCustomGlob: autoDeployByCustomGlobDefault, + DeployRequest: &client.DeployRequest{ + BlueprintId: templateId, + }, + }).Times(1).Return(updatedEnvironment, nil) + mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id).AnyTimes().Return([]client.ConfigurationVariable{}, nil) + + gomock.InOrder( + mock.EXPECT().Environment(gomock.Any()).Times(1).Return(environment, nil), + mock.EXPECT().Environment(gomock.Any()).Times(1).Return(driftEnvironment, nil), // 1 after create, 1 before update + mock.EXPECT().Environment(gomock.Any()).Times(1).Return(updatedEnvironment, nil), // 1 after update + ) + + mock.EXPECT().EnvironmentDestroy(environment.Id).Times(1) + }) + }) } diff --git a/env0/resource_project.go b/env0/resource_project.go index 285368b3..f5c96573 100644 --- a/env0/resource_project.go +++ b/env0/resource_project.go @@ -3,12 +3,13 @@ package env0 import ( "context" "errors" + "log" + "github.com/env0/terraform-provider-env0/client" "github.com/google/uuid" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "log" ) func resourceProject() *schema.Resource { @@ -76,6 +77,10 @@ func resourceProjectRead(ctx context.Context, d *schema.ResourceData, meta inter if err != nil { return diag.Errorf("could not get project: %v", err) } + if project.IsArchived { + d.SetId("") + return nil + } setProjectSchema(d, project) diff --git a/env0/resource_project_test.go b/env0/resource_project_test.go index accbd150..fb0fffdc 100644 --- a/env0/resource_project_test.go +++ b/env0/resource_project_test.go @@ -84,3 +84,75 @@ func TestUnitProjectInvalidParams(t *testing.T) { runUnitTest(t, testCase, func(mockFunc *client.MockApiClientInterface) {}) } + +func TestUnitProjectDrift(t *testing.T) { + resourceType := "env0_project" + resourceName := "test" + accessor := resourceAccessor(resourceType, resourceName) + + project := client.Project{ + Id: "id0", + Name: "name0", + Description: "description0", + } + + driftProject := client.Project{ + IsArchived: true, + Id: project.Id, + Name: "name0", + Description: "description0", + } + + projectAfterRecreate := client.Project{ + Id: project.Id, + Name: "new name", + Description: "new description", + } + + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "name": project.Name, + "description": project.Description, + }), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "id", project.Id), + resource.TestCheckResourceAttr(accessor, "name", project.Name), + resource.TestCheckResourceAttr(accessor, "description", project.Description), + ), + }, + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "name": projectAfterRecreate.Name, + "description": projectAfterRecreate.Description, + }), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "id", projectAfterRecreate.Id), + resource.TestCheckResourceAttr(accessor, "name", projectAfterRecreate.Name), + resource.TestCheckResourceAttr(accessor, "description", projectAfterRecreate.Description), + ), + }, + }, + } + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + mock.EXPECT().ProjectCreate(client.ProjectCreatePayload{ + Name: project.Name, + Description: project.Description, + }).Times(1).Return(project, nil) + mock.EXPECT().ProjectCreate(client.ProjectCreatePayload{ + Name: projectAfterRecreate.Name, + Description: projectAfterRecreate.Description, + }).Times(1).Return(projectAfterRecreate, nil) + + gomock.InOrder( + mock.EXPECT().Project(project.Id).Times(1).Return(project, nil), + mock.EXPECT().Project(project.Id).Times(1).Return(driftProject, nil), + mock.EXPECT().Project(project.Id).Times(1).Return(projectAfterRecreate, nil), + ) + + mock.EXPECT().ProjectDelete(project.Id).Times(1) + + }) + +} diff --git a/env0/resource_team_project_assignment.go b/env0/resource_team_project_assignment.go index 057ca056..f0b9ec7f 100644 --- a/env0/resource_team_project_assignment.go +++ b/env0/resource_team_project_assignment.go @@ -3,6 +3,7 @@ package env0 import ( "context" "fmt" + . "github.com/env0/terraform-provider-env0/client" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -59,14 +60,20 @@ func resourceTeamProjectAssignmentRead(ctx context.Context, d *schema.ResourceDa return diag.Errorf("could not get TeamProjectAssignment: %v", err) } + found := false for _, assignment := range assignments { if assignment.Id == id { d.Set("project_id", assignment.ProjectId) d.Set("team_id", assignment.TeamId) d.Set("role", assignment.ProjectRole) + found = true break } } + if !found { + d.SetId("") + return nil + } return nil } diff --git a/env0/resource_team_project_assignment_test.go b/env0/resource_team_project_assignment_test.go index c7a87e65..9f12d67c 100644 --- a/env0/resource_team_project_assignment_test.go +++ b/env0/resource_team_project_assignment_test.go @@ -1,9 +1,11 @@ package env0 import ( + "testing" + "github.com/env0/terraform-provider-env0/client" + "github.com/golang/mock/gomock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "testing" ) func TestUnitTeamProjectAssignmentResource(t *testing.T) { @@ -17,26 +19,77 @@ func TestUnitTeamProjectAssignmentResource(t *testing.T) { ProjectRole: client.Admin, } - testCase := resource.TestCase{ - Steps: []resource.TestStep{ - { - Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ - "team_id": assignment.TeamId, - "project_id": assignment.ProjectId, - "role": assignment.ProjectRole, - }), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(accessor, "team_id", assignment.TeamId), - resource.TestCheckResourceAttr(accessor, "project_id", assignment.ProjectId), - resource.TestCheckResourceAttr(accessor, "role", string(assignment.ProjectRole)), - ), - }, - }, + updateAsigment := client.TeamProjectAssignment{ + Id: "assignmentIdupdate", + TeamId: "teamIdUupdate", + ProjectId: "projectId0", + ProjectRole: client.Admin, } + t.Run("create", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "team_id": assignment.TeamId, + "project_id": assignment.ProjectId, + "role": assignment.ProjectRole, + }), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "team_id", assignment.TeamId), + resource.TestCheckResourceAttr(accessor, "project_id", assignment.ProjectId), + resource.TestCheckResourceAttr(accessor, "role", string(assignment.ProjectRole)), + ), + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + mock.EXPECT().TeamProjectAssignmentCreateOrUpdate(client.TeamProjectAssignmentPayload{TeamId: assignment.TeamId, ProjectId: assignment.ProjectId, ProjectRole: assignment.ProjectRole}).Times(1).Return(assignment, nil) + mock.EXPECT().TeamProjectAssignments(assignment.ProjectId).Times(1).Return([]client.TeamProjectAssignment{assignment}, nil) + mock.EXPECT().TeamProjectAssignmentDelete(assignment.Id).Times(1).Return(nil) + }) + }) - runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { - mock.EXPECT().TeamProjectAssignmentCreateOrUpdate(client.TeamProjectAssignmentPayload{TeamId: assignment.TeamId, ProjectId: assignment.ProjectId, ProjectRole: assignment.ProjectRole}).Times(1).Return(assignment, nil) - mock.EXPECT().TeamProjectAssignments(assignment.ProjectId).Times(1).Return([]client.TeamProjectAssignment{assignment}, nil) - mock.EXPECT().TeamProjectAssignmentDelete(assignment.Id).Times(1).Return(nil) + t.Run("detect drift", func(t *testing.T) { + driftTestCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "team_id": assignment.TeamId, + "project_id": assignment.ProjectId, + "role": assignment.ProjectRole, + }), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "team_id", assignment.TeamId), + resource.TestCheckResourceAttr(accessor, "project_id", assignment.ProjectId), + resource.TestCheckResourceAttr(accessor, "role", string(assignment.ProjectRole)), + ), + }, + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "team_id": updateAsigment.TeamId, + "project_id": assignment.ProjectId, + "role": assignment.ProjectRole, + }), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "team_id", updateAsigment.TeamId), + resource.TestCheckResourceAttr(accessor, "project_id", assignment.ProjectId), + resource.TestCheckResourceAttr(accessor, "role", string(assignment.ProjectRole)), + ), + }, + }, + } + + runUnitTest(t, driftTestCase, func(mock *client.MockApiClientInterface) { + mock.EXPECT().TeamProjectAssignmentCreateOrUpdate(client.TeamProjectAssignmentPayload{TeamId: assignment.TeamId, ProjectId: assignment.ProjectId, ProjectRole: assignment.ProjectRole}).Times(1).Return(assignment, nil) + mock.EXPECT().TeamProjectAssignmentCreateOrUpdate(client.TeamProjectAssignmentPayload{TeamId: updateAsigment.TeamId, ProjectId: assignment.ProjectId, ProjectRole: assignment.ProjectRole}).Times(1).Return(updateAsigment, nil) + gomock.InOrder( + mock.EXPECT().TeamProjectAssignments(assignment.ProjectId).Times(1).Return([]client.TeamProjectAssignment{assignment}, nil), + mock.EXPECT().TeamProjectAssignments(assignment.ProjectId).Times(1).Return([]client.TeamProjectAssignment{updateAsigment}, nil), + mock.EXPECT().TeamProjectAssignments(assignment.ProjectId).Times(1).Return([]client.TeamProjectAssignment{updateAsigment}, nil), + ) + mock.EXPECT().TeamProjectAssignmentDelete(updateAsigment.Id).Times(1).Return(nil) + }) }) + } diff --git a/env0/resource_template_project_assignment_test.go b/env0/resource_template_project_assignment_test.go index 1d835730..4834b6fc 100644 --- a/env0/resource_template_project_assignment_test.go +++ b/env0/resource_template_project_assignment_test.go @@ -40,7 +40,10 @@ func TestUnitTemplateProjectAssignmentResource(t *testing.T) { Id: "tid", ProjectIds: []string{"pid", "other-id"}, } - + driftReturnValues := client.Template{ + Id: "tid", + ProjectIds: []string{"other-id"}, + } updateReturnValues := client.Template{ Id: "updatetid", ProjectIds: []string{"updatepid"}, @@ -121,4 +124,26 @@ func TestUnitTemplateProjectAssignmentResource(t *testing.T) { Times(1).Return(client.Template{}, errors.New("error")) }) }) + + t.Run("detect drift", func(t *testing.T) { + runUnitTest(t, testCaseforCreate, func(mock *client.MockApiClientInterface) { + mock.EXPECT().AssignTemplateToProject(resourceTemplateAssignment["template_id"].(string), payLoad). + Times(1).Return(returnValues, nil) + + mock.EXPECT().AssignTemplateToProject(resourceTemplateAssignmentUpdate["template_id"].(string), updatePayload). + Times(1).Return(updateReturnValues, nil) + + mock.EXPECT().RemoveTemplateFromProject(resourceTemplateAssignmentUpdate["template_id"].(string), + resourceTemplateAssignmentUpdate["project_id"].(string)).Times(1).Return(nil) + + gomock.InOrder( + mock.EXPECT().Template(resourceTemplateAssignment["template_id"].(string)).Times(1). + Return(returnValues, nil), + mock.EXPECT().Template(resourceTemplateAssignment["template_id"].(string)).Times(1). + Return(driftReturnValues, nil), + mock.EXPECT().Template(resourceTemplateAssignmentUpdate["template_id"].(string)).Times(1). + Return(updateReturnValues, nil), + ) + }) + }) } From 79fd80dc14927e84264a4f5d0f579408af5962ba Mon Sep 17 00:00:00 2001 From: SHMUELBRENER Date: Thu, 17 Feb 2022 11:15:54 +0200 Subject: [PATCH 02/11] add unit test to resource_template --- env0/resource_template_test.go | 101 ++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/env0/resource_template_test.go b/env0/resource_template_test.go index 4308b730..758b4263 100644 --- a/env0/resource_template_test.go +++ b/env0/resource_template_test.go @@ -2,12 +2,13 @@ package env0 import ( "fmt" - "github.com/env0/terraform-provider-env0/client" - "github.com/golang/mock/gomock" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "regexp" "strconv" "testing" + + "github.com/env0/terraform-provider-env0/client" + "github.com/golang/mock/gomock" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) func TestUnitTemplateResource(t *testing.T) { @@ -581,4 +582,98 @@ func TestUnitTemplateResource(t *testing.T) { mock.EXPECT().TemplateDelete(template.Id).Times(1).Return(nil) }) }) + + t.Run("detect drift", func(t *testing.T) { + template := client.Template{ + Id: "id0", + Name: "template0", + Repository: "env0/repo", + } + + updateTemplate := client.Template{ + Id: "id0-update", + Name: "template0-update", + Repository: "env0/repo-update", + } + + templateWithDefaults := client.Template{ + Id: template.Id, + Name: template.Name, + Repository: template.Repository, + TerraformVersion: defaultVersion, + Type: string(defaultType), + } + templateWithDefaultsUpdate := client.Template{ + Id: updateTemplate.Id, + Name: updateTemplate.Name, + Repository: updateTemplate.Repository, + TerraformVersion: defaultVersion, + Type: string(defaultType), + } + + templateWithDrift := client.Template{ + IsDeleted: true, + Id: template.Id, + Name: template.Name, + Repository: template.Repository, + TerraformVersion: defaultVersion, + Type: string(defaultType), + } + + basicTemplateResourceConfig := func(resourceType string, resourceName string, template client.Template) string { + return resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ + "name": template.Name, + "repository": template.Repository, + }) + } + + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: basicTemplateResourceConfig(resourceType, resourceName, template), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceFullName, "id", template.Id), + resource.TestCheckResourceAttr(resourceFullName, "name", template.Name), + resource.TestCheckResourceAttr(resourceFullName, "repository", template.Repository), + resource.TestCheckResourceAttr(resourceFullName, "type", string(defaultType)), + resource.TestCheckResourceAttr(resourceFullName, "terraform_version", defaultVersion), + ), + }, + { + Config: basicTemplateResourceConfig(resourceType, resourceName, updateTemplate), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceFullName, "id", updateTemplate.Id), + resource.TestCheckResourceAttr(resourceFullName, "name", updateTemplate.Name), + resource.TestCheckResourceAttr(resourceFullName, "repository", updateTemplate.Repository), + resource.TestCheckResourceAttr(resourceFullName, "type", string(defaultType)), + resource.TestCheckResourceAttr(resourceFullName, "terraform_version", defaultVersion), + ), + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + + mock.EXPECT().TemplateCreate(client.TemplateCreatePayload{ + Name: template.Name, + Repository: template.Repository, + Type: defaultType, + TerraformVersion: defaultVersion, + }).Times(1).Return(template, nil) + + mock.EXPECT().TemplateCreate(client.TemplateCreatePayload{ + Name: updateTemplate.Name, + Repository: updateTemplate.Repository, + Type: defaultType, + TerraformVersion: defaultVersion, + }).Times(1).Return(updateTemplate, nil) + + gomock.InOrder( + mock.EXPECT().Template(template.Id).Times(1).Return(templateWithDefaults, nil), + mock.EXPECT().Template(template.Id).Times(1).Return(templateWithDrift, nil), + mock.EXPECT().Template(updateTemplate.Id).Times(1).Return(templateWithDefaultsUpdate, nil), + ) + mock.EXPECT().TemplateDelete(updateTemplate.Id).Times(1).Return(nil) + }) + }) } From 3868f818e1cd9e36156e9db1d2ce99f79f610410 Mon Sep 17 00:00:00 2001 From: SHMUELBRENER Date: Thu, 17 Feb 2022 15:51:04 +0200 Subject: [PATCH 03/11] fix cloud credentials project assignment --- env0/resource_cloud_credentials_project_assignment.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/env0/resource_cloud_credentials_project_assignment.go b/env0/resource_cloud_credentials_project_assignment.go index f2c762de..29f3ce47 100644 --- a/env0/resource_cloud_credentials_project_assignment.go +++ b/env0/resource_cloud_credentials_project_assignment.go @@ -60,12 +60,9 @@ func resourceCloudCredentialsProjectAssignmentRead(ctx context.Context, d *schem } } if !found { + d.SetId("") + return nil - if !d.IsNewResource() { - d.SetId("") - return nil - } - return diag.Errorf("could not find cloud credential project assignment.\n project id = %v, cloud credentials id = %v", projectId, credentialId) } d.SetId(getResourceId(credentialId, projectId)) From 234334f5b27de1837aad58db525caff70bd002e0 Mon Sep 17 00:00:00 2001 From: SHMUELBRENER Date: Thu, 17 Feb 2022 15:57:45 +0200 Subject: [PATCH 04/11] fix team project assignment --- env0/resource_team_project_assignment.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/env0/resource_team_project_assignment.go b/env0/resource_team_project_assignment.go index f0b9ec7f..0168ba2d 100644 --- a/env0/resource_team_project_assignment.go +++ b/env0/resource_team_project_assignment.go @@ -72,7 +72,7 @@ func resourceTeamProjectAssignmentRead(ctx context.Context, d *schema.ResourceDa } if !found { d.SetId("") - return nil + } return nil } From 0003acdf4af02c95a6b021183df7354c91c67a0f Mon Sep 17 00:00:00 2001 From: SHMUELBRENER Date: Thu, 17 Feb 2022 17:55:08 +0200 Subject: [PATCH 05/11] delete refrences to project and environment --- env0/resource_environment.go | 4 -- env0/resource_environment_test.go | 70 +----------------------------- env0/resource_project.go | 5 +-- env0/resource_project_test.go | 72 ------------------------------- 4 files changed, 3 insertions(+), 148 deletions(-) diff --git a/env0/resource_environment.go b/env0/resource_environment.go index ffb593ca..27fd5417 100644 --- a/env0/resource_environment.go +++ b/env0/resource_environment.go @@ -255,10 +255,6 @@ func resourceEnvironmentRead(ctx context.Context, d *schema.ResourceData, meta i return diag.Errorf("could not get environment: %v", err) } - if environment.IsArchived { - d.SetId("") - return nil - } environmentConfigurationVariables, err := apiClient.ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id) if err != nil { diff --git a/env0/resource_environment_test.go b/env0/resource_environment_test.go index ac83318e..d3cc3ef6 100644 --- a/env0/resource_environment_test.go +++ b/env0/resource_environment_test.go @@ -28,16 +28,7 @@ func TestUnitEnvironmentResource(t *testing.T) { }, } - driftEnvironment := client.Environment{ - IsArchived: true, - Id: "id0", - Name: "my-environment", - ProjectId: "project-id", - WorkspaceName: "workspace-name", - LatestDeploymentLog: client.DeploymentLog{ - BlueprintId: templateId, - }, - } + updatedEnvironment := client.Environment{ Id: environment.Id, @@ -895,62 +886,5 @@ func TestUnitEnvironmentResource(t *testing.T) { }) }) - t.Run("detect drift", func(t *testing.T) { - testCase := resource.TestCase{ - Steps: []resource.TestStep{ - { - Config: createEnvironmentResourceConfig(environment), - 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), - resource.TestCheckResourceAttr(accessor, "workspace", environment.WorkspaceName), - ), - }, - { - Config: createEnvironmentResourceConfig(updatedEnvironment), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(accessor, "id", updatedEnvironment.Id), - resource.TestCheckResourceAttr(accessor, "name", updatedEnvironment.Name), - resource.TestCheckResourceAttr(accessor, "project_id", updatedEnvironment.ProjectId), - resource.TestCheckResourceAttr(accessor, "template_id", templateId), - resource.TestCheckResourceAttr(accessor, "workspace", environment.WorkspaceName), - ), - }, - }, - } - runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { - mock.EXPECT().EnvironmentCreate(client.EnvironmentCreate{ - Name: environment.Name, - ProjectId: environment.ProjectId, - WorkspaceName: environment.WorkspaceName, - AutoDeployOnPathChangesOnly: &autoDeployOnPathChangesOnlyDefault, - AutoDeployByCustomGlob: autoDeployByCustomGlobDefault, - DeployRequest: &client.DeployRequest{ - BlueprintId: templateId, - }, - }).Times(1).Return(environment, nil) - - mock.EXPECT().EnvironmentCreate(client.EnvironmentCreate{ - Name: updatedEnvironment.Name, - ProjectId: updatedEnvironment.ProjectId, - WorkspaceName: updatedEnvironment.WorkspaceName, - AutoDeployOnPathChangesOnly: &autoDeployOnPathChangesOnlyDefault, - AutoDeployByCustomGlob: autoDeployByCustomGlobDefault, - DeployRequest: &client.DeployRequest{ - BlueprintId: templateId, - }, - }).Times(1).Return(updatedEnvironment, nil) - mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id).AnyTimes().Return([]client.ConfigurationVariable{}, nil) - - gomock.InOrder( - mock.EXPECT().Environment(gomock.Any()).Times(1).Return(environment, nil), - mock.EXPECT().Environment(gomock.Any()).Times(1).Return(driftEnvironment, nil), // 1 after create, 1 before update - mock.EXPECT().Environment(gomock.Any()).Times(1).Return(updatedEnvironment, nil), // 1 after update - ) - - mock.EXPECT().EnvironmentDestroy(environment.Id).Times(1) - }) - }) + } diff --git a/env0/resource_project.go b/env0/resource_project.go index f5c96573..fd4f7671 100644 --- a/env0/resource_project.go +++ b/env0/resource_project.go @@ -77,10 +77,7 @@ func resourceProjectRead(ctx context.Context, d *schema.ResourceData, meta inter if err != nil { return diag.Errorf("could not get project: %v", err) } - if project.IsArchived { - d.SetId("") - return nil - } + setProjectSchema(d, project) diff --git a/env0/resource_project_test.go b/env0/resource_project_test.go index fb0fffdc..accbd150 100644 --- a/env0/resource_project_test.go +++ b/env0/resource_project_test.go @@ -84,75 +84,3 @@ func TestUnitProjectInvalidParams(t *testing.T) { runUnitTest(t, testCase, func(mockFunc *client.MockApiClientInterface) {}) } - -func TestUnitProjectDrift(t *testing.T) { - resourceType := "env0_project" - resourceName := "test" - accessor := resourceAccessor(resourceType, resourceName) - - project := client.Project{ - Id: "id0", - Name: "name0", - Description: "description0", - } - - driftProject := client.Project{ - IsArchived: true, - Id: project.Id, - Name: "name0", - Description: "description0", - } - - projectAfterRecreate := client.Project{ - Id: project.Id, - Name: "new name", - Description: "new description", - } - - testCase := resource.TestCase{ - Steps: []resource.TestStep{ - { - Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ - "name": project.Name, - "description": project.Description, - }), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(accessor, "id", project.Id), - resource.TestCheckResourceAttr(accessor, "name", project.Name), - resource.TestCheckResourceAttr(accessor, "description", project.Description), - ), - }, - { - Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{ - "name": projectAfterRecreate.Name, - "description": projectAfterRecreate.Description, - }), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(accessor, "id", projectAfterRecreate.Id), - resource.TestCheckResourceAttr(accessor, "name", projectAfterRecreate.Name), - resource.TestCheckResourceAttr(accessor, "description", projectAfterRecreate.Description), - ), - }, - }, - } - runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { - mock.EXPECT().ProjectCreate(client.ProjectCreatePayload{ - Name: project.Name, - Description: project.Description, - }).Times(1).Return(project, nil) - mock.EXPECT().ProjectCreate(client.ProjectCreatePayload{ - Name: projectAfterRecreate.Name, - Description: projectAfterRecreate.Description, - }).Times(1).Return(projectAfterRecreate, nil) - - gomock.InOrder( - mock.EXPECT().Project(project.Id).Times(1).Return(project, nil), - mock.EXPECT().Project(project.Id).Times(1).Return(driftProject, nil), - mock.EXPECT().Project(project.Id).Times(1).Return(projectAfterRecreate, nil), - ) - - mock.EXPECT().ProjectDelete(project.Id).Times(1) - - }) - -} From d1d60de15841bbf7242942de0a48916c32cca0fe Mon Sep 17 00:00:00 2001 From: SHMUELBRENER Date: Thu, 17 Feb 2022 18:05:04 +0200 Subject: [PATCH 06/11] fix --- env0/resource_template_project_assignment_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/env0/resource_template_project_assignment_test.go b/env0/resource_template_project_assignment_test.go index 4834b6fc..27286ac1 100644 --- a/env0/resource_template_project_assignment_test.go +++ b/env0/resource_template_project_assignment_test.go @@ -126,6 +126,7 @@ func TestUnitTemplateProjectAssignmentResource(t *testing.T) { }) t.Run("detect drift", func(t *testing.T) { + runUnitTest(t, testCaseforCreate, func(mock *client.MockApiClientInterface) { mock.EXPECT().AssignTemplateToProject(resourceTemplateAssignment["template_id"].(string), payLoad). Times(1).Return(returnValues, nil) From 357054ee197b07fa02621b4a41dabc55b91e522d Mon Sep 17 00:00:00 2001 From: SHMUELBRENER Date: Thu, 17 Feb 2022 18:13:41 +0200 Subject: [PATCH 07/11] fix fmt --- env0/resource_environment.go | 1 - env0/resource_environment_test.go | 4 +--- env0/resource_project.go | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/env0/resource_environment.go b/env0/resource_environment.go index 27fd5417..47ca671f 100644 --- a/env0/resource_environment.go +++ b/env0/resource_environment.go @@ -255,7 +255,6 @@ func resourceEnvironmentRead(ctx context.Context, d *schema.ResourceData, meta i return diag.Errorf("could not get environment: %v", err) } - environmentConfigurationVariables, err := apiClient.ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id) if err != nil { return diag.Errorf("could not get environment configuration variables: %v", err) diff --git a/env0/resource_environment_test.go b/env0/resource_environment_test.go index d3cc3ef6..9f4a5a6e 100644 --- a/env0/resource_environment_test.go +++ b/env0/resource_environment_test.go @@ -28,8 +28,6 @@ func TestUnitEnvironmentResource(t *testing.T) { }, } - - updatedEnvironment := client.Environment{ Id: environment.Id, Name: "my-updated-environment-name", @@ -886,5 +884,5 @@ func TestUnitEnvironmentResource(t *testing.T) { }) }) - + } diff --git a/env0/resource_project.go b/env0/resource_project.go index fd4f7671..e232330d 100644 --- a/env0/resource_project.go +++ b/env0/resource_project.go @@ -77,7 +77,6 @@ func resourceProjectRead(ctx context.Context, d *schema.ResourceData, meta inter if err != nil { return diag.Errorf("could not get project: %v", err) } - setProjectSchema(d, project) From 696d15c531cae72152baed827ba9b47050e586cc Mon Sep 17 00:00:00 2001 From: SHMUELBRENER Date: Wed, 2 Mar 2022 20:41:35 +0200 Subject: [PATCH 08/11] add check for new resource --- env0/resource_cloud_credentials_project_assignment.go | 7 ++++--- env0/resource_team_project_assignment.go | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/env0/resource_cloud_credentials_project_assignment.go b/env0/resource_cloud_credentials_project_assignment.go index 29f3ce47..a5405f14 100644 --- a/env0/resource_cloud_credentials_project_assignment.go +++ b/env0/resource_cloud_credentials_project_assignment.go @@ -60,9 +60,10 @@ func resourceCloudCredentialsProjectAssignmentRead(ctx context.Context, d *schem } } if !found { - d.SetId("") - return nil - + if !d.IsNewResource() { + d.SetId("") + return nil + } } d.SetId(getResourceId(credentialId, projectId)) diff --git a/env0/resource_team_project_assignment.go b/env0/resource_team_project_assignment.go index 0168ba2d..95731358 100644 --- a/env0/resource_team_project_assignment.go +++ b/env0/resource_team_project_assignment.go @@ -71,8 +71,9 @@ func resourceTeamProjectAssignmentRead(ctx context.Context, d *schema.ResourceDa } } if !found { - d.SetId("") - + if !d.IsNewResource() { + d.SetId("") + } } return nil } From 443bf24900e40abc57b0e279f1d26276a0e6cd83 Mon Sep 17 00:00:00 2001 From: SHMUELBRENER Date: Tue, 8 Mar 2022 18:47:54 +0200 Subject: [PATCH 09/11] fix team project assignment --- env0/resource_team_project_assignment.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/env0/resource_team_project_assignment.go b/env0/resource_team_project_assignment.go index 95731358..b1e21be2 100644 --- a/env0/resource_team_project_assignment.go +++ b/env0/resource_team_project_assignment.go @@ -70,10 +70,8 @@ func resourceTeamProjectAssignmentRead(ctx context.Context, d *schema.ResourceDa break } } - if !found { - if !d.IsNewResource() { - d.SetId("") - } + if !found && !d.IsNewResource() { + d.SetId("") } return nil } From 8b20dc4aa1b0df01f1e1c5a46701ee0f03f2e090 Mon Sep 17 00:00:00 2001 From: SHMUELBRENER Date: Tue, 8 Mar 2022 19:24:52 +0200 Subject: [PATCH 10/11] update --- env0/resource_cloud_credentials_project_assignment.go | 8 +++----- env0/resource_template.go | 3 ++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/env0/resource_cloud_credentials_project_assignment.go b/env0/resource_cloud_credentials_project_assignment.go index a5405f14..ba6327bb 100644 --- a/env0/resource_cloud_credentials_project_assignment.go +++ b/env0/resource_cloud_credentials_project_assignment.go @@ -59,11 +59,9 @@ func resourceCloudCredentialsProjectAssignmentRead(ctx context.Context, d *schem found = true } } - if !found { - if !d.IsNewResource() { - d.SetId("") - return nil - } + if !found && !d.IsNewResource() { + d.SetId("") + return nil } d.SetId(getResourceId(credentialId, projectId)) diff --git a/env0/resource_template.go b/env0/resource_template.go index f005f61b..90d4cda7 100644 --- a/env0/resource_template.go +++ b/env0/resource_template.go @@ -3,12 +3,13 @@ package env0 import ( "context" "errors" + "log" + "github.com/env0/terraform-provider-env0/client" "github.com/google/uuid" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "log" ) func resourceTemplate() *schema.Resource { From c2ec119d609520f9038ee374e765173e65633ed5 Mon Sep 17 00:00:00 2001 From: SHMUELBRENER Date: Wed, 9 Mar 2022 13:44:47 +0200 Subject: [PATCH 11/11] add logs when drift detected --- env0/resource_cloud_credentials_project_assignment.go | 2 ++ env0/resource_team_project_assignment.go | 2 ++ env0/resource_template.go | 3 ++- env0/resource_template_project_assignment.go | 4 ++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/env0/resource_cloud_credentials_project_assignment.go b/env0/resource_cloud_credentials_project_assignment.go index ba6327bb..9f8fec82 100644 --- a/env0/resource_cloud_credentials_project_assignment.go +++ b/env0/resource_cloud_credentials_project_assignment.go @@ -2,6 +2,7 @@ package env0 import ( "context" + "log" "github.com/env0/terraform-provider-env0/client" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -60,6 +61,7 @@ func resourceCloudCredentialsProjectAssignmentRead(ctx context.Context, d *schem } } if !found && !d.IsNewResource() { + log.Printf("[WARN] Drift Detected: Terraform will remove %s from state", d.Id()) d.SetId("") return nil } diff --git a/env0/resource_team_project_assignment.go b/env0/resource_team_project_assignment.go index b1e21be2..a5514b8f 100644 --- a/env0/resource_team_project_assignment.go +++ b/env0/resource_team_project_assignment.go @@ -3,6 +3,7 @@ package env0 import ( "context" "fmt" + "log" . "github.com/env0/terraform-provider-env0/client" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -71,6 +72,7 @@ func resourceTeamProjectAssignmentRead(ctx context.Context, d *schema.ResourceDa } } if !found && !d.IsNewResource() { + log.Printf("[WARN] Drift Detected: Terraform will remove %s from state", d.Id()) d.SetId("") } return nil diff --git a/env0/resource_template.go b/env0/resource_template.go index 90d4cda7..36907c34 100644 --- a/env0/resource_template.go +++ b/env0/resource_template.go @@ -242,7 +242,8 @@ func resourceTemplateRead(ctx context.Context, d *schema.ResourceData, meta inte return diag.Errorf("could not get template: %v", err) } - if template.IsDeleted { + if template.IsDeleted && !d.IsNewResource() { + log.Printf("[WARN] Drift Detected: Terraform will remove %s from state", d.Id()) d.SetId("") return nil } diff --git a/env0/resource_template_project_assignment.go b/env0/resource_template_project_assignment.go index a79f3863..5c526691 100644 --- a/env0/resource_template_project_assignment.go +++ b/env0/resource_template_project_assignment.go @@ -2,6 +2,8 @@ package env0 import ( "context" + "log" + "github.com/env0/terraform-provider-env0/client" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -64,6 +66,7 @@ func resourceTemplateProjectAssignmentRead(ctx context.Context, d *schema.Resour } if template.IsDeleted { + log.Printf("[WARN] Drift Detected: Terraform will remove %s from state", d.Id()) d.SetId("") return nil } @@ -76,6 +79,7 @@ func resourceTemplateProjectAssignmentRead(ctx context.Context, d *schema.Resour } } if !isProjectIdInTemplate { + log.Printf("[WARN] Drift Detected: Terraform will remove %s from state", d.Id()) d.SetId("") return nil }