github.com/tarrant/terraform@v0.3.8-0.20150402012457-f68c9eee638e/terraform/transform_resource.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 6 "github.com/hashicorp/terraform/config" 7 "github.com/hashicorp/terraform/dag" 8 ) 9 10 // ResourceCountTransformer is a GraphTransformer that expands the count 11 // out for a specific resource. 12 type ResourceCountTransformer struct { 13 Resource *config.Resource 14 Destroy bool 15 Targets []ResourceAddress 16 } 17 18 func (t *ResourceCountTransformer) Transform(g *Graph) error { 19 // Expand the resource count 20 count, err := t.Resource.Count() 21 if err != nil { 22 return err 23 } 24 25 // Don't allow the count to be negative 26 if count < 0 { 27 return fmt.Errorf("negative count: %d", count) 28 } 29 30 // For each count, build and add the node 31 nodes := make([]dag.Vertex, 0, count) 32 for i := 0; i < count; i++ { 33 // Set the index. If our count is 1 we special case it so that 34 // we handle the "resource.0" and "resource" boundary properly. 35 index := i 36 if count == 1 { 37 index = -1 38 } 39 40 // Save the node for later so we can do connections. Make the 41 // proper node depending on if we're just a destroy node or if 42 // were a regular node. 43 var node dag.Vertex = &graphNodeExpandedResource{ 44 Index: index, 45 Resource: t.Resource, 46 } 47 if t.Destroy { 48 node = &graphNodeExpandedResourceDestroy{ 49 graphNodeExpandedResource: node.(*graphNodeExpandedResource), 50 } 51 } 52 53 // Skip nodes if targeting excludes them 54 if !t.nodeIsTargeted(node) { 55 continue 56 } 57 58 // Add the node now 59 nodes = append(nodes, node) 60 g.Add(node) 61 } 62 63 // Make the dependency connections 64 for _, n := range nodes { 65 // Connect the dependents. We ignore the return value for missing 66 // dependents since that should've been caught at a higher level. 67 g.ConnectDependent(n) 68 } 69 70 return nil 71 } 72 73 func (t *ResourceCountTransformer) nodeIsTargeted(node dag.Vertex) bool { 74 // no targets specified, everything stays in the graph 75 if len(t.Targets) == 0 { 76 return true 77 } 78 addressable, ok := node.(GraphNodeAddressable) 79 if !ok { 80 return false 81 } 82 83 addr := addressable.ResourceAddress() 84 for _, targetAddr := range t.Targets { 85 if targetAddr.Equals(addr) { 86 return true 87 } 88 } 89 return false 90 } 91 92 type graphNodeExpandedResource struct { 93 Index int 94 Resource *config.Resource 95 } 96 97 func (n *graphNodeExpandedResource) Name() string { 98 if n.Index == -1 { 99 return n.Resource.Id() 100 } 101 102 return fmt.Sprintf("%s #%d", n.Resource.Id(), n.Index) 103 } 104 105 // GraphNodeAddressable impl. 106 func (n *graphNodeExpandedResource) ResourceAddress() *ResourceAddress { 107 // We want this to report the logical index properly, so we must undo the 108 // special case from the expand 109 index := n.Index 110 if index == -1 { 111 index = 0 112 } 113 return &ResourceAddress{ 114 Index: index, 115 // TODO: kjkjkj 116 InstanceType: TypePrimary, 117 Name: n.Resource.Name, 118 Type: n.Resource.Type, 119 } 120 } 121 122 // GraphNodeDependable impl. 123 func (n *graphNodeExpandedResource) DependableName() []string { 124 return []string{ 125 n.Resource.Id(), 126 n.stateId(), 127 } 128 } 129 130 // GraphNodeDependent impl. 131 func (n *graphNodeExpandedResource) DependentOn() []string { 132 config := &GraphNodeConfigResource{Resource: n.Resource} 133 return config.DependentOn() 134 } 135 136 // GraphNodeProviderConsumer 137 func (n *graphNodeExpandedResource) ProvidedBy() []string { 138 return []string{resourceProvider(n.Resource.Type)} 139 } 140 141 // GraphNodeEvalable impl. 142 func (n *graphNodeExpandedResource) EvalTree() EvalNode { 143 var diff *InstanceDiff 144 var provider ResourceProvider 145 var resourceConfig *ResourceConfig 146 var state *InstanceState 147 148 // Build the resource. If we aren't part of a multi-resource, then 149 // we still consider ourselves as count index zero. 150 index := n.Index 151 if index < 0 { 152 index = 0 153 } 154 resource := &Resource{ 155 Name: n.Resource.Name, 156 Type: n.Resource.Type, 157 CountIndex: index, 158 } 159 160 seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} 161 162 // Validate the resource 163 vseq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} 164 vseq.Nodes = append(vseq.Nodes, &EvalGetProvider{ 165 Name: n.ProvidedBy()[0], 166 Output: &provider, 167 }) 168 vseq.Nodes = append(vseq.Nodes, &EvalInterpolate{ 169 Config: n.Resource.RawConfig, 170 Resource: resource, 171 Output: &resourceConfig, 172 }) 173 vseq.Nodes = append(vseq.Nodes, &EvalValidateResource{ 174 Provider: &provider, 175 Config: &resourceConfig, 176 ResourceName: n.Resource.Name, 177 ResourceType: n.Resource.Type, 178 }) 179 180 // Validate all the provisioners 181 for _, p := range n.Resource.Provisioners { 182 var provisioner ResourceProvisioner 183 vseq.Nodes = append(vseq.Nodes, &EvalGetProvisioner{ 184 Name: p.Type, 185 Output: &provisioner, 186 }, &EvalInterpolate{ 187 Config: p.RawConfig, 188 Resource: resource, 189 Output: &resourceConfig, 190 }, &EvalValidateProvisioner{ 191 Provisioner: &provisioner, 192 Config: &resourceConfig, 193 }) 194 } 195 196 // Add the validation operations 197 seq.Nodes = append(seq.Nodes, &EvalOpFilter{ 198 Ops: []walkOperation{walkValidate}, 199 Node: vseq, 200 }) 201 202 // Build instance info 203 info := n.instanceInfo() 204 seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info}) 205 206 // Refresh the resource 207 seq.Nodes = append(seq.Nodes, &EvalOpFilter{ 208 Ops: []walkOperation{walkRefresh}, 209 Node: &EvalSequence{ 210 Nodes: []EvalNode{ 211 &EvalGetProvider{ 212 Name: n.ProvidedBy()[0], 213 Output: &provider, 214 }, 215 &EvalReadState{ 216 Name: n.stateId(), 217 Output: &state, 218 }, 219 &EvalRefresh{ 220 Info: info, 221 Provider: &provider, 222 State: &state, 223 Output: &state, 224 }, 225 &EvalWriteState{ 226 Name: n.stateId(), 227 ResourceType: n.Resource.Type, 228 Dependencies: n.DependentOn(), 229 State: &state, 230 }, 231 }, 232 }, 233 }) 234 235 // Diff the resource 236 seq.Nodes = append(seq.Nodes, &EvalOpFilter{ 237 Ops: []walkOperation{walkPlan}, 238 Node: &EvalSequence{ 239 Nodes: []EvalNode{ 240 &EvalInterpolate{ 241 Config: n.Resource.RawConfig, 242 Resource: resource, 243 Output: &resourceConfig, 244 }, 245 &EvalGetProvider{ 246 Name: n.ProvidedBy()[0], 247 Output: &provider, 248 }, 249 &EvalReadState{ 250 Name: n.stateId(), 251 Output: &state, 252 }, 253 &EvalDiff{ 254 Info: info, 255 Config: &resourceConfig, 256 Provider: &provider, 257 State: &state, 258 Output: &diff, 259 OutputState: &state, 260 }, 261 &EvalWriteState{ 262 Name: n.stateId(), 263 ResourceType: n.Resource.Type, 264 Dependencies: n.DependentOn(), 265 State: &state, 266 }, 267 &EvalDiffTainted{ 268 Diff: &diff, 269 Name: n.stateId(), 270 }, 271 &EvalWriteDiff{ 272 Name: n.stateId(), 273 Diff: &diff, 274 }, 275 }, 276 }, 277 }) 278 279 // Diff the resource for destruction 280 seq.Nodes = append(seq.Nodes, &EvalOpFilter{ 281 Ops: []walkOperation{walkPlanDestroy}, 282 Node: &EvalSequence{ 283 Nodes: []EvalNode{ 284 &EvalReadState{ 285 Name: n.stateId(), 286 Output: &state, 287 }, 288 &EvalDiffDestroy{ 289 Info: info, 290 State: &state, 291 Output: &diff, 292 }, 293 &EvalWriteDiff{ 294 Name: n.stateId(), 295 Diff: &diff, 296 }, 297 }, 298 }, 299 }) 300 301 // Apply 302 var diffApply *InstanceDiff 303 var err error 304 var createNew, tainted bool 305 var createBeforeDestroyEnabled bool 306 seq.Nodes = append(seq.Nodes, &EvalOpFilter{ 307 Ops: []walkOperation{walkApply}, 308 Node: &EvalSequence{ 309 Nodes: []EvalNode{ 310 // Get the saved diff for apply 311 &EvalReadDiff{ 312 Name: n.stateId(), 313 Diff: &diffApply, 314 }, 315 316 // We don't want to do any destroys 317 &EvalIf{ 318 If: func(ctx EvalContext) (bool, error) { 319 if diffApply == nil { 320 return true, EvalEarlyExitError{} 321 } 322 323 if diffApply.Destroy && len(diffApply.Attributes) == 0 { 324 return true, EvalEarlyExitError{} 325 } 326 327 diffApply.Destroy = false 328 return true, nil 329 }, 330 Then: EvalNoop{}, 331 }, 332 333 &EvalIf{ 334 If: func(ctx EvalContext) (bool, error) { 335 destroy := false 336 if diffApply != nil { 337 destroy = diffApply.Destroy || diffApply.RequiresNew() 338 } 339 340 createBeforeDestroyEnabled = 341 n.Resource.Lifecycle.CreateBeforeDestroy && 342 destroy 343 344 return createBeforeDestroyEnabled, nil 345 }, 346 Then: &EvalDeposeState{ 347 Name: n.stateId(), 348 }, 349 }, 350 351 &EvalInterpolate{ 352 Config: n.Resource.RawConfig, 353 Resource: resource, 354 Output: &resourceConfig, 355 }, 356 &EvalGetProvider{ 357 Name: n.ProvidedBy()[0], 358 Output: &provider, 359 }, 360 &EvalReadState{ 361 Name: n.stateId(), 362 Output: &state, 363 }, 364 365 &EvalDiff{ 366 Info: info, 367 Config: &resourceConfig, 368 Provider: &provider, 369 State: &state, 370 Output: &diffApply, 371 }, 372 373 // Get the saved diff 374 &EvalReadDiff{ 375 Name: n.stateId(), 376 Diff: &diff, 377 }, 378 379 // Compare the diffs 380 &EvalCompareDiff{ 381 Info: info, 382 One: &diff, 383 Two: &diffApply, 384 }, 385 386 &EvalGetProvider{ 387 Name: n.ProvidedBy()[0], 388 Output: &provider, 389 }, 390 &EvalReadState{ 391 Name: n.stateId(), 392 Output: &state, 393 }, 394 &EvalApply{ 395 Info: info, 396 State: &state, 397 Diff: &diffApply, 398 Provider: &provider, 399 Output: &state, 400 Error: &err, 401 CreateNew: &createNew, 402 }, 403 &EvalWriteState{ 404 Name: n.stateId(), 405 ResourceType: n.Resource.Type, 406 Dependencies: n.DependentOn(), 407 State: &state, 408 }, 409 &EvalApplyProvisioners{ 410 Info: info, 411 State: &state, 412 Resource: n.Resource, 413 InterpResource: resource, 414 CreateNew: &createNew, 415 Tainted: &tainted, 416 Error: &err, 417 }, 418 &EvalIf{ 419 If: func(ctx EvalContext) (bool, error) { 420 if createBeforeDestroyEnabled { 421 tainted = err != nil 422 } 423 424 failure := tainted || err != nil 425 return createBeforeDestroyEnabled && failure, nil 426 }, 427 Then: &EvalUndeposeState{ 428 Name: n.stateId(), 429 }, 430 }, 431 432 // We clear the diff out here so that future nodes 433 // don't see a diff that is already complete. There 434 // is no longer a diff! 435 &EvalWriteDiff{ 436 Name: n.stateId(), 437 Diff: nil, 438 }, 439 440 &EvalIf{ 441 If: func(ctx EvalContext) (bool, error) { 442 return tainted, nil 443 }, 444 Then: &EvalSequence{ 445 Nodes: []EvalNode{ 446 &EvalWriteStateTainted{ 447 Name: n.stateId(), 448 ResourceType: n.Resource.Type, 449 Dependencies: n.DependentOn(), 450 State: &state, 451 Index: -1, 452 }, 453 &EvalIf{ 454 If: func(ctx EvalContext) (bool, error) { 455 return !n.Resource.Lifecycle.CreateBeforeDestroy, nil 456 }, 457 Then: &EvalClearPrimaryState{ 458 Name: n.stateId(), 459 }, 460 }, 461 }, 462 }, 463 Else: &EvalWriteState{ 464 Name: n.stateId(), 465 ResourceType: n.Resource.Type, 466 Dependencies: n.DependentOn(), 467 State: &state, 468 }, 469 }, 470 &EvalApplyPost{ 471 Info: info, 472 State: &state, 473 Error: &err, 474 }, 475 &EvalUpdateStateHook{}, 476 }, 477 }, 478 }) 479 480 return seq 481 } 482 483 // instanceInfo is used for EvalTree. 484 func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo { 485 return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type} 486 } 487 488 // stateId is the name used for the state key 489 func (n *graphNodeExpandedResource) stateId() string { 490 if n.Index == -1 { 491 return n.Resource.Id() 492 } 493 494 return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index) 495 } 496 497 // GraphNodeStateRepresentative impl. 498 func (n *graphNodeExpandedResource) StateId() []string { 499 return []string{n.stateId()} 500 } 501 502 // graphNodeExpandedResourceDestroy represents an expanded resource that 503 // is to be destroyed. 504 type graphNodeExpandedResourceDestroy struct { 505 *graphNodeExpandedResource 506 } 507 508 func (n *graphNodeExpandedResourceDestroy) Name() string { 509 return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name()) 510 } 511 512 // GraphNodeEvalable impl. 513 func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode { 514 info := n.instanceInfo() 515 516 var diffApply *InstanceDiff 517 var provider ResourceProvider 518 var state *InstanceState 519 var err error 520 return &EvalOpFilter{ 521 Ops: []walkOperation{walkApply}, 522 Node: &EvalSequence{ 523 Nodes: []EvalNode{ 524 // Get the saved diff for apply 525 &EvalReadDiff{ 526 Name: n.stateId(), 527 Diff: &diffApply, 528 }, 529 530 // Filter the diff so we only get the destroy 531 &EvalFilterDiff{ 532 Diff: &diffApply, 533 Output: &diffApply, 534 Destroy: true, 535 }, 536 537 // If we're not destroying, then compare diffs 538 &EvalIf{ 539 If: func(ctx EvalContext) (bool, error) { 540 if diffApply != nil && diffApply.Destroy { 541 return true, nil 542 } 543 544 return true, EvalEarlyExitError{} 545 }, 546 Then: EvalNoop{}, 547 }, 548 549 &EvalGetProvider{ 550 Name: n.ProvidedBy()[0], 551 Output: &provider, 552 }, 553 &EvalIf{ 554 If: func(ctx EvalContext) (bool, error) { 555 return n.Resource.Lifecycle.CreateBeforeDestroy, nil 556 }, 557 Then: &EvalReadStateTainted{ 558 Name: n.stateId(), 559 Output: &state, 560 Index: -1, 561 }, 562 Else: &EvalReadState{ 563 Name: n.stateId(), 564 Output: &state, 565 }, 566 }, 567 &EvalRequireState{ 568 State: &state, 569 }, 570 &EvalApply{ 571 Info: info, 572 State: &state, 573 Diff: &diffApply, 574 Provider: &provider, 575 Output: &state, 576 Error: &err, 577 }, 578 &EvalWriteState{ 579 Name: n.stateId(), 580 ResourceType: n.Resource.Type, 581 Dependencies: n.DependentOn(), 582 State: &state, 583 }, 584 &EvalApplyPost{ 585 Info: info, 586 State: &state, 587 Error: &err, 588 }, 589 }, 590 }, 591 } 592 }