Merge #7251: terraform apply -auto-approve=false

This commit is contained in:
Martin Atkins 2017-06-27 11:23:47 -07:00
commit c487c0bd0e
5 changed files with 103 additions and 40 deletions

View File

@ -121,9 +121,11 @@ 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
DestroyForce bool
// Input/output/control options.
UIIn terraform.UIInput

View File

@ -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,73 @@ 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.Destroy && !op.DestroyForce) ||
(!op.Destroy && !op.AutoApprove && !trivialPlan)) {
var desc, query string
if op.Destroy {
// Default destroy message
desc = "Terraform will delete all your managed infrastructure, as shown above.\n" +
"There is no undo. Only 'yes' will be accepted to confirm."
// If targets are specified, list those to user
if op.Targets != nil {
var descBuffer bytes.Buffer
descBuffer.WriteString("Terraform will delete the following infrastructure:\n")
for _, target := range op.Targets {
descBuffer.WriteString("\t")
descBuffer.WriteString(target)
descBuffer.WriteString("\n")
}
descBuffer.WriteString("There is no undo. Only 'yes' will be accepted to confirm")
desc = descBuffer.String()
}
query = "Do you really want to destroy?"
} else {
desc = "Terraform will apply the changes described above.\n" +
"Only 'yes' will be accepted to approve."
query = "Do you want to apply these changes?"
}
if !trivialPlan {
// Display the plan of what we are going to apply/destroy.
if op.Destroy {
op.UIOut.Output("\n" + strings.TrimSpace(approveDestroyPlanHeader) + "\n")
} else {
op.UIOut.Output("\n" + strings.TrimSpace(approvePlanHeader) + "\n")
}
op.UIOut.Output(format.Plan(&format.PlanOpts{
Plan: plan,
Color: b.Colorize(),
ModuleDepth: -1,
}))
}
v, err := op.UIIn.Input(&terraform.InputOpts{
Id: "approve",
Query: query,
Description: desc,
})
if err != nil {
runningOp.Err = errwrap.Wrapf("Error asking for approval: {{err}}", err)
return
}
if v != "yes" {
if op.Destroy {
runningOp.Err = errors.New("Destroy cancelled.")
} else {
runningOp.Err = errors.New("Apply cancelled.")
}
return
}
}
}
// Setup our hook for continuous state updates
@ -288,3 +352,17 @@ 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.
`
const approveDestroyPlanHeader = `
The Terraform destroy plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning.
Resources shown in red will be destroyed.
`

View File

@ -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")
@ -112,6 +115,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)
@ -153,41 +161,6 @@ func (c *ApplyCommand) Run(args []string) int {
return 1
}
// If we're not forcing and we're destroying, verify with the
// user at this point.
if !destroyForce && c.Destroy {
// Default destroy message
desc := "Terraform will delete all your managed infrastructure.\n" +
"There is no undo. Only 'yes' will be accepted to confirm."
// If targets are specified, list those to user
if c.Meta.targets != nil {
var descBuffer bytes.Buffer
descBuffer.WriteString("Terraform will delete the following infrastructure:\n")
for _, target := range c.Meta.targets {
descBuffer.WriteString("\t")
descBuffer.WriteString(target)
descBuffer.WriteString("\n")
}
descBuffer.WriteString("There is no undo. Only 'yes' will be accepted to confirm")
desc = descBuffer.String()
}
v, err := c.UIInput().Input(&terraform.InputOpts{
Id: "destroy",
Query: "Do you really want to destroy?",
Description: desc,
})
if err != nil {
c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err))
return 1
}
if v != "yes" {
c.Ui.Output("Destroy cancelled.")
return 1
}
}
// Build the operation
opReq := c.Operation()
opReq.Destroy = c.Destroy
@ -195,6 +168,8 @@ func (c *ApplyCommand) Run(args []string) int {
opReq.Plan = plan
opReq.PlanRefresh = refresh
opReq.Type = backend.OperationTypeApply
opReq.AutoApprove = autoApprove
opReq.DestroyForce = destroyForce
// Perform the operation
ctx, ctxCancel := context.WithCancel(context.Background())
@ -289,6 +264,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.

View File

@ -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,

View File

@ -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