Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add project level auto destroy #1550

Merged
merged 44 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
c47bc97
Add existing changes for just modifying the attributes
simonxmh Dec 12, 2024
6bfea88
Remove testing residues
simonxmh Dec 12, 2024
bf80b24
Add conditional switch logic for nonbreaking changing
simonxmh Dec 12, 2024
cef9d18
Remove automated boolean setting
simonxmh Dec 16, 2024
01a4069
Add all test changes
simonxmh Dec 19, 2024
468036a
Add changelog changes and datasource
simonxmh Dec 19, 2024
37bacc3
Merge branch 'main' into simonxmh/add_project_level_auto_destroy
simonxmh Dec 19, 2024
822df2e
Add project data source changes
simonxmh Dec 19, 2024
2152c80
Add workspace data source tests
simonxmh Dec 19, 2024
1b115d0
Merge branch 'main' into simonxmh/add_project_level_auto_destroy
simonxmh Dec 20, 2024
63b9299
Add go mod replace for now
simonxmh Dec 20, 2024
cfd4c95
Revert test for auto destroy
simonxmh Dec 20, 2024
bbbc2cd
Change go mod, make automated toggle
simonxmh Jan 6, 2025
c05b61a
Add missing interface method
simonxmh Jan 6, 2025
2d2c113
Reduce nesting and add automated logic
simonxmh Jan 7, 2025
c57f046
Use the config level setting
simonxmh Jan 7, 2025
c2beedc
Change behavior of inherited field
simonxmh Jan 8, 2025
54858bc
Fix the data source test
simonxmh Jan 9, 2025
f6701d0
Merge branch 'main' into simonxmh/add_project_level_auto_destroy
simonxmh Jan 9, 2025
3c5ba7c
Remove duplicate tag binding stub
simonxmh Jan 9, 2025
541a519
Disable beta tests
simonxmh Jan 9, 2025
7e8107f
Merge branch 'main' into simonxmh/add_project_level_auto_destroy
simonxmh Jan 10, 2025
bc421e4
Fix creation flow
simonxmh Jan 10, 2025
0a185f2
Add workspace updates
simonxmh Jan 17, 2025
bde235b
Update change log
simonxmh Jan 22, 2025
696df89
Remove skip unless beta on project test
simonxmh Jan 22, 2025
786db2c
update go-tfe dependency
simonxmh Jan 22, 2025
c138f5e
Merge branch 'main' into simonxmh/add_project_level_auto_destroy
simonxmh Jan 22, 2025
8b794ca
downgrade go-tfe
simonxmh Jan 22, 2025
a5fb595
Fix state detection
simonxmh Jan 22, 2025
67f6af2
Merge branch 'main' into simonxmh/add_project_level_auto_destroy
simonxmh Jan 22, 2025
d3c9240
Add provider doc update in projects doc
simonxmh Jan 23, 2025
55c666b
Merge branch 'main' into simonxmh/add_project_level_auto_destroy
simonxmh Jan 23, 2025
760f7f8
Merge branch 'main' into simonxmh/add_project_level_auto_destroy
simonxmh Jan 23, 2025
0213836
Fix merged conflicts
simonxmh Jan 27, 2025
8d46bb0
Add provider doc update in projects doc
simonxmh Jan 23, 2025
bd30592
Merge branch 'main' into simonxmh/add_project_level_auto_destroy
simonxmh Jan 27, 2025
48315c8
Add ignore specificity based on attribute
simonxmh Jan 28, 2025
fa1b6b5
Change auto destroy activity duration into computed property
simonxmh Jan 28, 2025
522bdd0
Fix data source test
simonxmh Jan 29, 2025
f571c80
Add read sensitivity to unsetting
simonxmh Jan 29, 2025
6d46e79
Add doc changes
simonxmh Feb 3, 2025
d0bf2d1
Use SkipIfEnterprise on datasource test
simonxmh Feb 3, 2025
6dd39b3
Add inherits attr to attribute referene
simonxmh Feb 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## Unreleased

FEATURES:

