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

[TF-9914] Add auto destroy attribute to workspaces #786

Merged
merged 13 commits into from
Jan 31, 2024
Merged
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]
ctrombley marked this conversation as resolved.
Show resolved Hide resolved

- 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)
}
Loading