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