Skip to content

Commit

Permalink
Merge pull request #786 from hashicorp/notchairmk/add-auto-destroy
Browse files Browse the repository at this point in the history
[TF-9914] Add auto destroy attribute to workspaces
  • Loading branch information
ctrombley authored Jan 31, 2024
2 parents f93d264 + bf4c87c commit c90619c
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 51 deletions.
10 changes: 1 addition & 9 deletions .github/actions/lint-go-tfe/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,7 @@ runs:

- name: Install mockgen
shell: bash
run: |
set -eux -o pipefail
MOCKGEN_VERSION=$(curl -Ls -o /dev/null -w %{url_effective} https://github.com/golang/mock/releases/latest | awk -F'/v' '{printf$2}')
MOCKGEN_ZIP=mock_${MOCKGEN_VERSION}_linux_amd64.tar.gz
curl -OL https://github.com/golang/mock/releases/download/v$MOCKGEN_VERSION/$MOCKGEN_ZIP
sudo tar xvzf $MOCKGEN_ZIP --strip-components 1 -C /usr/local
sudo chmod +x /usr/local/mockgen
rm -f $MOCKGEN_ZIP
echo /usr/local/ >> $GITHUB_PATH
run: go install github.com/golang/mock/[email protected]

- name: Get dependencies
shell: bash
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Unreleased

## Enhancements
* Updates `Workspaces` to include an `AutoDestroyAt` attribute on create and update by @notchairmk [#786](https://github.com/hashicorp/go-tfe/pull/786)

# v1.43.0

## Features
Expand Down
13 changes: 12 additions & 1 deletion examples/workspaces/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package main
import (
"context"
"log"
"time"

tfe "github.com/hashicorp/go-tfe"
)
Expand All @@ -26,7 +27,8 @@ func main() {

// Create a new workspace
w, err := client.Workspaces.Create(ctx, "org-name", tfe.WorkspaceCreateOptions{
Name: tfe.String("my-app-tst"),
Name: tfe.String("my-app-tst"),
AutoDestroyAt: tfe.NullableTime(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)),
})
if err != nil {
log.Fatal(err)
Expand All @@ -37,6 +39,15 @@ func main() {
AutoApply: tfe.Bool(false),
TerraformVersion: tfe.String("0.11.1"),
WorkingDirectory: tfe.String("my-app/infra"),
AutoDestroyAt: tfe.NullableTime(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)),
})
if err != nil {
log.Fatal(err)
}

// Disable auto destroy
w, err = client.Workspaces.Update(ctx, "org-name", w.Name, tfe.WorkspaceUpdateOptions{
AutoDestroyAt: tfe.NullTime(),
})
if err != nil {
log.Fatal(err)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/hashicorp/go-slug v0.13.4
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/jsonapi v1.3.0
github.com/hashicorp/jsonapi v1.3.1
github.com/stretchr/testify v1.8.4
golang.org/x/sync v0.6.0
golang.org/x/time v0.5.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/jsonapi v1.3.0 h1:4d7xWWdwJGm5bbP/sbDsqe6Kn6LERVr7sLEQxa6NR+w=
github.com/hashicorp/jsonapi v1.3.0/go.mod h1:kWfdn49yCjQvbpnvY1dxxAuAFzISwrrMDQOcu6NsFoM=
github.com/hashicorp/jsonapi v1.3.1 h1:GtPvnmcWgYwCuDGvYT5VZBHcUyFdq9lSyCzDjn1DdPo=
github.com/hashicorp/jsonapi v1.3.1/go.mod h1:kWfdn49yCjQvbpnvY1dxxAuAFzISwrrMDQOcu6NsFoM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
Expand Down
22 changes: 22 additions & 0 deletions type_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@

package tfe

import (
"time"

"github.com/hashicorp/jsonapi"
)

// Access returns a pointer to the given team access type.
func Access(v AccessType) *AccessType {
return &v
Expand Down Expand Up @@ -117,3 +123,19 @@ func SMTPAuthValue(v SMTPAuthType) *SMTPAuthType {
func String(v string) *string {
return &v
}

func NullableBool(v bool) jsonapi.NullableAttr[bool] {
return jsonapi.NewNullableAttrWithValue[bool](v)
}

func NullBool() jsonapi.NullableAttr[bool] {
return jsonapi.NewNullNullableAttr[bool]()
}

func NullableTime(v time.Time) jsonapi.NullableAttr[time.Time] {
return jsonapi.NewNullableAttrWithValue[time.Time](v)
}

func NullTime() jsonapi.NullableAttr[time.Time] {
return jsonapi.NewNullNullableAttr[time.Time]()
}
83 changes: 46 additions & 37 deletions workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"net/url"
"strings"
"time"

"github.com/hashicorp/jsonapi"
)

// Compile-time proof of interface implementation.
Expand Down Expand Up @@ -137,43 +139,44 @@ type LockedByChoice struct {

// Workspace represents a Terraform Enterprise workspace.
type Workspace struct {
ID string `jsonapi:"primary,workspaces"`
Actions *WorkspaceActions `jsonapi:"attr,actions"`
AllowDestroyPlan bool `jsonapi:"attr,allow-destroy-plan"`
AssessmentsEnabled bool `jsonapi:"attr,assessments-enabled"`
AutoApply bool `jsonapi:"attr,auto-apply"`
AutoApplyRunTrigger bool `jsonapi:"attr,auto-apply-run-trigger"`
CanQueueDestroyPlan bool `jsonapi:"attr,can-queue-destroy-plan"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
Description string `jsonapi:"attr,description"`
Environment string `jsonapi:"attr,environment"`
ExecutionMode string `jsonapi:"attr,execution-mode"`
FileTriggersEnabled bool `jsonapi:"attr,file-triggers-enabled"`
GlobalRemoteState bool `jsonapi:"attr,global-remote-state"`
Locked bool `jsonapi:"attr,locked"`
MigrationEnvironment string `jsonapi:"attr,migration-environment"`
Name string `jsonapi:"attr,name"`
Operations bool `jsonapi:"attr,operations"`
Permissions *WorkspacePermissions `jsonapi:"attr,permissions"`
QueueAllRuns bool `jsonapi:"attr,queue-all-runs"`
SpeculativeEnabled bool `jsonapi:"attr,speculative-enabled"`
SourceName string `jsonapi:"attr,source-name"`
SourceURL string `jsonapi:"attr,source-url"`
StructuredRunOutputEnabled bool `jsonapi:"attr,structured-run-output-enabled"`
TerraformVersion string `jsonapi:"attr,terraform-version"`
TriggerPrefixes []string `jsonapi:"attr,trigger-prefixes"`
TriggerPatterns []string `jsonapi:"attr,trigger-patterns"`
VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"`
WorkingDirectory string `jsonapi:"attr,working-directory"`
UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"`
ResourceCount int `jsonapi:"attr,resource-count"`
ApplyDurationAverage time.Duration `jsonapi:"attr,apply-duration-average"`
PlanDurationAverage time.Duration `jsonapi:"attr,plan-duration-average"`
PolicyCheckFailures int `jsonapi:"attr,policy-check-failures"`
RunFailures int `jsonapi:"attr,run-failures"`
RunsCount int `jsonapi:"attr,workspace-kpis-runs-count"`
TagNames []string `jsonapi:"attr,tag-names"`
SettingOverwrites *WorkspaceSettingOverwrites `jsonapi:"attr,setting-overwrites"`
ID string `jsonapi:"primary,workspaces"`
Actions *WorkspaceActions `jsonapi:"attr,actions"`
AllowDestroyPlan bool `jsonapi:"attr,allow-destroy-plan"`
AssessmentsEnabled bool `jsonapi:"attr,assessments-enabled"`
AutoApply bool `jsonapi:"attr,auto-apply"`
AutoApplyRunTrigger bool `jsonapi:"attr,auto-apply-run-trigger"`
AutoDestroyAt jsonapi.NullableAttr[time.Time] `jsonapi:"attr,auto-destroy-at,iso8601,omitempty"`
CanQueueDestroyPlan bool `jsonapi:"attr,can-queue-destroy-plan"`
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
Description string `jsonapi:"attr,description"`
Environment string `jsonapi:"attr,environment"`
ExecutionMode string `jsonapi:"attr,execution-mode"`
FileTriggersEnabled bool `jsonapi:"attr,file-triggers-enabled"`
GlobalRemoteState bool `jsonapi:"attr,global-remote-state"`
Locked bool `jsonapi:"attr,locked"`
MigrationEnvironment string `jsonapi:"attr,migration-environment"`
Name string `jsonapi:"attr,name"`
Operations bool `jsonapi:"attr,operations"`
Permissions *WorkspacePermissions `jsonapi:"attr,permissions"`
QueueAllRuns bool `jsonapi:"attr,queue-all-runs"`
SpeculativeEnabled bool `jsonapi:"attr,speculative-enabled"`
SourceName string `jsonapi:"attr,source-name"`
SourceURL string `jsonapi:"attr,source-url"`
StructuredRunOutputEnabled bool `jsonapi:"attr,structured-run-output-enabled"`
TerraformVersion string `jsonapi:"attr,terraform-version"`
TriggerPrefixes []string `jsonapi:"attr,trigger-prefixes"`
TriggerPatterns []string `jsonapi:"attr,trigger-patterns"`
VCSRepo *VCSRepo `jsonapi:"attr,vcs-repo"`
WorkingDirectory string `jsonapi:"attr,working-directory"`
UpdatedAt time.Time `jsonapi:"attr,updated-at,iso8601"`
ResourceCount int `jsonapi:"attr,resource-count"`
ApplyDurationAverage time.Duration `jsonapi:"attr,apply-duration-average"`
PlanDurationAverage time.Duration `jsonapi:"attr,plan-duration-average"`
PolicyCheckFailures int `jsonapi:"attr,policy-check-failures"`
RunFailures int `jsonapi:"attr,run-failures"`
RunsCount int `jsonapi:"attr,workspace-kpis-runs-count"`
TagNames []string `jsonapi:"attr,tag-names"`
SettingOverwrites *WorkspaceSettingOverwrites `jsonapi:"attr,setting-overwrites"`

// Relations
AgentPool *AgentPool `jsonapi:"relation,agent-pool"`
Expand Down Expand Up @@ -334,6 +337,9 @@ type WorkspaceCreateOptions struct {
// from another workspace.
AutoApplyRunTrigger *bool `jsonapi:"attr,auto-apply-run-trigger,omitempty"`

// Optional: The time after which an automatic destroy run will be queued
AutoDestroyAt jsonapi.NullableAttr[time.Time] `jsonapi:"attr,auto-destroy-at,iso8601,omitempty"`

// Optional: A description for the workspace.
Description *string `jsonapi:"attr,description,omitempty"`

Expand Down Expand Up @@ -481,6 +487,9 @@ type WorkspaceUpdateOptions struct {
// from another workspace.
AutoApplyRunTrigger *bool `jsonapi:"attr,auto-apply-run-trigger,omitempty"`

// Optional: The time after which an automatic destroy run will be queued
AutoDestroyAt jsonapi.NullableAttr[time.Time] `jsonapi:"attr,auto-destroy-at,iso8601,omitempty"`

// Optional: A new name for the workspace, which can only include letters, numbers, -,
// and _. This will be used as an identifier and must be unique in the
// organization. Warning: Changing a workspace's name changes its URL in the
Expand Down
50 changes: 49 additions & 1 deletion workspace_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,8 +537,9 @@ func TestWorkspacesCreate(t *testing.T) {
t.Run("with valid options", func(t *testing.T) {
options := WorkspaceCreateOptions{
Name: String(fmt.Sprintf("foo-%s", randomString(t))),
AllowDestroyPlan: Bool(false),
AllowDestroyPlan: Bool(true),
AutoApply: Bool(true),
AutoDestroyAt: NullableTime(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)),
Description: String("qux"),
AssessmentsEnabled: Bool(false),
FileTriggersEnabled: Bool(true),
Expand Down Expand Up @@ -577,6 +578,7 @@ func TestWorkspacesCreate(t *testing.T) {
assert.Equal(t, *options.Description, item.Description)
assert.Equal(t, *options.AllowDestroyPlan, item.AllowDestroyPlan)
assert.Equal(t, *options.AutoApply, item.AutoApply)
assert.Equal(t, options.AutoDestroyAt, item.AutoDestroyAt)
assert.Equal(t, *options.AssessmentsEnabled, item.AssessmentsEnabled)
assert.Equal(t, *options.FileTriggersEnabled, item.FileTriggersEnabled)
assert.Equal(t, *options.Operations, item.Operations)
Expand Down Expand Up @@ -1124,6 +1126,7 @@ func TestWorkspacesUpdate(t *testing.T) {
Name: String(randomString(t)),
AllowDestroyPlan: Bool(true),
AutoApply: Bool(false),
AutoDestroyAt: NullableTime(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)),
FileTriggersEnabled: Bool(true),
Operations: Bool(false),
QueueAllRuns: Bool(false),
Expand All @@ -1149,6 +1152,7 @@ func TestWorkspacesUpdate(t *testing.T) {
assert.Equal(t, *options.Name, item.Name)
assert.Equal(t, *options.AllowDestroyPlan, item.AllowDestroyPlan)
assert.Equal(t, *options.AutoApply, item.AutoApply)
assert.Equal(t, options.AutoDestroyAt, item.AutoDestroyAt)
assert.Equal(t, *options.FileTriggersEnabled, item.FileTriggersEnabled)
assert.Equal(t, *options.Description, item.Description)
assert.Equal(t, *options.Operations, item.Operations)
Expand Down Expand Up @@ -2644,3 +2648,47 @@ func TestWorkspace_DataRetentionPolicy(t *testing.T) {
require.Nil(t, dataRetentionPolicy)
})
}

func TestWorkspacesAutoDestroy(t *testing.T) {
client := testClient(t)
ctx := context.Background()

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

upgradeOrganizationSubscription(t, client, orgTest)

autoDestroyAt := NullableTime(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC))
wTest, wCleanup := createWorkspaceWithOptions(t, client, orgTest, WorkspaceCreateOptions{
Name: String(randomString(t)),
AutoDestroyAt: autoDestroyAt,
})
t.Cleanup(wCleanup)

require.Equal(t, wTest.AutoDestroyAt, autoDestroyAt)

// respect default omitempty
w, err := client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, WorkspaceUpdateOptions{
AutoDestroyAt: nil,
})

require.NoError(t, err)
require.NotNil(t, w.AutoDestroyAt)

// explicitly update the value of auto_destroy_at
w, err = client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, WorkspaceUpdateOptions{
AutoDestroyAt: NullableTime(time.Date(2025, 1, 2, 0, 0, 0, 0, time.UTC)),
})

require.NoError(t, err)
require.NotNil(t, w.AutoDestroyAt)
require.NotEqual(t, w.AutoDestroyAt, autoDestroyAt)

// disable auto destroy
w, err = client.Workspaces.Update(ctx, orgTest.Name, wTest.Name, WorkspaceUpdateOptions{
AutoDestroyAt: NullTime(),
})

require.NoError(t, err)
require.Nil(t, w.AutoDestroyAt)
}

0 comments on commit c90619c

Please sign in to comment.