// Apply applies the changes represented by this context and returns // the resulting state. // // In addition to returning the resulting state, this context is updated // with the latest state. func (c *Context) Apply() (*State, error) { v := c.acquireRun("apply") defer c.releaseRun(v) // Copy our own state c.state = c.state.DeepCopy() // Enable the new graph by default X_legacyGraph := experiment.Enabled(experiment.X_legacyGraph) // Build the graph. var graph *Graph var err error if !X_legacyGraph { graph, err = (&ApplyGraphBuilder{ Module: c.module, Diff: c.diff, State: c.state, Providers: c.components.ResourceProviders(), Provisioners: c.components.ResourceProvisioners(), Destroy: c.destroy, }).Build(RootModulePath) } else { graph, err = c.Graph(&ContextGraphOpts{Validate: true}) } if err != nil { return nil, err } // Determine the operation operation := walkApply if c.destroy { operation = walkDestroy } // Walk the graph walker, err := c.walk(graph, graph, operation) if len(walker.ValidationErrors) > 0 { err = multierror.Append(err, walker.ValidationErrors...) } // Clean out any unused things c.state.prune() return c.state, err }
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 } } // Check for the new apply if experiment.Enabled(experiment.X_newApply) && !experiment.Force() { desc := "Experimental new apply graph has been enabled. This may still\n" + "have bugs, and should be used with care. If you'd like to continue,\n" + "you must enter exactly 'yes' as a response." v, err := c.UIInput().Input(&terraform.InputOpts{ Id: "Xnew-apply", Query: "Experimental feature enabled: new apply graph. Continue?", 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 } } // Check for the new destroy if experiment.Enabled(experiment.X_newDestroy) && !experiment.Force() { desc := "Experimental new destroy graph has been enabled. This may still\n" + "have bugs, and should be used with care. If you'd like to continue,\n" + "you must enter exactly 'yes' as a response." v, err := c.UIInput().Input(&terraform.InputOpts{ Id: "Xnew-destroy", Query: "Experimental feature enabled: new destroy graph. Continue?", 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 } } // 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 } } 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 } } // 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() }() // 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)) } } return 0 }
func (c *Context) walk( graph, shadow *Graph, operation walkOperation) (*ContextGraphWalker, error) { // Keep track of the "real" context which is the context that does // the real work: talking to real providers, modifying real state, etc. realCtx := c // If we don't want shadowing, remove it if !experiment.Enabled(experiment.X_shadow) { shadow = nil } // If we have a shadow graph, walk that as well var shadowCtx *Context var shadowCloser Shadow if c.shadow && shadow != nil { // Build the shadow context. In the process, override the real context // with the one that is wrapped so that the shadow context can verify // the results of the real. realCtx, shadowCtx, shadowCloser = newShadowContext(c) } // Just log this so we can see it in a debug log if !c.shadow { log.Printf("[WARN] terraform: shadow graph disabled") } log.Printf("[DEBUG] Starting graph walk: %s", operation.String()) walker := &ContextGraphWalker{ Context: realCtx, Operation: operation, } // Watch for a stop so we can call the provider Stop() API. doneCh := make(chan struct{}) go c.watchStop(walker, c.stopCh, doneCh) // Walk the real graph, this will block until it completes realErr := graph.Walk(walker) // Close the done channel so the watcher stops close(doneCh) // If we have a shadow graph and we interrupted the real graph, then // we just close the shadow and never verify it. It is non-trivial to // recreate the exact execution state up until an interruption so this // isn't supported with shadows at the moment. if shadowCloser != nil && c.sh.Stopped() { // Ignore the error result, there is nothing we could care about shadowCloser.CloseShadow() // Set it to nil so we don't do anything shadowCloser = nil } // If we have a shadow graph, wait for that to complete. if shadowCloser != nil { // Build the graph walker for the shadow. We also wrap this in // a panicwrap so that panics are captured. For the shadow graph, // we just want panics to be normal errors rather than to crash // Terraform. shadowWalker := GraphWalkerPanicwrap(&ContextGraphWalker{ Context: shadowCtx, Operation: operation, }) // Kick off the shadow walk. This will block on any operations // on the real walk so it is fine to start first. log.Printf("[INFO] Starting shadow graph walk: %s", operation.String()) shadowCh := make(chan error) go func() { shadowCh <- shadow.Walk(shadowWalker) }() // Notify the shadow that we're done if err := shadowCloser.CloseShadow(); err != nil { c.shadowErr = multierror.Append(c.shadowErr, err) } // Wait for the walk to end log.Printf("[DEBUG] Waiting for shadow graph to complete...") shadowWalkErr := <-shadowCh // Get any shadow errors if err := shadowCloser.ShadowError(); err != nil { c.shadowErr = multierror.Append(c.shadowErr, err) } // Verify the contexts (compare) if err := shadowContextVerify(realCtx, shadowCtx); err != nil { c.shadowErr = multierror.Append(c.shadowErr, err) } // At this point, if we're supposed to fail on error, then // we PANIC. Some tests just verify that there is an error, // so simply appending it to realErr and returning could hide // shadow problems. // // This must be done BEFORE appending shadowWalkErr since the // shadowWalkErr may include expected errors. // // We only do this if we don't have a real error. In the case of // a real error, we can't guarantee what nodes were and weren't // traversed in parallel scenarios so we can't guarantee no // shadow errors. if c.shadowErr != nil && contextFailOnShadowError && realErr == nil { panic(multierror.Prefix(c.shadowErr, "shadow graph:")) } // Now, if we have a walk error, we append that through if shadowWalkErr != nil { c.shadowErr = multierror.Append(c.shadowErr, shadowWalkErr) } if c.shadowErr == nil { log.Printf("[INFO] Shadow graph success!") } else { log.Printf("[ERROR] Shadow graph error: %s", c.shadowErr) // If we're supposed to fail on shadow errors, then report it if contextFailOnShadowError { realErr = multierror.Append(realErr, multierror.Prefix( c.shadowErr, "shadow graph:")) } } } return walker, realErr }
// Plan generates an execution plan for the given context. // // The execution plan encapsulates the context and can be stored // in order to reinstantiate a context later for Apply. // // Plan also updates the diff of this context to be the diff generated // by the plan, so Apply can be called after. func (c *Context) Plan() (*Plan, error) { v := c.acquireRun("plan") defer c.releaseRun(v) p := &Plan{ Module: c.module, Vars: c.variables, State: c.state, Targets: c.targets, } var operation walkOperation if c.destroy { operation = walkPlanDestroy } else { // Set our state to be something temporary. We do this so that // the plan can update a fake state so that variables work, then // we replace it back with our old state. old := c.state if old == nil { c.state = &State{} c.state.init() } else { c.state = old.DeepCopy() } defer func() { c.state = old }() operation = walkPlan } // Setup our diff c.diffLock.Lock() c.diff = new(Diff) c.diff.init() c.diffLock.Unlock() // Used throughout below X_legacyGraph := experiment.Enabled(experiment.X_legacyGraph) // Build the graph. var graph *Graph var err error if !X_legacyGraph { if c.destroy { graph, err = (&DestroyPlanGraphBuilder{ Module: c.module, State: c.state, Targets: c.targets, }).Build(RootModulePath) } else { graph, err = (&PlanGraphBuilder{ Module: c.module, State: c.state, Providers: c.components.ResourceProviders(), Targets: c.targets, }).Build(RootModulePath) } } else { graph, err = c.Graph(&ContextGraphOpts{Validate: true}) } if err != nil { return nil, err } // Do the walk walker, err := c.walk(graph, graph, operation) if err != nil { return nil, err } p.Diff = c.diff // If this is true, it means we're running unit tests. In this case, // we perform a deep copy just to ensure that all context tests also // test that a diff is copy-able. This will panic if it fails. This // is enabled during unit tests. // // This should never be true during production usage, but even if it is, // it can't do any real harm. if contextTestDeepCopyOnPlan { p.Diff.DeepCopy() } // We don't do the reverification during the new destroy plan because // it will use a different apply process. if X_legacyGraph { // Now that we have a diff, we can build the exact graph that Apply will use // and catch any possible cycles during the Plan phase. if _, err := c.Graph(&ContextGraphOpts{Validate: true}); err != nil { return nil, err } } var errs error if len(walker.ValidationErrors) > 0 { errs = multierror.Append(errs, walker.ValidationErrors...) } return p, errs }
// Apply applies the changes represented by this context and returns // the resulting state. // // In addition to returning the resulting state, this context is updated // with the latest state. func (c *Context) Apply() (*State, error) { v := c.acquireRun() defer c.releaseRun(v) // Copy our own state c.state = c.state.DeepCopy() X_newApply := experiment.Enabled(experiment.X_newApply) X_newDestroy := experiment.Enabled(experiment.X_newDestroy) newGraphEnabled := (c.destroy && X_newDestroy) || (!c.destroy && X_newApply) // Build the original graph. This is before the new graph builders // coming in 0.8. We do this for shadow graphing. oldGraph, err := c.Graph(&ContextGraphOpts{Validate: true}) if err != nil && X_newApply { // If we had an error graphing but we're using the new graph, // just set it to nil and let it go. There are some features that // may work with the new graph that don't with the old. oldGraph = nil err = nil } if err != nil { return nil, err } // Build the new graph. We do this no matter what so we can shadow it. newGraph, err := (&ApplyGraphBuilder{ Module: c.module, Diff: c.diff, State: c.state, Providers: c.components.ResourceProviders(), Provisioners: c.components.ResourceProvisioners(), Destroy: c.destroy, }).Build(RootModulePath) if err != nil && !newGraphEnabled { // If we had an error graphing but we're not using this graph, just // set it to nil and record it as a shadow error. c.shadowErr = multierror.Append(c.shadowErr, fmt.Errorf( "Error building new graph: %s", err)) newGraph = nil err = nil } if err != nil { return nil, err } // Determine what is the real and what is the shadow. The logic here // is straightforward though the if statements are not: // // * Destroy mode - always use original, shadow with nothing because // we're only testing the new APPLY graph. // * Apply with new apply - use new graph, shadow is new graph. We can't // shadow with the old graph because the old graph does a lot more // that it shouldn't. // * Apply with old apply - use old graph, shadow with new graph. // real := oldGraph shadow := newGraph if newGraphEnabled { log.Printf("[WARN] terraform: real graph is experiment, shadow is experiment") real = shadow } else { log.Printf("[WARN] terraform: real graph is original, shadow is experiment") } // Determine the operation operation := walkApply if c.destroy { operation = walkDestroy } // This shouldn't happen, so assert it. This is before any state changes // so it is safe to crash here. if real == nil { panic("nil real graph") } // Walk the graph walker, err := c.walk(real, shadow, operation) if len(walker.ValidationErrors) > 0 { err = multierror.Append(err, walker.ValidationErrors...) } // Clean out any unused things c.state.prune() return c.state, err }
// Plan generates an execution plan for the given context. // // The execution plan encapsulates the context and can be stored // in order to reinstantiate a context later for Apply. // // Plan also updates the diff of this context to be the diff generated // by the plan, so Apply can be called after. func (c *Context) Plan() (*Plan, error) { v := c.acquireRun("plan") defer c.releaseRun(v) p := &Plan{ Module: c.module, Vars: c.variables, State: c.state, Targets: c.targets, } var operation walkOperation if c.destroy { operation = walkPlanDestroy } else { // Set our state to be something temporary. We do this so that // the plan can update a fake state so that variables work, then // we replace it back with our old state. old := c.state if old == nil { c.state = &State{} c.state.init() } else { c.state = old.DeepCopy() } defer func() { c.state = old }() operation = walkPlan } // Setup our diff c.diffLock.Lock() c.diff = new(Diff) c.diff.init() c.diffLock.Unlock() // Used throughout below X_newApply := experiment.Enabled(experiment.X_newApply) X_newDestroy := experiment.Enabled(experiment.X_newDestroy) newGraphEnabled := (c.destroy && X_newDestroy) || (!c.destroy && X_newApply) // Build the original graph. This is before the new graph builders // coming in 0.8. We do this for shadow graphing. oldGraph, err := c.Graph(&ContextGraphOpts{Validate: true}) if err != nil && newGraphEnabled { // If we had an error graphing but we're using the new graph, // just set it to nil and let it go. There are some features that // may work with the new graph that don't with the old. oldGraph = nil err = nil } if err != nil { return nil, err } // Build the new graph. We do this no matter wht so we can shadow it. var newGraph *Graph err = nil if c.destroy { newGraph, err = (&DestroyPlanGraphBuilder{ Module: c.module, State: c.state, Targets: c.targets, }).Build(RootModulePath) } else { newGraph, err = (&PlanGraphBuilder{ Module: c.module, State: c.state, Providers: c.components.ResourceProviders(), Targets: c.targets, }).Build(RootModulePath) } if err != nil && !newGraphEnabled { // If we had an error graphing but we're not using this graph, just // set it to nil and record it as a shadow error. c.shadowErr = multierror.Append(c.shadowErr, fmt.Errorf( "Error building new graph: %s", err)) newGraph = nil err = nil } if err != nil { return nil, err } // Determine what is the real and what is the shadow. The logic here // is straightforward though the if statements are not: // // * If the new graph, shadow with experiment in both because the // experiment has less nodes so the original can't shadow. // * If not the new graph, shadow with the experiment // real := oldGraph shadow := newGraph if newGraphEnabled { log.Printf("[WARN] terraform: real graph is experiment, shadow is experiment") real = shadow } else { log.Printf("[WARN] terraform: real graph is original, shadow is experiment") } // Special case here: if we're using destroy don't shadow it because // the new destroy graph behaves a bit differently on purpose by not // setting the module destroy flag. if c.destroy && !newGraphEnabled { shadow = nil } // Do the walk walker, err := c.walk(real, shadow, operation) if err != nil { return nil, err } p.Diff = c.diff // If this is true, it means we're running unit tests. In this case, // we perform a deep copy just to ensure that all context tests also // test that a diff is copy-able. This will panic if it fails. This // is enabled during unit tests. // // This should never be true during production usage, but even if it is, // it can't do any real harm. if contextTestDeepCopyOnPlan { p.Diff.DeepCopy() } // We don't do the reverification during the new destroy plan because // it will use a different apply process. if !newGraphEnabled { // Now that we have a diff, we can build the exact graph that Apply will use // and catch any possible cycles during the Plan phase. if _, err := c.Graph(&ContextGraphOpts{Validate: true}); err != nil { return nil, err } } var errs error if len(walker.ValidationErrors) > 0 { errs = multierror.Append(errs, walker.ValidationErrors...) } return p, errs }