github.com/kjmkznr/terraform@v0.5.2-0.20180216194316-1d0f5fdac99e/terraform/node_resource_apply.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/terraform/config" 7 ) 8 9 // NodeApplyableResource represents a resource that is "applyable": 10 // it is ready to be applied and is represented by a diff. 11 type NodeApplyableResource struct { 12 *NodeAbstractResource 13 } 14 15 // GraphNodeCreator 16 func (n *NodeApplyableResource) CreateAddr() *ResourceAddress { 17 return n.NodeAbstractResource.Addr 18 } 19 20 // GraphNodeReferencer, overriding NodeAbstractResource 21 func (n *NodeApplyableResource) References() []string { 22 result := n.NodeAbstractResource.References() 23 24 // The "apply" side of a resource generally also depends on the 25 // destruction of its dependencies as well. For example, if a LB 26 // references a set of VMs with ${vm.foo.*.id}, then we must wait for 27 // the destruction so we get the newly updated list of VMs. 28 // 29 // The exception here is CBD. When CBD is set, we don't do this since 30 // it would create a cycle. By not creating a cycle, we require two 31 // applies since the first apply the creation step will use the OLD 32 // values (pre-destroy) and the second step will update. 33 // 34 // This is how Terraform behaved with "legacy" graphs (TF <= 0.7.x). 35 // We mimic that behavior here now and can improve upon it in the future. 36 // 37 // This behavior is tested in graph_build_apply_test.go to test ordering. 38 cbd := n.Config != nil && n.Config.Lifecycle.CreateBeforeDestroy 39 if !cbd { 40 // The "apply" side of a resource always depends on the destruction 41 // of all its dependencies in addition to the creation. 42 for _, v := range result { 43 result = append(result, v+".destroy") 44 } 45 } 46 47 return result 48 } 49 50 // GraphNodeEvalable 51 func (n *NodeApplyableResource) EvalTree() EvalNode { 52 addr := n.NodeAbstractResource.Addr 53 54 // stateId is the ID to put into the state 55 stateId := addr.stateId() 56 57 // Build the instance info. More of this will be populated during eval 58 info := &InstanceInfo{ 59 Id: stateId, 60 Type: addr.Type, 61 } 62 63 // Build the resource for eval 64 resource := &Resource{ 65 Name: addr.Name, 66 Type: addr.Type, 67 CountIndex: addr.Index, 68 } 69 if resource.CountIndex < 0 { 70 resource.CountIndex = 0 71 } 72 73 // Determine the dependencies for the state. 74 stateDeps := n.StateReferences() 75 76 // Eval info is different depending on what kind of resource this is 77 switch n.Config.Mode { 78 case config.ManagedResourceMode: 79 return n.evalTreeManagedResource( 80 stateId, info, resource, stateDeps, 81 ) 82 case config.DataResourceMode: 83 return n.evalTreeDataResource( 84 stateId, info, resource, stateDeps) 85 default: 86 panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode)) 87 } 88 } 89 90 func (n *NodeApplyableResource) evalTreeDataResource( 91 stateId string, info *InstanceInfo, 92 resource *Resource, stateDeps []string) EvalNode { 93 var provider ResourceProvider 94 var config *ResourceConfig 95 var diff *InstanceDiff 96 var state *InstanceState 97 98 return &EvalSequence{ 99 Nodes: []EvalNode{ 100 // Build the instance info 101 &EvalInstanceInfo{ 102 Info: info, 103 }, 104 105 // Get the saved diff for apply 106 &EvalReadDiff{ 107 Name: stateId, 108 Diff: &diff, 109 }, 110 111 // Stop here if we don't actually have a diff 112 &EvalIf{ 113 If: func(ctx EvalContext) (bool, error) { 114 if diff == nil { 115 return true, EvalEarlyExitError{} 116 } 117 118 if diff.GetAttributesLen() == 0 { 119 return true, EvalEarlyExitError{} 120 } 121 122 return true, nil 123 }, 124 Then: EvalNoop{}, 125 }, 126 127 // Normally we interpolate count as a preparation step before 128 // a DynamicExpand, but an apply graph has pre-expanded nodes 129 // and so the count would otherwise never be interpolated. 130 // 131 // This is redundant when there are multiple instances created 132 // from the same config (count > 1) but harmless since the 133 // underlying structures have mutexes to make this concurrency-safe. 134 // 135 // In most cases this isn't actually needed because we dealt with 136 // all of the counts during the plan walk, but we do it here 137 // for completeness because other code assumes that the 138 // final count is always available during interpolation. 139 // 140 // Here we are just populating the interpolated value in-place 141 // inside this RawConfig object, like we would in 142 // NodeAbstractCountResource. 143 &EvalInterpolate{Config: n.Config.RawCount}, 144 145 // We need to re-interpolate the config here, rather than 146 // just using the diff's values directly, because we've 147 // potentially learned more variable values during the 148 // apply pass that weren't known when the diff was produced. 149 &EvalInterpolate{ 150 Config: n.Config.RawConfig.Copy(), 151 Resource: resource, 152 Output: &config, 153 }, 154 155 &EvalGetProvider{ 156 Name: n.ResolvedProvider, 157 Output: &provider, 158 }, 159 160 // Make a new diff with our newly-interpolated config. 161 &EvalReadDataDiff{ 162 Info: info, 163 Config: &config, 164 Previous: &diff, 165 Provider: &provider, 166 Output: &diff, 167 }, 168 169 &EvalReadDataApply{ 170 Info: info, 171 Diff: &diff, 172 Provider: &provider, 173 Output: &state, 174 }, 175 176 &EvalWriteState{ 177 Name: stateId, 178 ResourceType: n.Config.Type, 179 Provider: n.ResolvedProvider, 180 Dependencies: stateDeps, 181 State: &state, 182 }, 183 184 // Clear the diff now that we've applied it, so 185 // later nodes won't see a diff that's now a no-op. 186 &EvalWriteDiff{ 187 Name: stateId, 188 Diff: nil, 189 }, 190 191 &EvalUpdateStateHook{}, 192 }, 193 } 194 } 195 196 func (n *NodeApplyableResource) evalTreeManagedResource( 197 stateId string, info *InstanceInfo, 198 resource *Resource, stateDeps []string) EvalNode { 199 // Declare a bunch of variables that are used for state during 200 // evaluation. Most of this are written to by-address below. 201 var provider ResourceProvider 202 var diff, diffApply *InstanceDiff 203 var state *InstanceState 204 var resourceConfig *ResourceConfig 205 var err error 206 var createNew bool 207 var createBeforeDestroyEnabled bool 208 209 return &EvalSequence{ 210 Nodes: []EvalNode{ 211 // Build the instance info 212 &EvalInstanceInfo{ 213 Info: info, 214 }, 215 216 // Get the saved diff for apply 217 &EvalReadDiff{ 218 Name: stateId, 219 Diff: &diffApply, 220 }, 221 222 // We don't want to do any destroys 223 &EvalIf{ 224 If: func(ctx EvalContext) (bool, error) { 225 if diffApply == nil { 226 return true, EvalEarlyExitError{} 227 } 228 229 if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 { 230 return true, EvalEarlyExitError{} 231 } 232 233 diffApply.SetDestroy(false) 234 return true, nil 235 }, 236 Then: EvalNoop{}, 237 }, 238 239 &EvalIf{ 240 If: func(ctx EvalContext) (bool, error) { 241 destroy := false 242 if diffApply != nil { 243 destroy = diffApply.GetDestroy() || diffApply.RequiresNew() 244 } 245 246 createBeforeDestroyEnabled = 247 n.Config.Lifecycle.CreateBeforeDestroy && 248 destroy 249 250 return createBeforeDestroyEnabled, nil 251 }, 252 Then: &EvalDeposeState{ 253 Name: stateId, 254 }, 255 }, 256 257 // Normally we interpolate count as a preparation step before 258 // a DynamicExpand, but an apply graph has pre-expanded nodes 259 // and so the count would otherwise never be interpolated. 260 // 261 // This is redundant when there are multiple instances created 262 // from the same config (count > 1) but harmless since the 263 // underlying structures have mutexes to make this concurrency-safe. 264 // 265 // In most cases this isn't actually needed because we dealt with 266 // all of the counts during the plan walk, but we need to do this 267 // in order to support interpolation of resource counts from 268 // apply-time-interpolated expressions, such as those in 269 // "provisioner" blocks. 270 // 271 // Here we are just populating the interpolated value in-place 272 // inside this RawConfig object, like we would in 273 // NodeAbstractCountResource. 274 &EvalInterpolate{Config: n.Config.RawCount}, 275 276 &EvalInterpolate{ 277 Config: n.Config.RawConfig.Copy(), 278 Resource: resource, 279 Output: &resourceConfig, 280 }, 281 &EvalGetProvider{ 282 Name: n.ResolvedProvider, 283 Output: &provider, 284 }, 285 &EvalReadState{ 286 Name: stateId, 287 Output: &state, 288 }, 289 // Re-run validation to catch any errors we missed, e.g. type 290 // mismatches on computed values. 291 &EvalValidateResource{ 292 Provider: &provider, 293 Config: &resourceConfig, 294 ResourceName: n.Config.Name, 295 ResourceType: n.Config.Type, 296 ResourceMode: n.Config.Mode, 297 IgnoreWarnings: true, 298 }, 299 &EvalDiff{ 300 Info: info, 301 Config: &resourceConfig, 302 Resource: n.Config, 303 Provider: &provider, 304 Diff: &diffApply, 305 State: &state, 306 OutputDiff: &diffApply, 307 }, 308 309 // Get the saved diff 310 &EvalReadDiff{ 311 Name: stateId, 312 Diff: &diff, 313 }, 314 315 // Compare the diffs 316 &EvalCompareDiff{ 317 Info: info, 318 One: &diff, 319 Two: &diffApply, 320 }, 321 322 &EvalGetProvider{ 323 Name: n.ResolvedProvider, 324 Output: &provider, 325 }, 326 &EvalReadState{ 327 Name: stateId, 328 Output: &state, 329 }, 330 // Call pre-apply hook 331 &EvalApplyPre{ 332 Info: info, 333 State: &state, 334 Diff: &diffApply, 335 }, 336 &EvalApply{ 337 Info: info, 338 State: &state, 339 Diff: &diffApply, 340 Provider: &provider, 341 Output: &state, 342 Error: &err, 343 CreateNew: &createNew, 344 }, 345 &EvalWriteState{ 346 Name: stateId, 347 ResourceType: n.Config.Type, 348 Provider: n.ResolvedProvider, 349 Dependencies: stateDeps, 350 State: &state, 351 }, 352 &EvalApplyProvisioners{ 353 Info: info, 354 State: &state, 355 Resource: n.Config, 356 InterpResource: resource, 357 CreateNew: &createNew, 358 Error: &err, 359 When: config.ProvisionerWhenCreate, 360 }, 361 &EvalIf{ 362 If: func(ctx EvalContext) (bool, error) { 363 return createBeforeDestroyEnabled && err != nil, nil 364 }, 365 Then: &EvalUndeposeState{ 366 Name: stateId, 367 State: &state, 368 }, 369 Else: &EvalWriteState{ 370 Name: stateId, 371 ResourceType: n.Config.Type, 372 Provider: n.ResolvedProvider, 373 Dependencies: stateDeps, 374 State: &state, 375 }, 376 }, 377 378 // We clear the diff out here so that future nodes 379 // don't see a diff that is already complete. There 380 // is no longer a diff! 381 &EvalWriteDiff{ 382 Name: stateId, 383 Diff: nil, 384 }, 385 386 &EvalApplyPost{ 387 Info: info, 388 State: &state, 389 Error: &err, 390 }, 391 &EvalUpdateStateHook{}, 392 }, 393 } 394 }