Skip to content

Commit

Permalink
feat(workspace): add auto destroy attribute to resource and data source
Browse files Browse the repository at this point in the history
  • Loading branch information
notchairmk committed May 10, 2024
1 parent c842c97 commit 615ea2e
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## UNRELEASED

ENHANCEMENTS:
* `r/tfe_workspace`: Add an `auto_destroy_at` attribute for scheduling an auto-destroy run in the future, by @notchairmk [1354](https://github.com/hashicorp/terraform-provider-tfe/pull/1354)
* `d/tfe_workspace`: Add an `auto_destroy_at` attribute for reading a scheduled auto-destroy, by @notchairmk [1354](https://github.com/hashicorp/terraform-provider-tfe/pull/1354)

## v0.54.0

ENHANCEMENTS:
Expand Down
11 changes: 11 additions & 0 deletions internal/provider/data_source_workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ func dataSourceTFEWorkspace() *schema.Resource {
Computed: true,
},

"auto_destroy_at": {
Type: schema.TypeString,
Computed: true,
},

"file_triggers_enabled": {
Type: schema.TypeBool,
Computed: true,
Expand Down Expand Up @@ -240,6 +245,12 @@ func dataSourceTFEWorkspaceRead(d *schema.ResourceData, meta interface{}) error
d.Set("operations", workspace.Operations)
d.Set("policy_check_failures", workspace.PolicyCheckFailures)

autoDestroyAt, err := flattenAutoDestroyAt(workspace.AutoDestroyAt)
if err != nil {
return fmt.Errorf("Error flattening auto_destroy_at from workspace response: %w", err)
}
d.Set("auto_destroy_at", autoDestroyAt)

// If target tfe instance predates projects, then workspace.Project will be nil
if workspace.Project != nil {
d.Set("project_id", workspace.Project.ID)
Expand Down
57 changes: 57 additions & 0 deletions internal/provider/data_source_workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,25 @@ func TestAccTFEWorkspaceDataSourceWithTriggerPatterns(t *testing.T) {
})
}

func TestAccTFEWorkspaceDataSource_readAutoDestroyAt(t *testing.T) {
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccTFEWorkspaceDataSourceConfig_basic(rInt),
Check: resource.TestCheckResourceAttr("data.tfe_workspace.foobar", "auto_destroy_at", ""),
},
{
Config: testAccTFEWorkspaceDataSourceConfig_basicWithAutoDestroy(rInt),
Check: resource.TestCheckResourceAttr("data.tfe_workspace.foobar", "auto_destroy_at", "2100-01-01T00:00:00Z"),
},
},
})
}

func TestAccTFEWorkspaceDataSource_readProjectIDDefault(t *testing.T) {
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()

Expand Down Expand Up @@ -243,6 +262,44 @@ data "tfe_workspace" "foobar" {
}`, rInt, rInt, aart)
}

func testAccTFEWorkspaceDataSourceConfig_basic(rInt int) string {
return fmt.Sprintf(`
resource "tfe_organization" "foobar" {
name = "tst-terraform-%d"
email = "admin@company.com"
}
resource "tfe_workspace" "foobar" {
name = "workspace-test-%d"
organization = tfe_organization.foobar.id
description = "provider-testing"
}
data "tfe_workspace" "foobar" {
name = tfe_workspace.foobar.name
organization = tfe_workspace.foobar.organization
}`, rInt, rInt)
}

func testAccTFEWorkspaceDataSourceConfig_basicWithAutoDestroy(rInt int) string {
return fmt.Sprintf(`
resource "tfe_organization" "foobar" {
name = "tst-terraform-%d"
email = "admin@company.com"
}
resource "tfe_workspace" "foobar" {
name = "workspace-test-%d"
organization = tfe_organization.foobar.id
description = "provider-testing"
auto_destroy_at = "2100-01-01T00:00:00Z"
}
data "tfe_workspace" "foobar" {
name = tfe_workspace.foobar.name
organization = tfe_workspace.foobar.organization
}`, rInt, rInt)
}
func testAccTFEWorkspaceDataSourceConfigWithTriggerPatterns(workspaceName string, organizationName string) string {
return fmt.Sprintf(`
data "tfe_workspace" "foobar" {
Expand Down
59 changes: 58 additions & 1 deletion internal/provider/resource_tfe_workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"time"

tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/jsonapi"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
Expand Down Expand Up @@ -109,6 +110,11 @@ func resourceTFEWorkspace() *schema.Resource {
Default: false,
},

"auto_destroy_at": {
Type: schema.TypeString,
Optional: true,
},

"execution_mode": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -340,6 +346,14 @@ func resourceTFEWorkspaceCreate(d *schema.ResourceData, meta interface{}) error
}
}

