Skip to content

Commit

Permalink
command+backend: generalized "plan mode"
Browse files Browse the repository at this point in the history
So far we've only had "normal mode" and "destroy mode", where the latter
is activated either by "terraform plan -destroy" or "terraform destroy".

In preparation for introducing a third mode "refresh only" this
generalizes how we handle modes so we can potentially deal with an
arbitrary number of modes, although for now we only intend to have three.

Mostly this is just a different implementation of the same old behavior,
but there is one small user-visible difference here: the "terraform apply"
command now accepts a -destroy option, mirroring the option of the same
name on "terraform plan", which in turn makes "terraform destroy"
effectively a shorthand for "terraform apply -destroy".

This is intended to make us consistent that "terraform apply" without a
plan file argument accepts all of the same plan-customization options that
"terraform plan" does, which will in turn avoid us having to add a new
alias of "terraform plan" for each new plan mode we might add. The -help
output is changed in that vein here, although we'll wait for subsequent
commit to make a similar change to the website documentation just so we
can deal with the "refresh only mode" docs at the same time.
  • Loading branch information
apparentlymart committed Apr 22, 2021
1 parent 941c080 commit 06d24ac
Show file tree
Hide file tree
Showing 21 changed files with 299 additions and 129 deletions.
2 changes: 1 addition & 1 deletion backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ type Operation struct {

// The options below are more self-explanatory and affect the runtime
// behavior of the operation.
PlanMode plans.Mode
AutoApprove bool
Destroy bool
Parallelism int
Targets []addrs.Targetable
Variables map[string]UnparsedVariableValue
Expand Down
7 changes: 4 additions & 3 deletions backend/local/backend_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statefile"
"github.com/hashicorp/terraform/states/statemgr"
Expand All @@ -26,7 +27,7 @@ func (b *Local) opApply(

// If we have a nil module at this point, then set it to an empty tree
// to avoid any potential crashes.
if op.PlanFile == nil && !op.Destroy && !op.HasConfig() {
if op.PlanFile == nil && op.PlanMode != plans.DestroyMode && !op.HasConfig() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No configuration files",
Expand Down Expand Up @@ -76,7 +77,7 @@ func (b *Local) opApply(
mustConfirm := hasUI && !op.AutoApprove && !trivialPlan
if mustConfirm {
var desc, query string
if op.Destroy {
if op.PlanMode == plans.DestroyMode {
if op.Workspace != "default" {
query = "Do you really want to destroy all resources in workspace \"" + op.Workspace + "\"?"
} else {
Expand Down Expand Up @@ -116,7 +117,7 @@ func (b *Local) opApply(
return
}
if v != "yes" {
op.View.Cancelled(op.Destroy)
op.View.Cancelled(op.PlanMode)
runningOp.Result = backend.OperationFailure
return
}
Expand Down
3 changes: 2 additions & 1 deletion backend/local/backend_apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/internal/initwd"
"github.com/hashicorp/terraform/internal/terminal"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
Expand Down Expand Up @@ -115,7 +116,7 @@ func TestLocal_applyEmptyDirDestroy(t *testing.T) {

op, configCleanup, done := testOperationApply(t, "./testdata/empty")
defer configCleanup()
op.Destroy = true
op.PlanMode = plans.DestroyMode

run, err := b.Operation(context.Background(), op)
if err != nil {
Expand Down
8 changes: 1 addition & 7 deletions backend/local/backend_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/configs/configload"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform"
Expand Down Expand Up @@ -66,12 +65,7 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, *configload.
}

// Copy set options from the operation
switch {
case op.Destroy:
opts.PlanMode = plans.DestroyMode
default:
opts.PlanMode = plans.NormalMode
}
opts.PlanMode = op.PlanMode
opts.Targets = op.Targets
opts.UIInput = op.UIIn
opts.Hooks = op.Hooks
Expand Down
2 changes: 1 addition & 1 deletion backend/local/backend_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (b *Local) opPlan(
}

// Local planning requires a config, unless we're planning to destroy.
if !op.Destroy && !op.HasConfig() {
if op.PlanMode != plans.DestroyMode && !op.HasConfig() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No configuration files",
Expand Down
4 changes: 2 additions & 2 deletions backend/local/backend_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ func TestLocal_planDestroy(t *testing.T) {

op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
op.Destroy = true
op.PlanMode = plans.DestroyMode
op.PlanRefresh = true
op.PlanOutPath = planPath
cfg := cty.ObjectVal(map[string]cty.Value{
Expand Down Expand Up @@ -598,7 +598,7 @@ func TestLocal_planDestroy_withDataSources(t *testing.T) {

op, configCleanup, done := testOperationPlan(t, "./testdata/destroy-with-ds")
defer configCleanup()
op.Destroy = true
op.PlanMode = plans.DestroyMode
op.PlanRefresh = true
op.PlanOutPath = planPath
cfg := cty.ObjectVal(map[string]cty.Value{
Expand Down
11 changes: 7 additions & 4 deletions backend/remote/backend_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
tfe "github.com/hashicorp/go-tfe"
version "github.com/hashicorp/go-version"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
)
Expand Down Expand Up @@ -84,7 +85,7 @@ func (b *Remote) opApply(stopCtx, cancelCtx context.Context, op *backend.Operati
))
}

if !op.HasConfig() && !op.Destroy {
if !op.HasConfig() && op.PlanMode != plans.DestroyMode {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No configuration files found",
Expand Down Expand Up @@ -152,10 +153,12 @@ func (b *Remote) opApply(stopCtx, cancelCtx context.Context, op *backend.Operati
if r.Actions.IsDiscardable {
err = b.client.Runs.Discard(stopCtx, r.ID, tfe.RunDiscardOptions{})
if err != nil {
if op.Destroy {
switch op.PlanMode {
case plans.DestroyMode:
return r, generalError("Failed to discard destroy", err)
default:
return r, generalError("Failed to discard apply", err)
}
return r, generalError("Failed to discard apply", err)
}
}
diags = diags.Append(tfdiags.Sourceless(
Expand All @@ -176,7 +179,7 @@ func (b *Remote) opApply(stopCtx, cancelCtx context.Context, op *backend.Operati
if mustConfirm {
opts := &terraform.InputOpts{Id: "approve"}

if op.Destroy {
if op.PlanMode == plans.DestroyMode {
opts.Query = "\nDo you really want to destroy all resources in workspace \"" + op.Workspace + "\"?"
opts.Description = "Terraform will destroy all your managed infrastructure, as shown above.\n" +
"There is no undo. Only 'yes' will be accepted to confirm."
Expand Down
5 changes: 3 additions & 2 deletions backend/remote/backend_apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/internal/initwd"
"github.com/hashicorp/terraform/internal/terminal"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform"
Expand Down Expand Up @@ -968,7 +969,7 @@ func TestRemote_applyDestroy(t *testing.T) {
"approve": "yes",
})

op.Destroy = true
op.PlanMode = plans.DestroyMode
op.UIIn = input
op.UIOut = b.CLI
op.Workspace = backend.DefaultStateName
Expand Down Expand Up @@ -1014,7 +1015,7 @@ func TestRemote_applyDestroyNoConfig(t *testing.T) {
defer configCleanup()
defer done(t)

op.Destroy = true
op.PlanMode = plans.DestroyMode
op.UIIn = input
op.UIOut = b.CLI
op.Workspace = backend.DefaultStateName
Expand Down
7 changes: 4 additions & 3 deletions backend/remote/backend_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/terraform"
)

Expand Down Expand Up @@ -508,7 +509,7 @@ func (b *Remote) confirm(stopCtx context.Context, op *backend.Operation, opts *t

if err == errRunDiscarded {
err = errApplyDiscarded
if op.Destroy {
if op.PlanMode == plans.DestroyMode {
err = errDestroyDiscarded
}
}
Expand Down Expand Up @@ -551,7 +552,7 @@ func (b *Remote) confirm(stopCtx context.Context, op *backend.Operation, opts *t
if r.Actions.IsDiscardable {
err = b.client.Runs.Discard(stopCtx, r.ID, tfe.RunDiscardOptions{})
if err != nil {
if op.Destroy {
if op.PlanMode == plans.DestroyMode {
return generalError("Failed to discard destroy", err)
}
return generalError("Failed to discard apply", err)
Expand All @@ -560,7 +561,7 @@ func (b *Remote) confirm(stopCtx context.Context, op *backend.Operation, opts *t

// Even if the run was discarded successfully, we still
// return an error as the apply command was canceled.
if op.Destroy {
if op.PlanMode == plans.DestroyMode {
return errDestroyDiscarded
}
return errApplyDiscarded
Expand Down
8 changes: 1 addition & 7 deletions backend/remote/backend_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
Expand Down Expand Up @@ -62,12 +61,7 @@ func (b *Remote) Context(op *backend.Operation) (*terraform.Context, statemgr.Fu
}

// Copy set options from the operation
switch {
case op.Destroy:
opts.PlanMode = plans.DestroyMode
default:
opts.PlanMode = plans.NormalMode
}
opts.PlanMode = op.PlanMode
opts.Targets = op.Targets
opts.UIInput = op.UIIn

Expand Down
19 changes: 17 additions & 2 deletions backend/remote/backend_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
tfe "github.com/hashicorp/go-tfe"
version "github.com/hashicorp/go-version"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/tfdiags"
)

Expand Down Expand Up @@ -89,7 +90,7 @@ func (b *Remote) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operatio
))
}

if !op.HasConfig() && !op.Destroy {
if !op.HasConfig() && op.PlanMode != plans.DestroyMode {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No configuration files found",
Expand Down Expand Up @@ -238,12 +239,26 @@ in order to capture the filesystem context the remote workspace expects:
}

runOptions := tfe.RunCreateOptions{
IsDestroy: tfe.Bool(op.Destroy),
Message: tfe.String(queueMessage),
ConfigurationVersion: cv,
Workspace: w,
}

switch op.PlanMode {
case plans.NormalMode:
// okay, but we don't need to do anything special for this
case plans.DestroyMode:
runOptions.IsDestroy = tfe.Bool(true)
default:
// Shouldn't get here because we should update this for each new
// plan mode we add, mapping it to the corresponding RunCreateOptions
// field.
return nil, generalError(
"Invalid plan mode",
fmt.Errorf("remote backend doesn't support %s", op.PlanMode),
)
}

if len(op.Targets) != 0 {
runOptions.TargetAddrs = make([]string, 0, len(op.Targets))
for _, addr := range op.Targets {
Expand Down
5 changes: 3 additions & 2 deletions backend/remote/backend_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/internal/initwd"
"github.com/hashicorp/terraform/internal/terminal"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform"
Expand Down Expand Up @@ -709,7 +710,7 @@ func TestRemote_planDestroy(t *testing.T) {
defer configCleanup()
defer done(t)

op.Destroy = true
op.PlanMode = plans.DestroyMode
op.Workspace = backend.DefaultStateName

run, err := b.Operation(context.Background(), op)
Expand All @@ -734,7 +735,7 @@ func TestRemote_planDestroyNoConfig(t *testing.T) {
defer configCleanup()
defer done(t)

op.Destroy = true
op.PlanMode = plans.DestroyMode
op.Workspace = backend.DefaultStateName

run, err := b.Operation(context.Background(), op)
Expand Down
Loading

0 comments on commit 06d24ac

Please sign in to comment.