* `r/tfe_project`: Add `auto_destroy_activity_duration` field to the project resource, which automatically propagates auto-destroy settings to workspaces [1550](https://github.com/hashicorp/terraform-provider-tfe/pull/1550)
* `d/tfe_project`: Add `auto_destroy_activity_duration` field to the project datasource [1550](https://github.com/hashicorp/terraform-provider-tfe/pull/1550)

## v.0.63.0

BUG FIXES:
Expand Down
74 changes: 45 additions & 29 deletions internal/provider/data_source_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ func dataSourceTFEProject() *schema.Resource {
Optional: true,
},

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

"workspace_ids": {
Type: schema.TypeSet,
Optional: true,
Expand Down Expand Up @@ -77,39 +82,50 @@ func dataSourceTFEProjectRead(ctx context.Context, d *schema.ResourceData, meta

for _, proj := range l.Items {
// Case-insensitive uniqueness is enforced in TFC
if strings.EqualFold(proj.Name, projName) {
// Only now include workspaces to cut down on request load.
readOptions := &tfe.WorkspaceListOptions{
ProjectID: proj.ID,
if !strings.EqualFold(proj.Name, projName) {
continue
}
// Only now include workspaces to cut down on request load.
readOptions := &tfe.WorkspaceListOptions{
ProjectID: proj.ID,
}
var workspaces []interface{}
var workspaceNames []interface{}
for {
wl, err := config.Client.Workspaces.List(ctx, orgName, readOptions)
if err != nil {
return diag.Errorf("Error retrieving workspaces: %v", err)
}

for _, workspace := range wl.Items {
workspaces = append(workspaces, workspace.ID)
workspaceNames = append(workspaceNames, workspace.Name)
}
var workspaces []interface{}
var workspaceNames []interface{}
for {
wl, err := config.Client.Workspaces.List(ctx, orgName, readOptions)
if err != nil {
return diag.Errorf("Error retrieving workspaces: %v", err)
}

for _, workspace := range wl.Items {
workspaces = append(workspaces, workspace.ID)
workspaceNames = append(workspaceNames, workspace.Name)
}

// Exit the loop when we've seen all pages.
if wl.CurrentPage >= wl.TotalPages {
break
}

// Update the page number to get the next page.
readOptions.PageNumber = wl.NextPage

// Exit the loop when we've seen all pages.
if wl.CurrentPage >= wl.TotalPages {
break
}

d.Set("workspace_ids", workspaces)
d.Set("workspace_names", workspaceNames)
d.Set("description", proj.Description)
d.SetId(proj.ID)
return nil
// Update the page number to get the next page.
readOptions.PageNumber = wl.NextPage
}

d.Set("workspace_ids", workspaces)
d.Set("workspace_names", workspaceNames)
d.Set("description", proj.Description)

var autoDestroyDuration string
if proj.AutoDestroyActivityDuration.IsSpecified() {
autoDestroyDuration, err = proj.AutoDestroyActivityDuration.Get()
if err != nil {
return diag.Errorf("Error reading auto destroy activity duration: %v", err)
}
}
d.Set("auto_destroy_activity_duration", autoDestroyDuration)
d.SetId(proj.ID)

return nil
}
return diag.Errorf("could not find project %s/%s", orgName, projName)
}
42 changes: 42 additions & 0 deletions internal/provider/data_source_project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,33 @@ func TestAccTFEProjectDataSource_caseInsensitive(t *testing.T) {
})
}

func TestAccTFEProjectDataSource_basicWithAutoDestroy(t *testing.T) {
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this feature requires a subscription upgrade we should skip this test for our nightly TFE CI using skipUnlessCloud(t).

Copy link
Contributor Author

@simonxmh simonxmh Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll use skipIfEnterprise if that's the flag you mean? I don't believe skipUnlessCloud is a thing. Unless you mean the opposite in skipIfCloud?


tfeClient, err := getClientUsingEnv()
if err != nil {
t.Fatal(err)
}
org, orgCleanup := createBusinessOrganization(t, tfeClient)
t.Cleanup(orgCleanup)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccTFEProjectDataSourceConfigWithAutoDestroy(rInt, org.Name, "3d"),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(
"data.tfe_project.foobar", "name", fmt.Sprintf("project-test-%d", rInt)),
resource.TestCheckResourceAttr(
"data.tfe_project.foobar", "auto_destroy_activity_duration", "3d"),
),
},
},
})
}

func testAccTFEProjectDataSourceConfig(rInt int) string {
return fmt.Sprintf(`
resource "tfe_organization" "foobar" {
Expand Down Expand Up @@ -112,3 +139,18 @@ data "tfe_project" "foobar" {
]
}`, rInt, rInt, rInt)
}

func testAccTFEProjectDataSourceConfigWithAutoDestroy(rInt int, orgName string, duration string) string {
return fmt.Sprintf(`
resource "tfe_project" "foobar" {
name = "project-test-%d"
description = "project description"
organization = "%s"
auto_destroy_activity_duration = "%s"
}

data "tfe_project" "foobar" {
name = tfe_project.foobar.name
organization = tfe_project.foobar.organization
}`, rInt, orgName, duration)
}
6 changes: 6 additions & 0 deletions internal/provider/data_source_workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ func dataSourceTFEWorkspace() *schema.Resource {
Computed: true,
},

