github.com/chentex/terraform@v0.11.2-0.20171208003256-252e8145842e/terraform/eval_apply.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 "strconv" 7 8 "github.com/hashicorp/go-multierror" 9 "github.com/hashicorp/terraform/config" 10 ) 11 12 // EvalApply is an EvalNode implementation that writes the diff to 13 // the full diff. 14 type EvalApply struct { 15 Info *InstanceInfo 16 State **InstanceState 17 Diff **InstanceDiff 18 Provider *ResourceProvider 19 Output **InstanceState 20 CreateNew *bool 21 Error *error 22 } 23 24 // TODO: test 25 func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) { 26 diff := *n.Diff 27 provider := *n.Provider 28 state := *n.State 29 30 // If we have no diff, we have nothing to do! 31 if diff.Empty() { 32 log.Printf( 33 "[DEBUG] apply: %s: diff is empty, doing nothing.", n.Info.Id) 34 return nil, nil 35 } 36 37 // Remove any output values from the diff 38 for k, ad := range diff.CopyAttributes() { 39 if ad.Type == DiffAttrOutput { 40 diff.DelAttribute(k) 41 } 42 } 43 44 // If the state is nil, make it non-nil 45 if state == nil { 46 state = new(InstanceState) 47 } 48 state.init() 49 50 // Flag if we're creating a new instance 51 if n.CreateNew != nil { 52 *n.CreateNew = state.ID == "" && !diff.GetDestroy() || diff.RequiresNew() 53 } 54 55 // With the completed diff, apply! 56 log.Printf("[DEBUG] apply: %s: executing Apply", n.Info.Id) 57 state, err := provider.Apply(n.Info, state, diff) 58 if state == nil { 59 state = new(InstanceState) 60 } 61 state.init() 62 63 // Force the "id" attribute to be our ID 64 if state.ID != "" { 65 state.Attributes["id"] = state.ID 66 } 67 68 // If the value is the unknown variable value, then it is an error. 69 // In this case we record the error and remove it from the state 70 for ak, av := range state.Attributes { 71 if av == config.UnknownVariableValue { 72 err = multierror.Append(err, fmt.Errorf( 73 "Attribute with unknown value: %s", ak)) 74 delete(state.Attributes, ak) 75 } 76 } 77 78 // Write the final state 79 if n.Output != nil { 80 *n.Output = state 81 } 82 83 // If there are no errors, then we append it to our output error 84 // if we have one, otherwise we just output it. 85 if err != nil { 86 if n.Error != nil { 87 helpfulErr := fmt.Errorf("%s: %s", n.Info.Id, err.Error()) 88 *n.Error = multierror.Append(*n.Error, helpfulErr) 89 } else { 90 return nil, err 91 } 92 } 93 94 return nil, nil 95 } 96 97 // EvalApplyPre is an EvalNode implementation that does the pre-Apply work 98 type EvalApplyPre struct { 99 Info *InstanceInfo 100 State **InstanceState 101 Diff **InstanceDiff 102 } 103 104 // TODO: test 105 func (n *EvalApplyPre) Eval(ctx EvalContext) (interface{}, error) { 106 state := *n.State 107 diff := *n.Diff 108 109 // If the state is nil, make it non-nil 110 if state == nil { 111 state = new(InstanceState) 112 } 113 state.init() 114 115 if resourceHasUserVisibleApply(n.Info) { 116 // Call post-apply hook 117 err := ctx.Hook(func(h Hook) (HookAction, error) { 118 return h.PreApply(n.Info, state, diff) 119 }) 120 if err != nil { 121 return nil, err 122 } 123 } 124 125 return nil, nil 126 } 127 128 // EvalApplyPost is an EvalNode implementation that does the post-Apply work 129 type EvalApplyPost struct { 130 Info *InstanceInfo 131 State **InstanceState 132 Error *error 133 } 134 135 // TODO: test 136 func (n *EvalApplyPost) Eval(ctx EvalContext) (interface{}, error) { 137 state := *n.State 138 139 if resourceHasUserVisibleApply(n.Info) { 140 // Call post-apply hook 141 err := ctx.Hook(func(h Hook) (HookAction, error) { 142 return h.PostApply(n.Info, state, *n.Error) 143 }) 144 if err != nil { 145 return nil, err 146 } 147 } 148 149 return nil, *n.Error 150 } 151 152 // resourceHasUserVisibleApply returns true if the given resource is one where 153 // apply actions should be exposed to the user. 154 // 155 // Certain resources do apply actions only as an implementation detail, so 156 // these should not be advertised to code outside of this package. 157 func resourceHasUserVisibleApply(info *InstanceInfo) bool { 158 addr := info.ResourceAddress() 159 160 // Only managed resources have user-visible apply actions. 161 // In particular, this excludes data resources since we "apply" these 162 // only as an implementation detail of removing them from state when 163 // they are destroyed. (When reading, they don't get here at all because 164 // we present them as "Refresh" actions.) 165 return addr.Mode == config.ManagedResourceMode 166 } 167 168 // EvalApplyProvisioners is an EvalNode implementation that executes 169 // the provisioners for a resource. 170 // 171 // TODO(mitchellh): This should probably be split up into a more fine-grained 172 // ApplyProvisioner (single) that is looped over. 173 type EvalApplyProvisioners struct { 174 Info *InstanceInfo 175 State **InstanceState 176 Resource *config.Resource 177 InterpResource *Resource 178 CreateNew *bool 179 Error *error 180 181 // When is the type of provisioner to run at this point 182 When config.ProvisionerWhen 183 } 184 185 // TODO: test 186 func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) { 187 state := *n.State 188 189 if n.CreateNew != nil && !*n.CreateNew { 190 // If we're not creating a new resource, then don't run provisioners 191 return nil, nil 192 } 193 194 provs := n.filterProvisioners() 195 if len(provs) == 0 { 196 // We have no provisioners, so don't do anything 197 return nil, nil 198 } 199 200 // taint tells us whether to enable tainting. 201 taint := n.When == config.ProvisionerWhenCreate 202 203 if n.Error != nil && *n.Error != nil { 204 if taint { 205 state.Tainted = true 206 } 207 208 // We're already tainted, so just return out 209 return nil, nil 210 } 211 212 { 213 // Call pre hook 214 err := ctx.Hook(func(h Hook) (HookAction, error) { 215 return h.PreProvisionResource(n.Info, state) 216 }) 217 if err != nil { 218 return nil, err 219 } 220 } 221 222 // If there are no errors, then we append it to our output error 223 // if we have one, otherwise we just output it. 224 err := n.apply(ctx, provs) 225 if err != nil { 226 if taint { 227 state.Tainted = true 228 } 229 230 if n.Error != nil { 231 *n.Error = multierror.Append(*n.Error, err) 232 } else { 233 return nil, err 234 } 235 } 236 237 { 238 // Call post hook 239 err := ctx.Hook(func(h Hook) (HookAction, error) { 240 return h.PostProvisionResource(n.Info, state) 241 }) 242 if err != nil { 243 return nil, err 244 } 245 } 246 247 return nil, nil 248 } 249 250 // filterProvisioners filters the provisioners on the resource to only 251 // the provisioners specified by the "when" option. 252 func (n *EvalApplyProvisioners) filterProvisioners() []*config.Provisioner { 253 // Fast path the zero case 254 if n.Resource == nil { 255 return nil 256 } 257 258 if len(n.Resource.Provisioners) == 0 { 259 return nil 260 } 261 262 result := make([]*config.Provisioner, 0, len(n.Resource.Provisioners)) 263 for _, p := range n.Resource.Provisioners { 264 if p.When == n.When { 265 result = append(result, p) 266 } 267 } 268 269 return result 270 } 271 272 func (n *EvalApplyProvisioners) apply(ctx EvalContext, provs []*config.Provisioner) error { 273 state := *n.State 274 275 // Store the original connection info, restore later 276 origConnInfo := state.Ephemeral.ConnInfo 277 defer func() { 278 state.Ephemeral.ConnInfo = origConnInfo 279 }() 280 281 for _, prov := range provs { 282 // Get the provisioner 283 provisioner := ctx.Provisioner(prov.Type) 284 285 // Interpolate the provisioner config 286 provConfig, err := ctx.Interpolate(prov.RawConfig.Copy(), n.InterpResource) 287 if err != nil { 288 return err 289 } 290 291 // Interpolate the conn info, since it may contain variables 292 connInfo, err := ctx.Interpolate(prov.ConnInfo.Copy(), n.InterpResource) 293 if err != nil { 294 return err 295 } 296 297 // Merge the connection information 298 overlay := make(map[string]string) 299 if origConnInfo != nil { 300 for k, v := range origConnInfo { 301 overlay[k] = v 302 } 303 } 304 for k, v := range connInfo.Config { 305 switch vt := v.(type) { 306 case string: 307 overlay[k] = vt 308 case int64: 309 overlay[k] = strconv.FormatInt(vt, 10) 310 case int32: 311 overlay[k] = strconv.FormatInt(int64(vt), 10) 312 case int: 313 overlay[k] = strconv.FormatInt(int64(vt), 10) 314 case float32: 315 overlay[k] = strconv.FormatFloat(float64(vt), 'f', 3, 32) 316 case float64: 317 overlay[k] = strconv.FormatFloat(vt, 'f', 3, 64) 318 case bool: 319 overlay[k] = strconv.FormatBool(vt) 320 default: 321 overlay[k] = fmt.Sprintf("%v", vt) 322 } 323 } 324 state.Ephemeral.ConnInfo = overlay 325 326 { 327 // Call pre hook 328 err := ctx.Hook(func(h Hook) (HookAction, error) { 329 return h.PreProvision(n.Info, prov.Type) 330 }) 331 if err != nil { 332 return err 333 } 334 } 335 336 // The output function 337 outputFn := func(msg string) { 338 ctx.Hook(func(h Hook) (HookAction, error) { 339 h.ProvisionOutput(n.Info, prov.Type, msg) 340 return HookActionContinue, nil 341 }) 342 } 343 344 // Invoke the Provisioner 345 output := CallbackUIOutput{OutputFn: outputFn} 346 applyErr := provisioner.Apply(&output, state, provConfig) 347 348 // Call post hook 349 hookErr := ctx.Hook(func(h Hook) (HookAction, error) { 350 return h.PostProvision(n.Info, prov.Type, applyErr) 351 }) 352 353 // Handle the error before we deal with the hook 354 if applyErr != nil { 355 // Determine failure behavior 356 switch prov.OnFailure { 357 case config.ProvisionerOnFailureContinue: 358 log.Printf( 359 "[INFO] apply: %s [%s]: error during provision, continue requested", 360 n.Info.Id, prov.Type) 361 362 case config.ProvisionerOnFailureFail: 363 return applyErr 364 } 365 } 366 367 // Deal with the hook 368 if hookErr != nil { 369 return hookErr 370 } 371 } 372 373 return nil 374 375 }