Skip to content

Commit 98975f4

Browse files
simonxmhsebasslash
andauthored
Add project level auto destroy setting (#1011)
* Add project level settings for auto destroy setting * Add missing example change * fix fmt * Update changelog * Fix typo * Run go fmt * Add project moving workspace scoped tests * Run go fmt again * Fix potential subscription error * Add workspace level indicator of project inheritance * Add new subscription updater * Remove some moving tests that have dependent logic * Update API for go-tfe * Add fmt changes * update the main example * Update all to use business plan * Add skip unless beta tags to certain tests * Run fmt * Update CHANGELOG.md Co-authored-by: Sebastian Rivera <[email protected]> --------- Co-authored-by: Sebastian Rivera <[email protected]>
1 parent f9d7888 commit 98975f4

File tree

7 files changed

+174
-24
lines changed

7 files changed

+174
-24
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Unreleased
22

3+
## Enhancements
4+
* Add support for project level auto destroy settings @simonxmh [#1011](https://github.com/hashicorp/go-tfe/pull/1011)
5+
36
# v1.71.0
47

58
## Enhancements

examples/projects/main.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package main
5+
6+
import (
7+
"context"
8+
"log"
9+
10+
tfe "github.com/hashicorp/go-tfe"
11+
12+
"github.com/hashicorp/jsonapi"
13+
)
14+
15+
func main() {
16+
config := &tfe.Config{
17+
Token: "insert-your-token-here",
18+
RetryServerErrors: true,
19+
}
20+
21+
client, err := tfe.NewClient(config)
22+
if err != nil {
23+
log.Fatal(err)
24+
}
25+
26+
// Create a context
27+
ctx := context.Background()
28+
29+
// Create a new project
30+
p, err := client.Projects.Create(ctx, "org-test", tfe.ProjectCreateOptions{
31+
Name: "my-app-tst",
32+
})
33+
if err != nil {
34+
log.Fatal(err)
35+
}
36+
37+
// Update the project auto destroy activity duration
38+
p, err = client.Projects.Update(ctx, p.ID, tfe.ProjectUpdateOptions{
39+
AutoDestroyActivityDuration: jsonapi.NewNullableAttrWithValue("3d"),
40+
})
41+
if err != nil {
42+
log.Fatal(err)
43+
}
44+
45+
// Disable auto destroy
46+
p, err = client.Projects.Update(ctx, p.ID, tfe.ProjectUpdateOptions{
47+
AutoDestroyActivityDuration: jsonapi.NewNullNullableAttr[string](),
48+
})
49+
if err != nil {
50+
log.Fatal(err)
51+
}
52+
53+
err = client.Projects.Delete(ctx, p.ID)
54+
if err != nil {
55+
log.Fatal(err)
56+
}
57+
}

examples/workspaces/main.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,21 @@ func main() {
2727

2828
// Create a new workspace
2929
w, err := client.Workspaces.Create(ctx, "org-name", tfe.WorkspaceCreateOptions{
30-
Name: tfe.String("my-app-tst"),
31-
AutoDestroyAt: tfe.NullableTime(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)),
30+
Name: tfe.String("my-app-tst"),
31+
AutoDestroyAt: tfe.NullableTime(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)),
32+
InheritsProjectAutoDestroy: tfe.Bool(false),
3233
})
3334
if err != nil {
3435
log.Fatal(err)
3536
}
3637

3738
// Update the workspace
3839
w, err = client.Workspaces.Update(ctx, "org-name", w.Name, tfe.WorkspaceUpdateOptions{
39-
AutoApply: tfe.Bool(false),
40-
TerraformVersion: tfe.String("0.11.1"),
41-
WorkingDirectory: tfe.String("my-app/infra"),
42-
AutoDestroyAt: tfe.NullableTime(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)),
40+
AutoApply: tfe.Bool(false),
41+
TerraformVersion: tfe.String("0.11.1"),
42+
WorkingDirectory: tfe.String("my-app/infra"),
43+
AutoDestroyAt: tfe.NullableTime(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)),
44+
InheritsProjectAutoDestroy: tfe.Bool(false),
4345
})
4446
if err != nil {
4547
log.Fatal(err)

project.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"context"
88
"fmt"
99
"net/url"
10+
11+
"github.com/hashicorp/jsonapi"
1012
)
1113