"inherits_project_auto_destroy": {
Type: schema.TypeBool,
Computed: true,
},

"file_triggers_enabled": {
Type: schema.TypeBool,
Computed: true,
Expand Down Expand Up @@ -249,6 +254,7 @@ func dataSourceTFEWorkspaceRead(d *schema.ResourceData, meta interface{}) error
d.Set("file_triggers_enabled", workspace.FileTriggersEnabled)
d.Set("operations", workspace.Operations)
d.Set("policy_check_failures", workspace.PolicyCheckFailures)
d.Set("inherits_project_auto_destroy", workspace.InheritsProjectAutoDestroy)

autoDestroyAt, err := flattenAutoDestroyAt(workspace.AutoDestroyAt)
if err != nil {
Expand Down
12 changes: 10 additions & 2 deletions internal/provider/data_source_workspace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,10 @@ func TestAccTFEWorkspaceDataSource_readAutoDestroyAt(t *testing.T) {
},
{
Config: testAccTFEWorkspaceDataSourceConfig_basicWithAutoDestroy(rInt),
Check: resource.TestCheckResourceAttr("data.tfe_workspace.foobar", "auto_destroy_at", "2100-01-01T00:00:00Z"),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.tfe_workspace.foobar", "auto_destroy_at", "2100-01-01T00:00:00Z"),
resource.TestCheckResourceAttr("data.tfe_workspace.foobar", "inherits_project_auto_destroy", "false"),
),
},
},
})
Expand All @@ -203,7 +206,10 @@ func TestAccTFEWorkspaceDataSource_readAutoDestroyDuration(t *testing.T) {
},
{
Config: testAccTFEWorkspaceDataSourceConfig_basicWithAutoDestroyDuration(rInt),
Check: resource.TestCheckResourceAttr("data.tfe_workspace.foobar", "auto_destroy_activity_duration", "1d"),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("data.tfe_workspace.foobar", "auto_destroy_activity_duration", "1d"),
resource.TestCheckResourceAttr("data.tfe_workspace.foobar", "inherits_project_auto_destroy", "false"),
),
},
},
})
Expand Down Expand Up @@ -312,6 +318,7 @@ resource "tfe_workspace" "foobar" {
organization = tfe_organization.foobar.id
description = "provider-testing"
auto_destroy_at = "2100-01-01T00:00:00Z"
inherits_project_auto_destroy = false
}

