github.com/magodo/terraform@v0.11.12-beta1/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{ 144 Config: n.Config.RawCount, 145 ContinueOnErr: true, 146 }, 147 148 // We need to re-interpolate the config here, rather than 149 // just using the diff's values directly, because we've 150 // potentially learned more variable values during the 151 // apply pass that weren't known when the diff was produced. 152 &EvalInterpolate{ 153 Config: n.Config.RawConfig.Copy(), 154 Resource: resource, 155 Output: &config, 156 }, 157 158 &EvalGetProvider{ 159 Name: n.ResolvedProvider, 160 Output: &provider, 161 }, 162 163 // Make a new diff with our newly-interpolated config. 164 &EvalReadDataDiff{ 165 Info: info, 166 Config: &config, 167 Previous: &diff, 168 Provider: &provider, 169 Output: &diff, 170 }, 171 172 &EvalReadDataApply{ 173 Info: info, 174 Diff: &diff, 175 Provider: &provider, 176 Output: &state, 177 }, 178 179 &EvalWriteState{ 180 Name: stateId, 181 ResourceType: n.Config.Type, 182 Provider: n.ResolvedProvider, 183 Dependencies: stateDeps, 184 State: &state, 185 }, 186 187 // Clear the diff now that we've applied it, so 188 // later nodes won't see a diff that's now a no-op. 189 &EvalWriteDiff{ 190 Name: stateId, 191 Diff: nil, 192 }, 193 194 &EvalUpdateStateHook{}, 195 }, 196 } 197 } 198 199 func (n *NodeApplyableResource) evalTreeManagedResource( 200 stateId string, info *InstanceInfo, 201 resource *Resource, stateDeps []string) EvalNode { 202 // Declare a bunch of variables that are used for state during 203 // evaluation. Most of this are written to by-address below. 204 var provider ResourceProvider 205 var diff, diffApply *InstanceDiff 206 var state *InstanceState 207 var resourceConfig *ResourceConfig 208 var err error 209 var createNew bool 210 var createBeforeDestroyEnabled bool 211 212 return &EvalSequence{ 213 Nodes: []EvalNode{ 214 // Build the instance info 215 &EvalInstanceInfo{ 216 Info: info, 217 }, 218 219 // Get the saved diff for apply 220 &EvalReadDiff{ 221 Name: stateId, 222 Diff: &diffApply, 223 }, 224 225 // We don't want to do any destroys 226 &EvalIf{ 227 If: func(ctx EvalContext) (bool, error) { 228 if diffApply == nil { 229 return true, EvalEarlyExitError{} 230 } 231 232 if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 { 233 return true, EvalEarlyExitError{} 234 } 235 236 diffApply.SetDestroy(false) 237 return true, nil 238 }, 239 Then: EvalNoop{}, 240 }, 241 242 &EvalIf{ 243 If: func(ctx EvalContext) (bool, error) { 244 destroy := false 245 if diffApply != nil { 246 destroy = diffApply.GetDestroy() || diffApply.RequiresNew() 247 } 248 249 createBeforeDestroyEnabled = 250 n.Config.Lifecycle.CreateBeforeDestroy && 251 destroy 252 253 return createBeforeDestroyEnabled, nil 254 }, 255 Then: &EvalDeposeState{ 256 Name: stateId, 257 }, 258 }, 259 260 // Normally we interpolate count as a preparation step before 261 // a DynamicExpand, but an apply graph has pre-expanded nodes 262 // and so the count would otherwise never be interpolated. 263 // 264 // This is redundant when there are multiple instances created 265 // from the same config (count > 1) but harmless since the 266 // underlying structures have mutexes to make this concurrency-safe. 267 // 268 // In most cases this isn't actually needed because we dealt with 269 // all of the counts during the plan walk, but we need to do this 270 // in order to support interpolation of resource counts from 271 // apply-time-interpolated expressions, such as those in 272 // "provisioner" blocks. 273 // 274 // Here we are just populating the interpolated value in-place 275 // inside this RawConfig object, like we would in 276 // NodeAbstractCountResource. 277 &EvalInterpolate{ 278 Config: n.Config.RawCount, 279 ContinueOnErr: true, 280 }, 281 282 &EvalInterpolate{ 283 Config: n.Config.RawConfig.Copy(), 284 Resource: resource, 285 Output: &resourceConfig, 286 }, 287 &EvalGetProvider{ 288 Name: n.ResolvedProvider, 289 Output: &provider, 290 }, 291 &EvalReadState{ 292 Name: stateId, 293 Output: &state, 294 }, 295 // Re-run validation to catch any errors we missed, e.g. type 296 // mismatches on computed values. 297 &EvalValidateResource{ 298 Provider: &provider, 299 Config: &resourceConfig, 300 ResourceName: n.Config.Name, 301 ResourceType: n.Config.Type, 302 ResourceMode: n.Config.Mode, 303 IgnoreWarnings: true, 304 }, 305 &EvalDiff{ 306 Info: info, 307 Config: &resourceConfig, 308 Resource: n.Config, 309 Provider: &provider, 310 Diff: &diffApply, 311 State: &state, 312 OutputDiff: &diffApply, 313 }, 314 315 // Get the saved diff 316 &EvalReadDiff{ 317 Name: stateId, 318 Diff: &diff, 319 }, 320 321 // Compare the diffs 322 &EvalCompareDiff{ 323 Info: info, 324 One: &diff, 325 Two: &diffApply, 326 }, 327 328 &EvalGetProvider{ 329 Name: n.ResolvedProvider, 330 Output: &provider, 331 }, 332 &EvalReadState{ 333 Name: stateId, 334 Output: &state, 335 }, 336 // Call pre-apply hook 337 &EvalApplyPre{ 338 Info: info, 339 State: &state, 340 Diff: &diffApply, 341 }, 342 &EvalApply{ 343 Info: info, 344 State: &state, 345 Diff: &diffApply, 346 Provider: &provider, 347 Output: &state, 348 Error: &err, 349 CreateNew: &createNew, 350 }, 351 &EvalWriteState{ 352 Name: stateId, 353 ResourceType: n.Config.Type, 354 Provider: n.ResolvedProvider, 355 Dependencies: stateDeps, 356 State: &state, 357 }, 358 &EvalApplyProvisioners{ 359 Info: info, 360 State: &state, 361 Resource: n.Config, 362 InterpResource: resource, 363 CreateNew: &createNew, 364 Error: &err, 365 When: config.ProvisionerWhenCreate, 366 }, 367 &EvalIf{ 368 If: func(ctx EvalContext) (bool, error) { 369 return createBeforeDestroyEnabled && err != nil, nil 370 }, 371 Then: &EvalUndeposeState{ 372 Name: stateId, 373 State: &state, 374 }, 375 Else: &EvalWriteState{ 376 Name: stateId, 377 ResourceType: n.Config.Type, 378 Provider: n.ResolvedProvider, 379 Dependencies: stateDeps, 380 State: &state, 381 }, 382 }, 383 384 // We clear the diff out here so that future nodes 385 // don't see a diff that is already complete. There 386 // is no longer a diff! 387 &EvalWriteDiff{ 388 Name: stateId, 389 Diff: nil, 390 }, 391 392 &EvalApplyPost{ 393 Info: info, 394 State: &state, 395 Error: &err, 396 }, 397 &EvalUpdateStateHook{}, 398 }, 399 } 400 }