1214
// Compile-time proof of interface implementation.
@@ -63,6 +65,8 @@ type Project struct {
6365

6466
Description string `jsonapi:"attr,description"`
6567

68+
AutoDestroyActivityDuration jsonapi.NullableAttr[string] `jsonapi:"attr,auto-destroy-activity-duration,omitempty"`
69+
6670
// Relations
6771
Organization *Organization `jsonapi:"relation,organization"`
6872
}
@@ -100,6 +104,11 @@ type ProjectCreateOptions struct {
100104

101105
// Associated TagBindings of the project.
102106
TagBindings []*TagBinding `jsonapi:"relation,tag-bindings,omitempty"`
107+
108+
// Optional: For all workspaces in the project, the period of time to wait
109+
// after workspace activity to trigger a destroy run. The format should roughly
110+
// match a Go duration string limited to days and hours, e.g. "24h" or "1d".
111+
AutoDestroyActivityDuration jsonapi.NullableAttr[string] `jsonapi:"attr,auto-destroy-activity-duration,omitempty"`
103112
}
104113

105114
// ProjectUpdateOptions represents the options for updating a project
@@ -119,6 +128,11 @@ type ProjectUpdateOptions struct {
119128
// Associated TagBindings of the project. Note that this will replace
120129
// all existing tag bindings.
121130
TagBindings []*TagBinding `jsonapi:"relation,tag-bindings,omitempty"`
131+
132+
// Optional: For all workspaces in the project, the period of time to wait
133+
// after workspace activity to trigger a destroy run. The format should roughly
134+
// match a Go duration string limited to days and hours, e.g. "24h" or "1d".
135+
AutoDestroyActivityDuration jsonapi.NullableAttr[string] `jsonapi:"attr,auto-destroy-activity-duration,omitempty"`
122136
}
123137

124138
// ProjectAddTagBindingsOptions represents the options for adding tag bindings

projects_integration_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"github.com/stretchr/testify/assert"
1111

1212
"github.com/stretchr/testify/require"
13+
14+
"github.com/hashicorp/jsonapi"
1315
)
1416

1517
func TestProjectsList(t *testing.T) {
@@ -150,6 +152,8 @@ func TestProjectsCreate(t *testing.T) {
150152
orgTest, orgTestCleanup := createOrganization(t, client)
151153
defer orgTestCleanup()
152154

155+
newSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)
156+
153157
t.Run("with valid options", func(t *testing.T) {
154158
options := ProjectCreateOptions{
155159
Name: "foo",
@@ -193,6 +197,17 @@ func TestProjectsCreate(t *testing.T) {
193197
assert.Nil(t, w)
194198
assert.EqualError(t, err, ErrInvalidOrg.Error())
195199
})
200+
201+
t.Run("when options has an invalid auto destroy activity duration", func(t *testing.T) {
202+
skipUnlessBeta(t)
203+
204+
w, err := client.Projects.Create(ctx, orgTest.Name, ProjectCreateOptions{
205+
Name: "foo",
206+
AutoDestroyActivityDuration: jsonapi.NewNullableAttrWithValue("20m"),
207+
})
208+
assert.Nil(t, w)
209+
assert.Contains(t, err.Error(), "invalid attribute\n\nAuto destroy activity duration has an incorrect format, we expect up to 4 numeric digits and 1 unit ('d' or 'h')")
210+
})
196211
}
197212

198213
func TestProjectsUpdate(t *testing.T) {
@@ -284,6 +299,21 @@ func TestProjectsUpdate(t *testing.T) {
284299
assert.Nil(t, w)
285300
assert.EqualError(t, err, ErrInvalidProjectID.Error())
286301
})
302+
303+
t.Run("without a valid projects auto destroy activity duration", func(t *testing.T) {
304+
skipUnlessBeta(t)
305+
306+
newSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)
307+
308+
kBefore, kTestCleanup := createProject(t, client, orgTest)
309+
defer kTestCleanup()
310+
311+
w, err := client.Projects.Update(ctx, kBefore.ID, ProjectUpdateOptions{
312+
AutoDestroyActivityDuration: jsonapi.NewNullableAttrWithValue("bar"),
313+
})
314+
assert.Nil(t, w)
315+
assert.Contains(t, err.Error(), "invalid attribute\n\nAuto destroy activity duration has an incorrect format, we expect up to 4 numeric digits and 1 unit ('d' or 'h')")
316+
})
287317
}
288318

