func (c *PlanCommand) Run(args []string) int { var destroy, refresh, detailed bool var outPath string var moduleDepth int args = c.Meta.process(args, true) cmdFlags := c.Meta.flagSet("plan") cmdFlags.BoolVar(&destroy, "destroy", false, "destroy") cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") c.addModuleDepthFlag(cmdFlags, &moduleDepth) cmdFlags.StringVar(&outPath, "out", "", "path") cmdFlags.IntVar( &c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism") cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") cmdFlags.BoolVar(&detailed, "detailed-exitcode", false, "detailed-exitcode") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 } var path string args = cmdFlags.Args() if len(args) > 1 { c.Ui.Error( "The plan command expects at most one argument with the path\n" + "to a Terraform configuration.\n") cmdFlags.Usage() return 1 } else if len(args) == 1 { path = args[0] } else { var err error path, err = os.Getwd() if err != nil { c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) } } countHook := new(CountHook) c.Meta.extraHooks = []terraform.Hook{countHook} // This is going to keep track of shadow errors var shadowErr error ctx, planned, err := c.Context(contextOpts{ Destroy: destroy, Path: path, StatePath: c.Meta.statePath, Parallelism: c.Meta.parallelism, }) if err != nil { c.Ui.Error(err.Error()) return 1 } if planned { c.Ui.Output(c.Colorize().Color( "[reset][bold][yellow]" + "The plan command received a saved plan file as input. This command\n" + "will output the saved plan. This will not modify the already-existing\n" + "plan. If you wish to generate a new plan, please pass in a configuration\n" + "directory as an argument.\n\n")) // Disable refreshing no matter what since we only want to show the plan refresh = false } err = terraform.SetDebugInfo(DefaultDataDir) if err != nil { c.Ui.Error(err.Error()) return 1 } if err := ctx.Input(c.InputMode()); err != nil { c.Ui.Error(fmt.Sprintf("Error configuring: %s", err)) return 1 } // Record any shadow errors for later if err := ctx.ShadowError(); err != nil { shadowErr = multierror.Append(shadowErr, multierror.Prefix( err, "input operation:")) } if !validateContext(ctx, c.Ui) { return 1 } if refresh { c.Ui.Output("Refreshing Terraform state in-memory prior to plan...") c.Ui.Output("The refreshed state will be used to calculate this plan, but") c.Ui.Output("will not be persisted to local or remote state storage.\n") _, err := ctx.Refresh() if err != nil { c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err)) return 1 } c.Ui.Output("") } plan, err := ctx.Plan() if err != nil { c.Ui.Error(fmt.Sprintf("Error running plan: %s", err)) return 1 } if outPath != "" { log.Printf("[INFO] Writing plan output to: %s", outPath) f, err := os.Create(outPath) if err == nil { defer f.Close() err = terraform.WritePlan(plan, f) } if err != nil { c.Ui.Error(fmt.Sprintf("Error writing plan file: %s", err)) return 1 } } if plan.Diff.Empty() { c.Ui.Output( "No changes. Infrastructure is up-to-date. This means that Terraform\n" + "could not detect any differences between your configuration and\n" + "the real physical resources that exist. As a result, Terraform\n" + "doesn't need to do anything.") return 0 } if outPath == "" { c.Ui.Output(strings.TrimSpace(planHeaderNoOutput) + "\n") } else { c.Ui.Output(fmt.Sprintf( strings.TrimSpace(planHeaderYesOutput)+"\n", outPath)) } c.Ui.Output(FormatPlan(&FormatPlanOpts{ Plan: plan, Color: c.Colorize(), ModuleDepth: moduleDepth, })) c.Ui.Output(c.Colorize().Color(fmt.Sprintf( "[reset][bold]Plan:[reset] "+ "%d to add, %d to change, %d to destroy.", countHook.ToAdd+countHook.ToRemoveAndAdd, countHook.ToChange, countHook.ToRemove+countHook.ToRemoveAndAdd))) // Record any shadow errors for later if err := ctx.ShadowError(); err != nil { shadowErr = multierror.Append(shadowErr, multierror.Prefix( err, "plan operation:")) } // If we have an error in the shadow graph, let the user know. c.outputShadowError(shadowErr, true) if detailed { return 2 } return 0 }
func (c *ApplyCommand) Run(args []string) int { var destroyForce, refresh bool args = c.Meta.process(args, true) cmdName := "apply" if c.Destroy { cmdName = "destroy" } cmdFlags := c.Meta.flagSet(cmdName) if c.Destroy { cmdFlags.BoolVar(&destroyForce, "force", false, "force") } cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") cmdFlags.IntVar( &c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism") cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 } pwd, err := os.Getwd() if err != nil { c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) return 1 } var configPath string maybeInit := true args = cmdFlags.Args() if len(args) > 1 { c.Ui.Error("The apply command expects at most one argument.") cmdFlags.Usage() return 1 } else if len(args) == 1 { configPath = args[0] } else { configPath = pwd maybeInit = false } // Prepare the extra hooks to count resources countHook := new(CountHook) stateHook := new(StateHook) c.Meta.extraHooks = []terraform.Hook{countHook, stateHook} if !c.Destroy && maybeInit { // Do a detect to determine if we need to do an init + apply. if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil { c.Ui.Error(fmt.Sprintf( "Invalid path: %s", err)) return 1 } else if !strings.HasPrefix(detected, "file") { // If this isn't a file URL then we're doing an init + // apply. var init InitCommand init.Meta = c.Meta if code := init.Run([]string{detected}); code != 0 { return code } // Change the config path to be the cwd configPath = pwd } } terraform.SetDebugInfo(DefaultDataDir) // Check for the legacy graph if experiment.Enabled(experiment.X_legacyGraph) { c.Ui.Output(c.Colorize().Color( "[reset][bold][yellow]" + "Legacy graph enabled! This will use the graph from Terraform 0.7.x\n" + "to execute this operation. This will be removed in the future so\n" + "please report any issues causing you to use this to the Terraform\n" + "project.\n\n")) } // This is going to keep track of shadow errors var shadowErr error // Build the context based on the arguments given ctx, planned, err := c.Context(contextOpts{ Destroy: c.Destroy, Path: configPath, StatePath: c.Meta.statePath, Parallelism: c.Meta.parallelism, }) if err != nil { c.Ui.Error(err.Error()) return 1 } if c.Destroy && planned { c.Ui.Error(fmt.Sprintf( "Destroy can't be called with a plan file.")) return 1 } 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 } } if !planned { if err := ctx.Input(c.InputMode()); err != nil { c.Ui.Error(fmt.Sprintf("Error configuring: %s", err)) return 1 } // Record any shadow errors for later if err := ctx.ShadowError(); err != nil { shadowErr = multierror.Append(shadowErr, multierror.Prefix( err, "input operation:")) } } if !validateContext(ctx, c.Ui) { return 1 } // Plan if we haven't already if !planned { if refresh { if _, err := ctx.Refresh(); err != nil { c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err)) return 1 } } if _, err := ctx.Plan(); err != nil { c.Ui.Error(fmt.Sprintf( "Error creating plan: %s", err)) return 1 } // Record any shadow errors for later if err := ctx.ShadowError(); err != nil { shadowErr = multierror.Append(shadowErr, multierror.Prefix( err, "plan operation:")) } } // Setup the state hook for continuous state updates { state, err := c.State() if err != nil { c.Ui.Error(fmt.Sprintf( "Error reading state: %s", err)) return 1 } stateHook.State = state } // Start the apply in a goroutine so that we can be interrupted. var state *terraform.State var applyErr error doneCh := make(chan struct{}) go func() { defer close(doneCh) state, applyErr = ctx.Apply() // Record any shadow errors for later if err := ctx.ShadowError(); err != nil { shadowErr = multierror.Append(shadowErr, multierror.Prefix( err, "apply operation:")) } }() // Wait for the apply to finish or for us to be interrupted so // we can handle it properly. err = nil select { case <-c.ShutdownCh: c.Ui.Output("Interrupt received. Gracefully shutting down...") // Stop execution go ctx.Stop() // Still get the result, since there is still one select { case <-c.ShutdownCh: c.Ui.Error( "Two interrupts received. Exiting immediately. Note that data\n" + "loss may have occurred.") return 1 case <-doneCh: } case <-doneCh: } // Persist the state if state != nil { if err := c.Meta.PersistState(state); err != nil { c.Ui.Error(fmt.Sprintf("Failed to save state: %s", err)) return 1 } } if applyErr != nil { c.Ui.Error(fmt.Sprintf( "Error applying plan:\n\n"+ "%s\n\n"+ "Terraform does not automatically rollback in the face of errors.\n"+ "Instead, your Terraform state file has been partially updated with\n"+ "any resources that successfully completed. Please address the error\n"+ "above and apply again to incrementally change your infrastructure.", multierror.Flatten(applyErr))) return 1 } if c.Destroy { c.Ui.Output(c.Colorize().Color(fmt.Sprintf( "[reset][bold][green]\n"+ "Destroy complete! Resources: %d destroyed.", countHook.Removed))) } else { c.Ui.Output(c.Colorize().Color(fmt.Sprintf( "[reset][bold][green]\n"+ "Apply complete! Resources: %d added, %d changed, %d destroyed.", countHook.Added, countHook.Changed, countHook.Removed))) } if countHook.Added > 0 || countHook.Changed > 0 { c.Ui.Output(c.Colorize().Color(fmt.Sprintf( "[reset]\n"+ "The state of your infrastructure has been saved to the path\n"+ "below. This state is required to modify and destroy your\n"+ "infrastructure, so keep it safe. To inspect the complete state\n"+ "use the `terraform show` command.\n\n"+ "State path: %s", c.Meta.StateOutPath()))) } if !c.Destroy { if outputs := outputsAsString(state, terraform.RootModulePath, ctx.Module().Config().Outputs, true); outputs != "" { c.Ui.Output(c.Colorize().Color(outputs)) } } // If we have an error in the shadow graph, let the user know. c.outputShadowError(shadowErr, applyErr == nil) return 0 }