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