github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/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 "github.com/hashicorp/terraform/command/clistate" 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 lockCtx, cancel := context.WithTimeout(ctx, op.StateLockTimeout) 56 defer cancel() 57 58 lockInfo := state.NewLockInfo() 59 lockInfo.Operation = op.Type.String() 60 lockID, err := clistate.Lock(lockCtx, opState, lockInfo, b.CLI, b.Colorize()) 61 if err != nil { 62 runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err) 63 return 64 } 65 66 defer func() { 67 if err := clistate.Unlock(opState, lockID, b.CLI, b.Colorize()); err != nil { 68 runningOp.Err = multierror.Append(runningOp.Err, err) 69 } 70 }() 71 } 72 73 // Setup the state 74 runningOp.State = tfCtx.State() 75 76 // If we weren't given a plan, then we refresh/plan 77 if op.Plan == nil { 78 // If we're refreshing before apply, perform that 79 if op.PlanRefresh { 80 log.Printf("[INFO] backend/local: apply calling Refresh") 81 _, err := tfCtx.Refresh() 82 if err != nil { 83 runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", err) 84 return 85 } 86 } 87 88 // Perform the plan 89 log.Printf("[INFO] backend/local: apply calling Plan") 90 if _, err := tfCtx.Plan(); err != nil { 91 runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", err) 92 return 93 } 94 } 95 96 // Setup our hook for continuous state updates 97 stateHook.State = opState 98 99 // Start the apply in a goroutine so that we can be interrupted. 100 var applyState *terraform.State 101 var applyErr error 102 doneCh := make(chan struct{}) 103 go func() { 104 defer close(doneCh) 105 _, applyErr = tfCtx.Apply() 106 // we always want the state, even if apply failed 107 applyState = tfCtx.State() 108 109 /* 110 // Record any shadow errors for later 111 if err := ctx.ShadowError(); err != nil { 112 shadowErr = multierror.Append(shadowErr, multierror.Prefix( 113 err, "apply operation:")) 114 } 115 */ 116 }() 117 118 // Wait for the apply to finish or for us to be interrupted so 119 // we can handle it properly. 120 err = nil 121 select { 122 case <-ctx.Done(): 123 if b.CLI != nil { 124 b.CLI.Output("stopping apply operation...") 125 } 126 127 // Stop execution 128 go tfCtx.Stop() 129 130 // Wait for completion still 131 <-doneCh 132 case <-doneCh: 133 } 134 135 // Store the final state 136 runningOp.State = applyState 137 138 // Persist the state 139 if err := opState.WriteState(applyState); err != nil { 140 runningOp.Err = fmt.Errorf("Failed to save state: %s", err) 141 return 142 } 143 if err := opState.PersistState(); err != nil { 144 runningOp.Err = fmt.Errorf("Failed to save state: %s", err) 145 return 146 } 147 148 if applyErr != nil { 149 runningOp.Err = fmt.Errorf( 150 "Error applying plan:\n\n"+ 151 "%s\n\n"+ 152 "Terraform does not automatically rollback in the face of errors.\n"+ 153 "Instead, your Terraform state file has been partially updated with\n"+ 154 "any resources that successfully completed. Please address the error\n"+ 155 "above and apply again to incrementally change your infrastructure.", 156 multierror.Flatten(applyErr)) 157 return 158 } 159 160 // If we have a UI, output the results 161 if b.CLI != nil { 162 if op.Destroy { 163 b.CLI.Output(b.Colorize().Color(fmt.Sprintf( 164 "[reset][bold][green]\n"+ 165 "Destroy complete! Resources: %d destroyed.", 166 countHook.Removed))) 167 } else { 168 b.CLI.Output(b.Colorize().Color(fmt.Sprintf( 169 "[reset][bold][green]\n"+ 170 "Apply complete! Resources: %d added, %d changed, %d destroyed.", 171 countHook.Added, 172 countHook.Changed, 173 countHook.Removed))) 174 } 175 176 if countHook.Added > 0 || countHook.Changed > 0 { 177 b.CLI.Output(b.Colorize().Color(fmt.Sprintf( 178 "[reset]\n"+ 179 "The state of your infrastructure has been saved to the path\n"+ 180 "below. This state is required to modify and destroy your\n"+ 181 "infrastructure, so keep it safe. To inspect the complete state\n"+ 182 "use the `terraform show` command.\n\n"+ 183 "State path: %s", 184 b.StateOutPath))) 185 } 186 } 187 } 188 189 const applyErrNoConfig = ` 190 No configuration files found! 191 192 Apply requires configuration to be present. Applying without a configuration 193 would mark everything for destruction, which is normally not what is desired. 194 If you would like to destroy everything, please run 'terraform destroy' instead 195 which does not require any configuration files. 196 `