289319
func TestProjectsAddTagBindings(t *testing.T) {
@@ -378,3 +408,32 @@ func TestProjectsDelete(t *testing.T) {
378408
assert.EqualError(t, err, ErrInvalidProjectID.Error())
379409
})
380410
}
411+
412+
func TestProjectsAutoDestroy(t *testing.T) {
413+
skipUnlessBeta(t)
414+
client := testClient(t)
415+
ctx := context.Background()
416+
417+
orgTest, orgTestCleanup := createOrganization(t, client)
418+
defer orgTestCleanup()
419+
420+
newSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)
421+
422+
t.Run("when creating workspace in project with autodestroy", func(t *testing.T) {
423+
options := ProjectCreateOptions{
424+
Name: "foo",
425+
Description: String("qux"),
426+
AutoDestroyActivityDuration: jsonapi.NewNullableAttrWithValue("3d"),
427+
}
428+
429+
p, err := client.Projects.Create(ctx, orgTest.Name, options)
430+
require.NoError(t, err)
431+
432+
w, _ := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{
433+
Name: String(randomString(t)),
434+
Project: p,
435+
})
436+
437+
assert.Equal(t, p.AutoDestroyActivityDuration, w.AutoDestroyActivityDuration)
438+
})
439+
}

workspace.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ type Workspace struct {
186186
ExecutionMode string `jsonapi:"attr,execution-mode"`
187187
FileTriggersEnabled bool `jsonapi:"attr,file-triggers-enabled"`
188188
GlobalRemoteState bool `jsonapi:"attr,global-remote-state"`
189+
InheritsProjectAutoDestroy bool `jsonapi:"attr,inherits-project-auto-destroy"`
189190
Locked bool `jsonapi:"attr,locked"`
190191
MigrationEnvironment string `jsonapi:"attr,migration-environment"`
191192
Name string `jsonapi:"attr,name"`
@@ -393,6 +394,9 @@ type WorkspaceCreateOptions struct {
393394
// should roughly match a Go duration string limited to days and hours, e.g. "24h" or "1d".
394395
AutoDestroyActivityDuration jsonapi.NullableAttr[string] `jsonapi:"attr,auto-destroy-activity-duration,omitempty"`
395396

397+
// Optional: Whether the workspace inherits auto destroy settings from the project
398+
InheritsProjectAutoDestroy *bool `jsonapi:"attr,inherits-project-auto-destroy,omitempty"`
399+
396400
// Optional: A description for the workspace.
397401
Description *string `jsonapi:"attr,description,omitempty"`
398402

@@ -550,6 +554,9 @@ type WorkspaceUpdateOptions struct {
550554
// should roughly match a Go duration string limited to days and hours, e.g. "24h" or "1d".
551555
AutoDestroyActivityDuration jsonapi.NullableAttr[string] `jsonapi:"attr,auto-destroy-activity-duration,omitempty"`
552556

557+
// Optional: Whether the workspace inherits auto destroy settings from the project
558+
InheritsProjectAutoDestroy *bool `jsonapi:"attr,inherits-project-auto-destroy,omitempty"`
559+
553560
// Optional: A new name for the workspace, which can only include letters, numbers, -,
554561
// and _. This will be used as an identifier and must be unique in the
555562
// organization. Warning: Changing a workspace's name changes its URL in the

workspace_integration_test.go

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2961,7 +2961,7 @@ func TestWorkspacesAutoDestroy(t *testing.T) {
29612961
orgTest, orgTestCleanup := createOrganization(t, client)
29622962
t.Cleanup(orgTestCleanup)
29632963

2964-
upgradeOrganizationSubscription(t, client, orgTest)
2964+
newSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)
29652965

29662966
autoDestroyAt := NullableTime(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC))
29672967
wTest, wCleanup := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{
@@ -2999,31 +2999,39 @@ func TestWorkspacesAutoDestroy(t *testing.T) {
29992999
}
30003000

30013001
func TestWorkspacesAutoDestroyDuration(t *testing.T) {
3002+
skipUnlessBeta(t)
3003+
30023004
client := testClient(t)
30033005
ctx := context.Background()
30043006

30053007
orgTest, orgTestCleanup := createOrganization(t, client)
30063008
t.Cleanup(orgTestCleanup)
30073009

3008-
upgradeOrganizationSubscription(t, client, orgTest)
3010+
newSubscriptionUpdater(orgTest).WithBusinessPlan().Update(t)
30093011

3010-
duration := jsonapi.NewNullableAttrWithValue("14d")
3011-
nilDuration := jsonapi.NewNullNullableAttr[string]()
3012-
nilAutoDestroy := jsonapi.NewNullNullableAttr[time.Time]()
3013-
wTest, wCleanup := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{
3014-
Name: String(randomString(t)),
3015-
AutoDestroyActivityDuration: duration,
3016-
})
3017-
t.Cleanup(wCleanup)
3012+
t.Run("when creating a new workspace with standalone auto destroy settings", func(t *testing.T) {
3013+
duration := jsonapi.NewNullableAttrWithValue("14d")
3014+
nilDuration := jsonapi.NewNullNullableAttr[string]()
3015+
nilAutoDestroy := jsonapi.NewNullNullableAttr[time.Time]()
3016+
wTest, wCleanup := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{
3017+
Name: String(randomString(t)),
3018+
AutoDestroyActivityDuration: duration,
3019+
InheritsProjectAutoDestroy: Bool(false),
3020+
})
3021+
t.Cleanup(wCleanup)
30183022

3019-
require.Equal(t, duration, wTest.AutoDestroyActivityDuration)
3020-
require.NotEqual(t, nilAutoDestroy, wTest.AutoDestroyAt)
3023+
require.Equal(t, duration, wTest.AutoDestroyActivityDuration)
3024+
require.NotEqual(t, nilAutoDestroy, wTest.AutoDestroyAt)
3025+
require.Equal(t, wTest.InheritsProjectAutoDestroy, false)
30213026

3022-
w, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, WorkspaceUpdateOptions{
3023-
AutoDestroyActivityDuration: nilDuration,
3024-
})
3027+
w, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, WorkspaceUpdateOptions{
3028+
AutoDestroyActivityDuration: nilDuration,
3029+
InheritsProjectAutoDestroy: Bool(false),
3030+
})
30253031

3026-
require.NoError(t, err)
3027-
require.False(t, w.AutoDestroyActivityDuration.IsSpecified())
3028-
require.False(t, w.AutoDestroyAt.IsSpecified())
3032+
require.NoError(t, err)
3033+
require.False(t, w.AutoDestroyActivityDuration.IsSpecified())
3034+
require.False(t, w.AutoDestroyAt.IsSpecified())
3035+
require.Equal(t, wTest.InheritsProjectAutoDestroy, false)
3036+
})
30293037
}

0 commit comments

Comments
 (0)