if _, ok := d.GetOk("auto_destroy_at"); ok {
autoDestroyAt, err := expandAutoDestroyAt(d)
if err != nil {
return err
}
options.AutoDestroyAt = autoDestroyAt
}

if v, ok := d.GetOk("execution_mode"); ok {
executionMode := tfe.String(v.(string))
options.SettingOverwrites = &tfe.WorkspaceSettingOverwritesOptions{
Expand Down Expand Up @@ -533,6 +547,12 @@ func resourceTFEWorkspaceRead(d *schema.ResourceData, meta interface{}) error {
}
d.Set("agent_pool_id", agentPoolID)

autoDestroyAt, err := flattenAutoDestroyAt(workspace.AutoDestroyAt)
if err != nil {
return fmt.Errorf("Error flattening autoDestroyAt: %w", err)
}
d.Set("auto_destroy_at", autoDestroyAt)

var tagNames []interface{}
managedTags := d.Get("tag_names").(*schema.Set)
for _, tagName := range workspace.TagNames {
Expand Down Expand Up @@ -585,7 +605,7 @@ func resourceTFEWorkspaceUpdate(d *schema.ResourceData, meta interface{}) error
d.HasChange("operations") || d.HasChange("execution_mode") ||
d.HasChange("description") || d.HasChange("agent_pool_id") ||
d.HasChange("global_remote_state") || d.HasChange("structured_run_output_enabled") ||
d.HasChange("assessments_enabled") || d.HasChange("project_id") {
d.HasChange("assessments_enabled") || d.HasChange("project_id") || d.HasChange("auto_destroy_at") {
// Create a new options struct.
options := tfe.WorkspaceUpdateOptions{
Name: tfe.String(d.Get("name").(string)),
Expand Down Expand Up @@ -638,6 +658,14 @@ func resourceTFEWorkspaceUpdate(d *schema.ResourceData, meta interface{}) error
}
}

if d.HasChange("auto_destroy_at") {
autoDestroyAt, err := expandAutoDestroyAt(d)
if err != nil {
return fmt.Errorf("Error expanding autoDestroyAt: %w", err)
}
options.AutoDestroyAt = autoDestroyAt
}

if d.HasChange("execution_mode") {
if v, ok := d.GetOk("execution_mode"); ok {
options.ExecutionMode = tfe.String(v.(string))
Expand Down Expand Up @@ -933,6 +961,35 @@ func validateAgentExecution(_ context.Context, d *schema.ResourceDiff) error {
return nil
}

func expandAutoDestroyAt(d *schema.ResourceData) (jsonapi.NullableAttr[time.Time], error) {
v, ok := d.GetOk("auto_destroy_at")

if !ok {
return jsonapi.NewNullNullableAttr[time.Time](), nil
}

autoDestroyAt, err := time.Parse(time.RFC3339, v.(string))
if err != nil {
return nil, err
}

return jsonapi.NewNullableAttrWithValue(autoDestroyAt), nil
}

func flattenAutoDestroyAt(a jsonapi.NullableAttr[time.Time]) (*string, error) {
if !a.IsSpecified() {
return nil, nil
}

autoDestroyTime, err := a.Get()
if err != nil {
return nil, err
}

autoDestroyAt := autoDestroyTime.Format(time.RFC3339)
return &autoDestroyAt, nil
}

func validTagName(tag string) bool {
// Tags are re-validated here because the API will accept uppercase letters and automatically
// downcase them, causing resource drift. It's better to catch this issue during the plan phase
Expand Down
62 changes: 62 additions & 0 deletions internal/provider/resource_tfe_workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2648,6 +2648,52 @@ func TestAccTFEWorkspace_basicAssessmentsEnabled(t *testing.T) {
})
}

func TestAccTFEWorkspace_createWithAutoDestroyAt(t *testing.T) {
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckTFEWorkspaceDestroy,
Steps: []resource.TestStep{
{
Config: testAccTFEWorkspace_basicWithAutoDestroyAt(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckTFEWorkspaceExists("tfe_workspace.foobar", &tfe.Workspace{}, testAccProvider),
resource.TestCheckResourceAttr("tfe_workspace.foobar", "auto_destroy_at", "2100-01-01T00:00:00Z"),
),
},
},
})
}

func TestAccTFEWorkspace_updateWithAutoDestroyAt(t *testing.T) {
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckTFEWorkspaceDestroy,
Steps: []resource.TestStep{
{
Config: testAccTFEWorkspace_basic(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckTFEWorkspaceExists("tfe_workspace.foobar", &tfe.Workspace{}, testAccProvider),
resource.TestCheckResourceAttr("tfe_workspace.foobar", "auto_destroy_at", ""),
),
},
{
Config: testAccTFEWorkspace_basicWithAutoDestroyAt(rInt),
Check: resource.TestCheckResourceAttr("tfe_workspace.foobar", "auto_destroy_at", "2100-01-01T00:00:00Z"),
},
{
Config: testAccTFEWorkspace_basic(rInt),
Check: resource.TestCheckResourceAttr("tfe_workspace.foobar", "auto_destroy_at", ""),
},
},
})
}

func TestAccTFEWorkspace_createWithSourceURL(t *testing.T) {
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()

Expand Down Expand Up @@ -2924,6 +2970,22 @@ resource "tfe_workspace" "foobar" {
}`, rInt)
}

func testAccTFEWorkspace_basicWithAutoDestroyAt(rInt int) string {
return fmt.Sprintf(`
resource "tfe_organization" "foobar" {
name = "tst-terraform-%d"
email = "admin@company.com"
}
resource "tfe_workspace" "foobar" {
name = "workspace-test"
organization = tfe_organization.foobar.id
auto_apply = true
file_triggers_enabled = false
auto_destroy_at = "2100-01-01T00:00:00Z"
}`, rInt)
}

func testAccTFEWorkspace_operationsTrue(organization string) string {
return fmt.Sprintf(`
resource "tfe_workspace" "foobar" {
Expand Down
1 change: 1 addition & 0 deletions website/docs/d/workspace.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ In addition to all arguments above, the following attributes are exported:
* `allow_destroy_plan` - Indicates whether destroy plans can be queued on the workspace.
* `auto_apply` - Indicates whether to automatically apply changes when a Terraform plan is successful.
* `auto_apply_run_trigger` - Whether the workspace will automatically apply changes for runs that were created by run triggers from another workspace.
* `auto_destroy_at` - Future date/time string at which point all resources in a workspace will be scheduled to be deleted.
* `assessments_enabled` - (Available only in HCP Terraform) Indicates whether health assessments such as drift detection are enabled for the workspace.
* `file_triggers_enabled` - Indicates whether runs are triggered based on the changed files in a VCS push (if `true`) or always triggered on every push (if `false`).
* `global_remote_state` - (Optional) Whether the workspace should allow all workspaces in the organization to access its state data during runs. If false, then only specifically approved workspaces can access its state (determined by the `remote_state_consumer_ids` argument).
Expand Down
1 change: 1 addition & 0 deletions website/docs/r/workspace.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ The following arguments are supported:
* `assessments_enabled` - (Optional) Whether to regularly run health assessments such as drift detection on the workspace. Defaults to `false`.
* `auto_apply` - (Optional) Whether to automatically apply changes when a Terraform plan is successful. Defaults to `false`.
* `auto_apply_run_trigger` - (Optional) Whether to automatically apply changes for runs that were created by run triggers from another workspace. Defaults to `false`.
* `auto_destroy_at` - (Optional) A future date/time string at which point all resources in a workspace will be scheduled for deletion. Must be a string in RFC3339 format (e.g. "2100-01-01T00:00:00Z").
* `description` - (Optional) A description for the workspace.
* `execution_mode` - (Optional) **Deprecated** Which [execution mode](https://developer.hashicorp.com/terraform/cloud-docs/workspaces/settings#execution-mode) to use. Use [tfe_workspace_settings](workspace_settings) instead.
* `file_triggers_enabled` - (Optional) Whether to filter runs based on the changed files
Expand Down

0 comments on commit 615ea2e

Please sign in to comment.