diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d833f611..c87f07184 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Enhancements * Adds `RunPreApplyCompleted` run status by @uk1288 [#727](https://github.com/hashicorp/go-tfe/pull/727) +* Added BETA support for saved plan runs, by @nfagerlund [#724](https://github.com/hashicorp/go-tfe/pull/724) + * New `SavePlan` fields in `Run` and `RunCreateOptions` + * New `RunPlannedAndSaved` `RunStatus` value + * New `PlannedAndSavedAt` field in `RunStatusTimestamps` + * New `RunOperationSavePlan` constant for run list filters # v1.28.0 diff --git a/run.go b/run.go index 142031192..137f6cc7a 100644 --- a/run.go +++ b/run.go @@ -70,6 +70,7 @@ const ( RunPending RunStatus = "pending" RunPlanned RunStatus = "planned" RunPlannedAndFinished RunStatus = "planned_and_finished" + RunPlannedAndSaved RunStatus = "planned_and_saved" // Note: This status is in BETA. RunPlanning RunStatus = "planning" RunPlanQueued RunStatus = "plan_queued" RunPolicyChecked RunStatus = "policy_checked" @@ -107,6 +108,8 @@ const ( RunOperationRefreshOnly RunOperation = "refresh_only" RunOperationDestroy RunOperation = "destroy" RunOperationEmptyApply RunOperation = "empty_apply" + // **Note: This operation type is still in BETA and subject to change.** + RunOperationSavePlan RunOperation = "save_plan" ) // RunList represents a list of runs. @@ -117,28 +120,30 @@ type RunList struct { // Run represents a Terraform Enterprise run. type Run struct { - ID string `jsonapi:"primary,runs"` - Actions *RunActions `jsonapi:"attr,actions"` - AutoApply bool `jsonapi:"attr,auto-apply,omitempty"` - AllowConfigGeneration *bool `jsonapi:"attr,allow-config-generation,omitempty"` - AllowEmptyApply bool `jsonapi:"attr,allow-empty-apply"` - CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` - ForceCancelAvailableAt time.Time `jsonapi:"attr,force-cancel-available-at,iso8601"` - HasChanges bool `jsonapi:"attr,has-changes"` - IsDestroy bool `jsonapi:"attr,is-destroy"` - Message string `jsonapi:"attr,message"` - Permissions *RunPermissions `jsonapi:"attr,permissions"` - PositionInQueue int `jsonapi:"attr,position-in-queue"` - PlanOnly bool `jsonapi:"attr,plan-only"` - Refresh bool `jsonapi:"attr,refresh"` - RefreshOnly bool `jsonapi:"attr,refresh-only"` - ReplaceAddrs []string `jsonapi:"attr,replace-addrs,omitempty"` - Source RunSource `jsonapi:"attr,source"` - Status RunStatus `jsonapi:"attr,status"` - StatusTimestamps *RunStatusTimestamps `jsonapi:"attr,status-timestamps"` - TargetAddrs []string `jsonapi:"attr,target-addrs,omitempty"` - TerraformVersion string `jsonapi:"attr,terraform-version"` - Variables []*RunVariableAttr `jsonapi:"attr,variables"` + ID string `jsonapi:"primary,runs"` + Actions *RunActions `jsonapi:"attr,actions"` + AutoApply bool `jsonapi:"attr,auto-apply,omitempty"` + AllowConfigGeneration *bool `jsonapi:"attr,allow-config-generation,omitempty"` + AllowEmptyApply bool `jsonapi:"attr,allow-empty-apply"` + CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"` + ForceCancelAvailableAt time.Time `jsonapi:"attr,force-cancel-available-at,iso8601"` + HasChanges bool `jsonapi:"attr,has-changes"` + IsDestroy bool `jsonapi:"attr,is-destroy"` + Message string `jsonapi:"attr,message"` + Permissions *RunPermissions `jsonapi:"attr,permissions"` + PositionInQueue int `jsonapi:"attr,position-in-queue"` + PlanOnly bool `jsonapi:"attr,plan-only"` + Refresh bool `jsonapi:"attr,refresh"` + RefreshOnly bool `jsonapi:"attr,refresh-only"` + ReplaceAddrs []string `jsonapi:"attr,replace-addrs,omitempty"` + // **Note: This field is still in BETA and subject to change.** + SavePlan bool `jsonapi:"attr,save-plan,omitempty"` + Source RunSource `jsonapi:"attr,source"` + Status RunStatus `jsonapi:"attr,status"` + StatusTimestamps *RunStatusTimestamps `jsonapi:"attr,status-timestamps"` + TargetAddrs []string `jsonapi:"attr,target-addrs,omitempty"` + TerraformVersion string `jsonapi:"attr,terraform-version"` + Variables []*RunVariableAttr `jsonapi:"attr,variables"` // Relations Apply *Apply `jsonapi:"relation,apply"` @@ -184,17 +189,19 @@ type RunStatusTimestamps struct { FetchingAt time.Time `jsonapi:"attr,fetching-at,rfc3339"` ForceCanceledAt time.Time `jsonapi:"attr,force-canceled-at,rfc3339"` PlannedAndFinishedAt time.Time `jsonapi:"attr,planned-and-finished-at,rfc3339"` - PlannedAt time.Time `jsonapi:"attr,planned-at,rfc3339"` - PlanningAt time.Time `jsonapi:"attr,planning-at,rfc3339"` - PlanQueueableAt time.Time `jsonapi:"attr,plan-queueable-at,rfc3339"` - PlanQueuedAt time.Time `jsonapi:"attr,plan-queued-at,rfc3339"` - PolicyCheckedAt time.Time `jsonapi:"attr,policy-checked-at,rfc3339"` - PolicySoftFailedAt time.Time `jsonapi:"attr,policy-soft-failed-at,rfc3339"` - PostPlanCompletedAt time.Time `jsonapi:"attr,post-plan-completed-at,rfc3339"` - PostPlanRunningAt time.Time `jsonapi:"attr,post-plan-running-at,rfc3339"` - PrePlanCompletedAt time.Time `jsonapi:"attr,pre-plan-completed-at,rfc3339"` - PrePlanRunningAt time.Time `jsonapi:"attr,pre-plan-running-at,rfc3339"` - QueuingAt time.Time `jsonapi:"attr,queuing-at,rfc3339"` + // **Note: This field is still in BETA and subject to change.** + PlannedAndSavedAt time.Time `jsonapi:"attr,planned-and-saved-at,rfc3339"` + PlannedAt time.Time `jsonapi:"attr,planned-at,rfc3339"` + PlanningAt time.Time `jsonapi:"attr,planning-at,rfc3339"` + PlanQueueableAt time.Time `jsonapi:"attr,plan-queueable-at,rfc3339"` + PlanQueuedAt time.Time `jsonapi:"attr,plan-queued-at,rfc3339"` + PolicyCheckedAt time.Time `jsonapi:"attr,policy-checked-at,rfc3339"` + PolicySoftFailedAt time.Time `jsonapi:"attr,policy-soft-failed-at,rfc3339"` + PostPlanCompletedAt time.Time `jsonapi:"attr,post-plan-completed-at,rfc3339"` + PostPlanRunningAt time.Time `jsonapi:"attr,post-plan-running-at,rfc3339"` + PrePlanCompletedAt time.Time `jsonapi:"attr,pre-plan-completed-at,rfc3339"` + PrePlanRunningAt time.Time `jsonapi:"attr,pre-plan-running-at,rfc3339"` + QueuingAt time.Time `jsonapi:"attr,queuing-at,rfc3339"` } // RunIncludeOpt represents the available options for include query params. @@ -290,6 +297,12 @@ type RunCreateOptions struct { // and refresh the state only RefreshOnly *bool `jsonapi:"attr,refresh-only,omitempty"` + // SavePlan determines whether this should be a saved-plan run. Saved-plan + // runs perform their plan and checks immediately, but won't lock the + // workspace and become its current run until they are confirmed for apply. + // **Note: This field is still in BETA and subject to change.** + SavePlan *bool `jsonapi:"attr,save-plan,omitempty"` + // Specifies the message to be associated with this run. Message *string `jsonapi:"attr,message,omitempty"` diff --git a/run_integration_test.go b/run_integration_test.go index b4a48005c..64b2692f7 100644 --- a/run_integration_test.go +++ b/run_integration_test.go @@ -149,12 +149,31 @@ func TestRunsListQueryParams(t *testing.T) { }, } + betaTestCases := []testCase{ + { + description: "with operation of save_plan parameter", + options: &RunListOptions{Operation: string(RunOperationSavePlan), Include: []RunIncludeOpt{RunWorkspace}}, + assertion: func(tc testCase, rl *RunList, err error) { + require.NoError(t, err) + assert.Equal(t, 0, len(rl.Items)) + }, + }, + } + for _, testCase := range testCases { t.Run(testCase.description, func(t *testing.T) { runs, err := client.Runs.List(ctx, workspaceTest.ID, testCase.options) testCase.assertion(testCase, runs, err) }) } + + for _, testCase := range betaTestCases { + t.Run(testCase.description, func(t *testing.T) { + skipUnlessBeta(t) + runs, err := client.Runs.List(ctx, workspaceTest.ID, testCase.options) + testCase.assertion(testCase, runs, err) + }) + } } func TestRunsCreate(t *testing.T) { @@ -203,6 +222,18 @@ func TestRunsCreate(t *testing.T) { assert.Equal(t, true, r.AllowEmptyApply) }) + t.Run("with save-plan", func(t *testing.T) { + skipUnlessBeta(t) + options := RunCreateOptions{ + Workspace: wTest, + SavePlan: Bool(true), + } + + r, err := client.Runs.Create(ctx, options) + require.NoError(t, err) + assert.Equal(t, true, r.SavePlan) + }) + t.Run("with terraform version and plan only", func(t *testing.T) { options := RunCreateOptions{ Workspace: wTest,