diff --git a/backend/backend.go b/backend/backend.go index a84d33d63ebc..3708cf40ab50 100644 --- a/backend/backend.go +++ b/backend/backend.go @@ -121,9 +121,10 @@ type Operation struct { // The options below are more self-explanatory and affect the runtime // behavior of the operation. - Destroy bool - Targets []string - Variables map[string]interface{} + Destroy bool + Targets []string + Variables map[string]interface{} + AutoApprove bool // Input/output/control options. UIIn terraform.UIInput diff --git a/backend/local/backend_apply.go b/backend/local/backend_apply.go index 269095940a1e..7ebee241e6d7 100644 --- a/backend/local/backend_apply.go +++ b/backend/local/backend_apply.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/command/clistate" + "github.com/hashicorp/terraform/command/format" "github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/terraform" @@ -89,10 +90,37 @@ func (b *Local) opApply( // Perform the plan log.Printf("[INFO] backend/local: apply calling Plan") - if _, err := tfCtx.Plan(); err != nil { + plan, err := tfCtx.Plan() + if err != nil { runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", err) return } + + trivialPlan := plan.Diff == nil || plan.Diff.Empty() + hasUI := op.UIOut != nil && op.UIIn != nil + if hasUI && !op.AutoApprove && !trivialPlan { + op.UIOut.Output(strings.TrimSpace(approvePlanHeader) + "\n") + op.UIOut.Output(format.Plan(&format.PlanOpts{ + Plan: plan, + Color: b.Colorize(), + ModuleDepth: -1, + })) + desc := "Terraform will apply the plan described above.\n" + + "Only 'yes' will be accepted to approve." + v, err := op.UIIn.Input(&terraform.InputOpts{ + Id: "approve", + Query: "Do you want to apply the plan above?", + Description: desc, + }) + if err != nil { + runningOp.Err = errwrap.Wrapf("Error asking for approval: {{err}}", err) + return + } + if v != "yes" { + runningOp.Err = errors.New("Apply cancelled.") + return + } + } } // Setup our hook for continuous state updates @@ -288,3 +316,11 @@ Terraform encountered an error attempting to save the state before canceling the current operation. Once the operation is complete another attempt will be made to save the final state. ` + +const approvePlanHeader = ` +The Terraform execution plan has been generated and is shown below. +Resources are shown in alphabetical order for quick scanning. Green resources +will be created (or destroyed and then created if an existing resource +exists), yellow resources are being changed in-place, and red resources +will be destroyed. Cyan entries are data sources to be read. +` diff --git a/command/apply.go b/command/apply.go index 9c8c4904a2a7..d4fea85f8004 100644 --- a/command/apply.go +++ b/command/apply.go @@ -29,7 +29,7 @@ type ApplyCommand struct { } func (c *ApplyCommand) Run(args []string) int { - var destroyForce, refresh bool + var destroyForce, refresh, autoApprove bool args = c.Meta.process(args, true) cmdName := "apply" @@ -42,6 +42,9 @@ func (c *ApplyCommand) Run(args []string) int { cmdFlags.BoolVar(&destroyForce, "force", false, "force") } cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") + if !c.Destroy { + cmdFlags.BoolVar(&autoApprove, "auto-approve", true, "skip interactive approval of plan before applying") + } cmdFlags.IntVar( &c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism") cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path") @@ -106,6 +109,11 @@ func (c *ApplyCommand) Run(args []string) int { if plan != nil { // Reset the config path for backend loading configPath = "" + + if !autoApprove { + c.Ui.Error("Cannot combine -auto-approve=false with a plan file.") + return 1 + } } // Load the module if we don't have one yet (not running from plan) @@ -189,6 +197,7 @@ func (c *ApplyCommand) Run(args []string) int { opReq.Plan = plan opReq.PlanRefresh = refresh opReq.Type = backend.OperationTypeApply + opReq.AutoApprove = autoApprove // Perform the operation ctx, ctxCancel := context.WithCancel(context.Background()) @@ -283,6 +292,10 @@ Options: -lock-timeout=0s Duration to retry a state lock. + -auto-approve=true Skip interactive approval of plan before applying. In a + future version of Terraform, this flag's default value + will change to false. + -input=true Ask for input for variables if not directly set. -no-color If specified, output won't contain any color. diff --git a/command/meta_backend.go b/command/meta_backend.go index 690eddcbdde5..854ceeb12564 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -168,6 +168,7 @@ func (m *Meta) Operation() *backend.Operation { PlanOutBackend: m.backendState, Targets: m.targets, UIIn: m.UIInput(), + UIOut: m.Ui, Workspace: m.Workspace(), LockState: m.stateLock, StateLockTimeout: m.stateLockTimeout, diff --git a/website/source/docs/commands/apply.html.markdown b/website/source/docs/commands/apply.html.markdown index 3c00e7c0597f..8eb1f7a6d28b 100644 --- a/website/source/docs/commands/apply.html.markdown +++ b/website/source/docs/commands/apply.html.markdown @@ -37,6 +37,9 @@ The command-line flags are all optional. The list of available flags are: * `-input=true` - Ask for input for variables if not directly set. +* `-auto-approve=true` - Skip interactive approval of plan before applying. In a + future version of Terraform, this flag's default value will change to false. + * `-no-color` - Disables output with coloring. * `-parallelism=n` - Limit the number of concurrent operation as Terraform