data "tfe_workspace" "foobar" {
Expand All @@ -332,6 +339,7 @@ resource "tfe_workspace" "foobar" {
organization = tfe_organization.foobar.id
description = "provider-testing"
auto_destroy_activity_duration = "1d"
inherits_project_auto_destroy = false
}

data "tfe_workspace" "foobar" {
Expand Down
29 changes: 29 additions & 0 deletions internal/provider/resource_tfe_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"regexp"

tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/jsonapi"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
Expand Down Expand Up @@ -55,6 +56,12 @@ func resourceTFEProject() *schema.Resource {
Computed: true,
ForceNew: true,
},

"auto_destroy_activity_duration": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^\d{1,4}[dh]$`), "must be 1-4 digits followed by d or h"),
},
},
}
}
Expand All @@ -73,6 +80,10 @@ func resourceTFEProjectCreate(ctx context.Context, d *schema.ResourceData, meta
Description: tfe.String(d.Get("description").(string)),
}

if v, ok := d.GetOk("auto_destroy_activity_duration"); ok {
options.AutoDestroyActivityDuration = jsonapi.NewNullableAttrWithValue(v.(string))
}

log.Printf("[DEBUG] Create new project: %s", name)
project, err := config.Client.Projects.Create(ctx, organization, options)
if err != nil {
Expand Down Expand Up @@ -102,6 +113,15 @@ func resourceTFEProjectRead(ctx context.Context, d *schema.ResourceData, meta in
d.Set("description", project.Description)
d.Set("organization", project.Organization.Name)

if project.AutoDestroyActivityDuration.IsSpecified() {
v, err := project.AutoDestroyActivityDuration.Get()
if err != nil {
return diag.Errorf("Error reading auto destroy activity duration: %v", err)
}

d.Set("auto_destroy_activity_duration", v)
}

return nil
}

Expand All @@ -113,6 +133,15 @@ func resourceTFEProjectUpdate(ctx context.Context, d *schema.ResourceData, meta
Description: tfe.String(d.Get("description").(string)),
}

if d.HasChange("auto_destroy_activity_duration") {
duration, ok := d.GetOk("auto_destroy_activity_duration")
if !ok {
options.AutoDestroyActivityDuration = jsonapi.NewNullNullableAttr[string]()
} else {
options.AutoDestroyActivityDuration = jsonapi.NewNullableAttrWithValue(duration.(string))
}
}

log.Printf("[DEBUG] Update configuration of project: %s", d.Id())
project, err := config.Client.Projects.Update(ctx, d.Id(), options)
if err != nil {
Expand Down
51 changes: 51 additions & 0 deletions internal/provider/resource_tfe_project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,43 @@ func TestAccTFEProject_import(t *testing.T) {
})
}

func TestAccTFEProject_withAutoDestroy(t *testing.T) {
project := &tfe.Project{}
rInt := rand.New(rand.NewSource(time.Now().UnixNano())).Int()

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckTFEProjectDestroy,
Steps: []resource.TestStep{
{
Config: testAccTFEProject_basicWithAutoDestroy(rInt, "3d"),
Check: resource.ComposeTestCheckFunc(
testAccCheckTFEProjectExists(
"tfe_project.foobar", project),
testAccCheckTFEProjectAttributes(project),
resource.TestCheckResourceAttr(
"tfe_project.foobar", "auto_destroy_activity_duration", "3d"),
),
},
{
Config: testAccTFEProject_basicWithAutoDestroy(rInt, "10m"),
ExpectError: regexp.MustCompile(`must be 1-4 digits followed by d or h`),
},
{
Config: testAccTFEProject_basic(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckTFEProjectExists(
"tfe_project.foobar", project),
testAccCheckTFEProjectAttributes(project),
resource.TestCheckResourceAttr(
"tfe_project.foobar", "auto_destroy_activity_duration", ""),
),
},
},
})
}

func testAccTFEProject_update(rInt int) string {
return fmt.Sprintf(`
resource "tfe_organization" "foobar" {
Expand Down Expand Up @@ -182,6 +219,20 @@ resource "tfe_project" "foobar" {
}`, rInt)
}

func testAccTFEProject_basicWithAutoDestroy(rInt int, duration string) string {
return fmt.Sprintf(`
resource "tfe_organization" "foobar" {
name = "tst-terraform-%d"
email = "[email protected]"
}

resource "tfe_project" "foobar" {
organization = tfe_organization.foobar.name
name = "projecttest"
auto_destroy_activity_duration = "%s"
}`, rInt, duration)
}

func testAccCheckTFEProjectDestroy(s *terraform.State) error {
config := testAccProvider.Meta().(ConfiguredClient)

Expand Down
15 changes: 13 additions & 2 deletions internal/provider/resource_tfe_workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ func resourceTFEWorkspace() *schema.Resource {
Deprecated: "Use resource `tfe_workspace_settings` to modify the workspace `global_remote_state`. `global_remote_state` on `tfe_workspace` is no longer validated properly and will be removed in a future release of the provider.",
},

"inherits_project_auto_destroy": {
Type: schema.TypeBool,
Optional: true,
Computed: true,
},

"remote_state_consumer_ids": {
Type: schema.TypeSet,
Optional: true,
Expand Down Expand Up @@ -538,6 +544,7 @@ func resourceTFEWorkspaceRead(d *schema.ResourceData, meta interface{}) error {
d.Set("working_directory", workspace.WorkingDirectory)
d.Set("organization", workspace.Organization.Name)
d.Set("resource_count", workspace.ResourceCount)
d.Set("inherits_project_auto_destroy", workspace.InheritsProjectAutoDestroy)

if workspace.Links["self-html"] != nil {
baseAPI := config.Client.BaseURL()
Expand Down Expand Up @@ -578,8 +585,9 @@ func resourceTFEWorkspaceRead(d *schema.ResourceData, meta interface{}) error {
if err != nil {
return fmt.Errorf("Error reading auto destroy activity duration: %w", err)
}

d.Set("auto_destroy_activity_duration", v)
if !workspace.InheritsProjectAutoDestroy {
d.Set("auto_destroy_activity_duration", v)
}
}

var tagNames []interface{}
Expand Down Expand Up @@ -1119,6 +1127,9 @@ func flattenAutoDestroyAt(a jsonapi.NullableAttr[time.Time]) (*string, error) {
func hasAutoDestroyAtChange(d *schema.ResourceData) bool {
state := d.GetRawState()
if state.IsNull() {
if state.GetAttr("inherits_project_auto_destroy").True() {
return false
}
return d.HasChange("auto_destroy_at")
}

Expand Down
Loading
Loading