github.com/tompao/terraform@v0.6.10-0.20180215233341-e41b29d0961b/backend/local/backend_apply.go (about) 1 package local 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "log" 9 "strings" 10 11 "github.com/hashicorp/errwrap" 12 "github.com/hashicorp/go-multierror" 13 "github.com/hashicorp/terraform/backend" 14 "github.com/hashicorp/terraform/command/clistate" 15 "github.com/hashicorp/terraform/command/format" 16 "github.com/hashicorp/terraform/config/module" 17 "github.com/hashicorp/terraform/state" 18 "github.com/hashicorp/terraform/terraform" 19 ) 20 21 func (b *Local) opApply( 22 stopCtx context.Context, 23 cancelCtx context.Context, 24 op *backend.Operation, 25 runningOp *backend.RunningOperation) { 26 log.Printf("[INFO] backend/local: starting Apply operation") 27 28 // If we have a nil module at this point, then set it to an empty tree 29 // to avoid any potential crashes. 30 if op.Plan == nil && op.Module == nil && !op.Destroy { 31 runningOp.Err = fmt.Errorf(strings.TrimSpace(applyErrNoConfig)) 32 return 33 } 34 35 // If we have a nil module at this point, then set it to an empty tree 36 // to avoid any potential crashes. 37 if op.Module == nil { 38 op.Module = module.NewEmptyTree() 39 } 40 41 // Setup our count hook that keeps track of resource changes 42 countHook := new(CountHook) 43 stateHook := new(StateHook) 44 if b.ContextOpts == nil { 45 b.ContextOpts = new(terraform.ContextOpts) 46 } 47 old := b.ContextOpts.Hooks 48 defer func() { b.ContextOpts.Hooks = old }() 49 b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook, stateHook) 50 51 // Get our context 52 tfCtx, opState, err := b.context(op) 53 if err != nil { 54 runningOp.Err = err 55 return 56 } 57 58 if op.LockState { 59 lockCtx, cancel := context.WithTimeout(stopCtx, op.StateLockTimeout) 60 defer cancel() 61 62 lockInfo := state.NewLockInfo() 63 lockInfo.Operation = op.Type.String() 64 lockID, err := clistate.Lock(lockCtx, opState, lockInfo, b.CLI, b.Colorize()) 65 if err != nil { 66 runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err) 67 return 68 } 69 70 defer func() { 71 if err := clistate.Unlock(opState, lockID, b.CLI, b.Colorize()); err != nil { 72 runningOp.Err = multierror.Append(runningOp.Err, err) 73 } 74 }() 75 } 76 77 // Setup the state 78 runningOp.State = tfCtx.State() 79 80 // If we weren't given a plan, then we refresh/plan 81 if op.Plan == nil { 82 // If we're refreshing before apply, perform that 83 if op.PlanRefresh { 84 log.Printf("[INFO] backend/local: apply calling Refresh") 85 _, err := tfCtx.Refresh() 86 if err != nil { 87 runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", err) 88 return 89 } 90 } 91 92 // Perform the plan 93 log.Printf("[INFO] backend/local: apply calling Plan") 94 plan, err := tfCtx.Plan() 95 if err != nil { 96 runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", err) 97 return 98 } 99 100 dispPlan := format.NewPlan(plan) 101 trivialPlan := dispPlan.Empty() 102 hasUI := op.UIOut != nil && op.UIIn != nil 103 mustConfirm := hasUI && ((op.Destroy && !op.DestroyForce) || (!op.Destroy && !op.AutoApprove && !trivialPlan)) 104 if mustConfirm { 105 var desc, query string 106 if op.Destroy { 107 // Default destroy message 108 desc = "Terraform will destroy all your managed infrastructure, as shown above.\n" + 109 "There is no undo. Only 'yes' will be accepted to confirm." 110 query = "Do you really want to destroy?" 111 } else { 112 desc = "Terraform will perform the actions described above.\n" + 113 "Only 'yes' will be accepted to approve." 114 query = "Do you want to perform these actions?" 115 } 116 117 if !trivialPlan { 118 // Display the plan of what we are going to apply/destroy. 119 b.renderPlan(dispPlan) 120 b.CLI.Output("") 121 } 122 123 v, err := op.UIIn.Input(&terraform.InputOpts{ 124 Id: "approve", 125 Query: query, 126 Description: desc, 127 }) 128 if err != nil { 129 runningOp.Err = errwrap.Wrapf("Error asking for approval: {{err}}", err) 130 return 131 } 132 if v != "yes" { 133 if op.Destroy { 134 runningOp.Err = errors.New("Destroy cancelled.") 135 } else { 136 runningOp.Err = errors.New("Apply cancelled.") 137 } 138 return 139 } 140 } 141 } 142 143 // Setup our hook for continuous state updates 144 stateHook.State = opState 145 146 // Start the apply in a goroutine so that we can be interrupted. 147 var applyState *terraform.State 148 var applyErr error 149 doneCh := make(chan struct{}) 150 go func() { 151 defer close(doneCh) 152 _, applyErr = tfCtx.Apply() 153 // we always want the state, even if apply failed 154 applyState = tfCtx.State() 155 }() 156 157 if b.opWait(doneCh, stopCtx, cancelCtx, tfCtx, opState) { 158 return 159 } 160 161 // Store the final state 162 runningOp.State = applyState 163 164 // Persist the state 165 if err := opState.WriteState(applyState); err != nil { 166 runningOp.Err = b.backupStateForError(applyState, err) 167 return 168 } 169 if err := opState.PersistState(); err != nil { 170 runningOp.Err = b.backupStateForError(applyState, err) 171 return 172 } 173 174 if applyErr != nil { 175 runningOp.Err = fmt.Errorf( 176 "Error applying plan:\n\n"+ 177 "%s\n\n"+ 178 "Terraform does not automatically rollback in the face of errors.\n"+ 179 "Instead, your Terraform state file has been partially updated with\n"+ 180 "any resources that successfully completed. Please address the error\n"+ 181 "above and apply again to incrementally change your infrastructure.", 182 multierror.Flatten(applyErr)) 183 return 184 } 185 186 // If we have a UI, output the results 187 if b.CLI != nil { 188 if op.Destroy { 189 b.CLI.Output(b.Colorize().Color(fmt.Sprintf( 190 "[reset][bold][green]\n"+ 191 "Destroy complete! Resources: %d destroyed.", 192 countHook.Removed))) 193 } else { 194 b.CLI.Output(b.Colorize().Color(fmt.Sprintf( 195 "[reset][bold][green]\n"+ 196 "Apply complete! Resources: %d added, %d changed, %d destroyed.", 197 countHook.Added, 198 countHook.Changed, 199 countHook.Removed))) 200 } 201 202 // only show the state file help message if the state is local. 203 if (countHook.Added > 0 || countHook.Changed > 0) && b.StateOutPath != "" { 204 b.CLI.Output(b.Colorize().Color(fmt.Sprintf( 205 "[reset]\n"+ 206 "The state of your infrastructure has been saved to the path\n"+ 207 "below. This state is required to modify and destroy your\n"+ 208 "infrastructure, so keep it safe. To inspect the complete state\n"+ 209 "use the `terraform show` command.\n\n"+ 210 "State path: %s", 211 b.StateOutPath))) 212 } 213 } 214 } 215 216 // backupStateForError is called in a scenario where we're unable to persist the 217 // state for some reason, and will attempt to save a backup copy of the state 218 // to local disk to help the user recover. This is a "last ditch effort" sort 219 // of thing, so we really don't want to end up in this codepath; we should do 220 // everything we possibly can to get the state saved _somewhere_. 221 func (b *Local) backupStateForError(applyState *terraform.State, err error) error { 222 b.CLI.Error(fmt.Sprintf("Failed to save state: %s\n", err)) 223 224 local := &state.LocalState{Path: "errored.tfstate"} 225 writeErr := local.WriteState(applyState) 226 if writeErr != nil { 227 b.CLI.Error(fmt.Sprintf( 228 "Also failed to create local state file for recovery: %s\n\n", writeErr, 229 )) 230 // To avoid leaving the user with no state at all, our last resort 231 // is to print the JSON state out onto the terminal. This is an awful 232 // UX, so we should definitely avoid doing this if at all possible, 233 // but at least the user has _some_ path to recover if we end up 234 // here for some reason. 235 stateBuf := new(bytes.Buffer) 236 jsonErr := terraform.WriteState(applyState, stateBuf) 237 if jsonErr != nil { 238 b.CLI.Error(fmt.Sprintf( 239 "Also failed to JSON-serialize the state to print it: %s\n\n", jsonErr, 240 )) 241 return errors.New(stateWriteFatalError) 242 } 243 244 b.CLI.Output(stateBuf.String()) 245 246 return errors.New(stateWriteConsoleFallbackError) 247 } 248 249 return errors.New(stateWriteBackedUpError) 250 } 251 252 const applyErrNoConfig = ` 253 No configuration files found! 254 255 Apply requires configuration to be present. Applying without a configuration 256 would mark everything for destruction, which is normally not what is desired. 257 If you would like to destroy everything, please run 'terraform destroy' instead 258 which does not require any configuration files. 259 ` 260 261 const stateWriteBackedUpError = `Failed to persist state to backend. 262 263 The error shown above has prevented Terraform from writing the updated state 264 to the configured backend. To allow for recovery, the state has been written 265 to the file "errored.tfstate" in the current working directory. 266 267 Running "terraform apply" again at this point will create a forked state, 268 making it harder to recover. 269 270 To retry writing this state, use the following command: 271 terraform state push errored.tfstate 272 ` 273 274 const stateWriteConsoleFallbackError = `Failed to persist state to backend. 275 276 The errors shown above prevented Terraform from writing the updated state to 277 the configured backend and from creating a local backup file. As a fallback, 278 the raw state data is printed above as a JSON object. 279 280 To retry writing this state, copy the state data (from the first { to the 281 last } inclusive) and save it into a local file called errored.tfstate, then 282 run the following command: 283 terraform state push errored.tfstate 284 ` 285 286 const stateWriteFatalError = `Failed to save state after apply. 287 288 A catastrophic error has prevented Terraform from persisting the state file 289 or creating a backup. Unfortunately this means that the record of any resources 290 created during this apply has been lost, and such resources may exist outside 291 of Terraform's management. 292 293 For resources that support import, it is possible to recover by manually 294 importing each resource using its id from the target system. 295 296 This is a serious bug in Terraform and should be reported. 297 ` 298 299 const earlyStateWriteErrorFmt = `Error saving current state: %s 300 301 Terraform encountered an error attempting to save the state before canceling 302 the current operation. Once the operation is complete another attempt will be 303 made to save the final state. 304 `