diff --git a/command/apply.go b/command/apply.go index 83ae29a7f6a4..db9f868b2ebc 100644 --- a/command/apply.go +++ b/command/apply.go @@ -27,7 +27,8 @@ type ApplyCommand struct { } func (c *ApplyCommand) Run(args []string) int { - var destroyForce, refresh bool + var destroyForce, refresh, confirm bool + var moduleDepth int args = c.Meta.process(args, true) cmdName := "apply" @@ -40,6 +41,10 @@ func (c *ApplyCommand) Run(args []string) int { cmdFlags.BoolVar(&destroyForce, "force", false, "force") } cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") + if !c.Destroy { + cmdFlags.BoolVar(&confirm, "confirm", false, "confirm plan before applying") + c.addModuleDepthFlag(cmdFlags, &moduleDepth) + } cmdFlags.IntVar( &c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism") cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") @@ -111,6 +116,10 @@ func (c *ApplyCommand) Run(args []string) int { "Destroy can't be called with a plan file.")) return 1 } + if confirm && planned { + c.Ui.Error("Cannot confirm with a plan file.") + return 1 + } if !destroyForce && c.Destroy { // Default destroy message desc := "Terraform will delete all your managed infrastructure.\n" + @@ -162,11 +171,36 @@ func (c *ApplyCommand) Run(args []string) int { } } - if _, err := ctx.Plan(); err != nil { + plan, err := ctx.Plan() + if err != nil { c.Ui.Error(fmt.Sprintf( "Error creating plan: %s", err)) return 1 } + + if confirm { + c.Ui.Output(strings.TrimSpace(confirmHeader) + "\n") + c.Ui.Output(FormatPlan(&FormatPlanOpts{ + Plan: plan, + Color: c.Colorize(), + ModuleDepth: moduleDepth, + })) + desc := "Terraform will apply the plan described above.\n" + + "Only 'yes' will be accepted to confirm." + v, err := c.UIInput().Input(&terraform.InputOpts{ + Id: "confirm", + Query: "Do you want to apply the plan above?", + Description: desc, + }) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err)) + return 1 + } + if v != "yes" { + c.Ui.Output("Apply cancelled.") + return 1 + } + } } // Setup the state hook for continuous state updates @@ -299,8 +333,14 @@ Options: modifying. Defaults to the "-state-out" path with ".backup" extension. Set to "-" to disable backup. + -confirm Display the plan and ask for confirmation before applying. + -input=true Ask for input for variables if not directly set. + -module-depth=n Specifies the depth of modules to show when using -confirm. + This does not affect the plan itself, only the output + shown. By default, this is -1, which will expand all. + -no-color If specified, output won't contain any color. -parallelism=n Limit the number of concurrent operations. @@ -430,3 +470,11 @@ func outputsAsString(state *terraform.State, schema []*config.Output, includeHea return strings.TrimSpace(outputBuf.String()) } + +const confirmHeader = ` +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/website/source/docs/commands/apply.html.markdown b/website/source/docs/commands/apply.html.markdown index 6f9a35e2fbba..16bc251cf65e 100644 --- a/website/source/docs/commands/apply.html.markdown +++ b/website/source/docs/commands/apply.html.markdown @@ -33,6 +33,8 @@ The command-line flags are all optional. The list of available flags are: * `-input=true` - Ask for input for variables if not directly set. +* `-confirm` - Display the plan and ask for confirmation before applying. + * `-no-color` - Disables output with coloring. * `-parallelism=n` - Limit the number of concurrent operation as Terraform