diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b503d93e..b5c4c82c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,7 @@ jobs: uses: goreleaser/goreleaser-action@v5 with: version: latest - args: release --rm-dist + args: release --clean env: GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/client/api_client.go b/client/api_client.go index 59182507..0b0dedb5 100644 --- a/client/api_client.go +++ b/client/api_client.go @@ -70,6 +70,7 @@ type ApiClientInterface interface { EnvironmentCreate(payload EnvironmentCreate) (Environment, error) EnvironmentCreateWithoutTemplate(payload EnvironmentCreateWithoutTemplate) (Environment, error) EnvironmentDestroy(id string) (Environment, error) + EnvironmentMarkAsArchived(id string) error EnvironmentUpdate(id string, payload EnvironmentUpdate) (Environment, error) EnvironmentDeploy(id string, payload DeployRequest) (EnvironmentDeployResponse, error) EnvironmentUpdateTTL(id string, payload TTL) (Environment, error) diff --git a/client/api_client_mock.go b/client/api_client_mock.go index 0de49f17..7ac32f6a 100644 --- a/client/api_client_mock.go +++ b/client/api_client_mock.go @@ -704,6 +704,20 @@ func (mr *MockApiClientInterfaceMockRecorder) EnvironmentDriftDetection(arg0 any return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnvironmentDriftDetection", reflect.TypeOf((*MockApiClientInterface)(nil).EnvironmentDriftDetection), arg0) } +// EnvironmentMarkAsArchived mocks base method. +func (m *MockApiClientInterface) EnvironmentMarkAsArchived(arg0 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnvironmentMarkAsArchived", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// EnvironmentMarkAsArchived indicates an expected call of EnvironmentMarkAsArchived. +func (mr *MockApiClientInterfaceMockRecorder) EnvironmentMarkAsArchived(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnvironmentMarkAsArchived", reflect.TypeOf((*MockApiClientInterface)(nil).EnvironmentMarkAsArchived), arg0) +} + // EnvironmentScheduling mocks base method. func (m *MockApiClientInterface) EnvironmentScheduling(arg0 string) (EnvironmentScheduling, error) { m.ctrl.T.Helper() diff --git a/client/environment.go b/client/environment.go index 7d24d1c1..fd8ba905 100644 --- a/client/environment.go +++ b/client/environment.go @@ -270,6 +270,14 @@ func (client *ApiClient) EnvironmentUpdate(id string, payload EnvironmentUpdate) return result, nil } +func (client *ApiClient) EnvironmentMarkAsArchived(id string) error { + payload := struct { + IsArchived bool `json:"isArchived"` + }{true} + + return client.http.Put("/environments/"+id, &payload, nil) +} + func (client *ApiClient) EnvironmentUpdateTTL(id string, payload TTL) (Environment, error) { var result Environment err := client.http.Put("/environments/"+id+"/ttl", payload, &result) diff --git a/client/environment_test.go b/client/environment_test.go index aeb14756..da39dfcf 100644 --- a/client/environment_test.go +++ b/client/environment_test.go @@ -277,6 +277,26 @@ var _ = Describe("Environment Client", func() { }) }) + Describe("EnvironmentMarkAsArchived", func() { + payload := struct { + IsArchived bool `json:"isArchived"` + }{true} + + var err error + + BeforeEach(func() { + httpCall = mockHttpClient.EXPECT().Put("/environments/"+mockEnvironment.Id, &payload, nil).Return(nil).Times(1) + err = apiClient.EnvironmentMarkAsArchived(mockEnvironment.Id) + }) + + It("Should send an archive put request", func() {}) + + It("Should not return error", func() { + Expect(err).To(BeNil()) + }) + + }) + Describe("EnvironmentUpdate", func() { Describe("Success", func() { var updatedEnvironment Environment diff --git a/env0/resource_environment.go b/env0/resource_environment.go index 4c9769f9..5d8ca15f 100644 --- a/env0/resource_environment.go +++ b/env0/resource_environment.go @@ -335,6 +335,13 @@ func resourceEnvironment() *schema.Resource { Optional: true, Default: false, }, + "removal_strategy": { + Type: schema.TypeString, + Description: "by default when removing an environment, it gets destroyed. Setting this value to 'mark_as_archived' will force the environment to be archived instead of tying to destroy it ('Mark as inactive' in the UI)", + Optional: true, + Default: "destroy", + ValidateDiagFunc: NewStringInValidator([]string{"destroy", "mark_as_archived"}), + }, }, CustomizeDiff: customdiff.ValidateChange("template_id", func(ctx context.Context, oldValue, newValue, meta interface{}) error { if oldValue != "" && oldValue != newValue { @@ -768,14 +775,24 @@ func updateTTL(d *schema.ResourceData, apiClient client.ApiClientInterface) diag } func resourceEnvironmentDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(client.ApiClientInterface) + + markAsArchived := d.Get("removal_strategy").(string) == "mark_as_archived" + + if markAsArchived { + if err := apiClient.EnvironmentMarkAsArchived(d.Id()); err != nil { + return diag.Errorf("could not archive the environment: %v", err) + } + + return nil + } + canDestroy := d.Get("force_destroy") if canDestroy != true { return diag.Errorf(`must enable "force_destroy" safeguard in order to destroy`) } - apiClient := meta.(client.ApiClientInterface) - _, err := apiClient.EnvironmentDestroy(d.Id()) if err != nil { if frerr, ok := err.(*http.FailedResponseError); ok && frerr.BadRequest() { @@ -1180,6 +1197,7 @@ func resourceEnvironmentImport(ctx context.Context, d *schema.ResourceData, meta } d.Set("force_destroy", false) + d.Set("removal_strategy", "destroy") if getErr != nil { return nil, errors.New(getErr[0].Summary) diff --git a/env0/resource_environment_test.go b/env0/resource_environment_test.go index a47e7b3f..26e78d2a 100644 --- a/env0/resource_environment_test.go +++ b/env0/resource_environment_test.go @@ -412,6 +412,54 @@ func TestUnitEnvironmentResource(t *testing.T) { }) }) + t.Run("Mark as archived", func(t *testing.T) { + 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": false, + "removal_strategy": "mark_as_archived", + }), + 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), + ), + }, + }, + } + + 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().EnvironmentMarkAsArchived(environment.Id).Times(1).Return(nil), + ) + }) + }) + t.Run("Import By Id", func(t *testing.T) { testCase := resource.TestCase{ Steps: []resource.TestStep{ diff --git a/tests/integration/012_environment/main.tf b/tests/integration/012_environment/main.tf index b6a5723e..47ef2cf0 100644 --- a/tests/integration/012_environment/main.tf +++ b/tests/integration/012_environment/main.tf @@ -226,3 +226,11 @@ resource "env0_environment" "workflow-environment" { } } } + +resource "env0_environment" "mark_as_archived" { + depends_on = [env0_template_project_assignment.assignment] + name = "environment-mark-as-archived-${random_string.random.result}" + project_id = env0_project.test_project.id + template_id = env0_template.template.id + removal_strategy = "mark_as_archived" +}