github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/backend/local/backend_apply.go (about) 1 package local 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "strings" 8 9 "github.com/hashicorp/errwrap" 10 "github.com/hashicorp/go-multierror" 11 "github.com/hashicorp/terraform/backend" 12 clistate "github.com/hashicorp/terraform/command/state" 13 "github.com/hashicorp/terraform/config/module" 14 "github.com/hashicorp/terraform/state" 15 "github.com/hashicorp/terraform/terraform" 16 ) 17 18 func (b *Local) opApply( 19 ctx context.Context, 20 op *backend.Operation, 21 runningOp *backend.RunningOperation) { 22 log.Printf("[INFO] backend/local: starting Apply operation") 23 24 // If we have a nil module at this point, then set it to an empty tree 25 // to avoid any potential crashes. 26 if op.Plan == nil && op.Module == nil && !op.Destroy { 27 runningOp.Err = fmt.Errorf(strings.TrimSpace(applyErrNoConfig)) 28 return 29 } 30 31 // If we have a nil module at this point, then set it to an empty tree 32 // to avoid any potential crashes. 33 if op.Module == nil { 34 op.Module = module.NewEmptyTree() 35 } 36 37 // Setup our count hook that keeps track of resource changes 38 countHook := new(CountHook) 39 stateHook := new(StateHook) 40 if b.ContextOpts == nil { 41 b.ContextOpts = new(terraform.ContextOpts) 42 } 43 old := b.ContextOpts.Hooks 44 defer func() { b.ContextOpts.Hooks = old }() 45 b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook, stateHook) 46 47 // Get our context 48 tfCtx, opState, err := b.context(op) 49 if err != nil { 50 runningOp.Err = err 51 return 52 } 53 54 if op.LockState { 55 lockInfo := state.NewLockInfo() 56 lockInfo.Operation = op.Type.String() 57 lockID, err := clistate.Lock(opState, lockInfo, b.CLI, b.Colorize()) 58 if err != nil { 59 runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err) 60 return 61 } 62 63 defer func() { 64 if err := clistate.Unlock(opState, lockID, b.CLI, b.Colorize()); err != nil { 65 runningOp.Err = multierror.Append(runningOp.Err, err) 66 } 67 }() 68 } 69 70 // Setup the state 71 runningOp.State = tfCtx.State() 72 73 // If we weren't given a plan, then we refresh/plan 74 if op.Plan == nil { 75 // If we're refreshing before apply, perform that 76 if op.PlanRefresh { 77 log.Printf("[INFO] backend/local: apply calling Refresh") 78 _, err := tfCtx.Refresh() 79 if err != nil { 80 runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", err) 81 return 82 } 83 } 84 85 // Perform the plan 86 log.Printf("[INFO] backend/local: apply calling Plan") 87 if _, err := tfCtx.Plan(); err != nil { 88 runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", err) 89 return 90 } 91 } 92 93 // Setup our hook for continuous state updates 94 stateHook.State = opState 95 96 // Start the apply in a goroutine so that we can be interrupted. 97 var applyState *terraform.State 98 var applyErr error 99 doneCh := make(chan struct{}) 100 go func() { 101 defer close(doneCh) 102 applyState, applyErr = tfCtx.Apply() 103 104 /* 105 // Record any shadow errors for later 106 if err := ctx.ShadowError(); err != nil { 107 shadowErr = multierror.Append(shadowErr, multierror.Prefix( 108 err, "apply operation:")) 109 } 110 */ 111 }() 112 113 // Wait for the apply to finish or for us to be interrupted so 114 // we can handle it properly. 115 err = nil 116 select { 117 case <-ctx.Done(): 118 if b.CLI != nil { 119 b.CLI.Output("Interrupt received. Gracefully shutting down...") 120 } 121 122 // Stop execution 123 go tfCtx.Stop() 124 125 // Wait for completion still 126 <-doneCh 127 case <-doneCh: 128 } 129 130 // Store the final state 131 runningOp.State = applyState 132 133 // Persist the state 134 if err := opState.WriteState(applyState); err != nil { 135 runningOp.Err = fmt.Errorf("Failed to save state: %s", err) 136 return 137 } 138 if err := opState.PersistState(); err != nil { 139 runningOp.Err = fmt.Errorf("Failed to save state: %s", err) 140 return 141 } 142 143 if applyErr != nil { 144 runningOp.Err = fmt.Errorf( 145 "Error applying plan:\n\n"+ 146 "%s\n\n"+ 147 "Terraform does not automatically rollback in the face of errors.\n"+ 148 "Instead, your Terraform state file has been partially updated with\n"+ 149 "any resources that successfully completed. Please address the error\n"+ 150 "above and apply again to incrementally change your infrastructure.", 151 multierror.Flatten(applyErr)) 152 return 153 } 154 155 // If we have a UI, output the results 156 if b.CLI != nil { 157 if op.Destroy { 158 b.CLI.Output(b.Colorize().Color(fmt.Sprintf( 159 "[reset][bold][green]\n"+ 160 "Destroy complete! Resources: %d destroyed.", 161 countHook.Removed))) 162 } else { 163 b.CLI.Output(b.Colorize().Color(fmt.Sprintf( 164 "[reset][bold][green]\n"+ 165 "Apply complete! Resources: %d added, %d changed, %d destroyed.", 166 countHook.Added, 167 countHook.Changed, 168 countHook.Removed))) 169 } 170 171 if countHook.Added > 0 || countHook.Changed > 0 { 172 b.CLI.Output(b.Colorize().Color(fmt.Sprintf( 173 "[reset]\n"+ 174 "The state of your infrastructure has been saved to the path\n"+ 175 "below. This state is required to modify and destroy your\n"+ 176 "infrastructure, so keep it safe. To inspect the complete state\n"+ 177 "use the `terraform show` command.\n\n"+ 178 "State path: %s", 179 b.StateOutPath))) 180 } 181 } 182 } 183 184 const applyErrNoConfig = ` 185 No configuration files found! 186 187 Apply requires configuration to be present. Applying without a configuration 188 would mark everything for destruction, which is normally not what is desired. 189 If you would like to destroy everything, please run 'terraform destroy' instead 190 which does not require any configuration files. 191 `