github.com/mikesimons/terraform@v0.6.13-0.20160304043642-f11448c69214/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 seq.Nodes = append(seq.Nodes, &EvalOpFilter{ 376 Ops: []walkOperation{walkApply, walkDestroy}, 377 Node: &EvalSequence{ 378 Nodes: []EvalNode{ 379 // Get the saved diff for apply 380 &EvalReadDiff{ 381 Name: n.stateId(), 382 Diff: &diffApply, 383 }, 384 385 // We don't want to do any destroys 386 &EvalIf{ 387 If: func(ctx EvalContext) (bool, error) { 388 if diffApply == nil { 389 return true, EvalEarlyExitError{} 390 } 391 392 if diffApply.Destroy && len(diffApply.Attributes) == 0 { 393 return true, EvalEarlyExitError{} 394 } 395 396 diffApply.Destroy = false 397 return true, nil 398 }, 399 Then: EvalNoop{}, 400 }, 401 402 &EvalIf{ 403 If: func(ctx EvalContext) (bool, error) { 404 destroy := false 405 if diffApply != nil { 406 destroy = diffApply.Destroy || diffApply.RequiresNew() 407 } 408 409 createBeforeDestroyEnabled = 410 n.Resource.Lifecycle.CreateBeforeDestroy && 411 destroy 412 413 return createBeforeDestroyEnabled, nil 414 }, 415 Then: &EvalDeposeState{ 416 Name: n.stateId(), 417 }, 418 }, 419 420 &EvalInterpolate{ 421 Config: n.Resource.RawConfig.Copy(), 422 Resource: resource, 423 Output: &resourceConfig, 424 }, 425 &EvalGetProvider{ 426 Name: n.ProvidedBy()[0], 427 Output: &provider, 428 }, 429 &EvalReadState{ 430 Name: n.stateId(), 431 Output: &state, 432 }, 433 434 &EvalDiff{ 435 Info: info, 436 Config: &resourceConfig, 437 Provider: &provider, 438 State: &state, 439 Output: &diffApply, 440 }, 441 &EvalIgnoreChanges{ 442 Resource: n.Resource, 443 Diff: &diffApply, 444 }, 445 446 // Get the saved diff 447 &EvalReadDiff{ 448 Name: n.stateId(), 449 Diff: &diff, 450 }, 451 452 // Compare the diffs 453 &EvalCompareDiff{ 454 Info: info, 455 One: &diff, 456 Two: &diffApply, 457 }, 458 459 &EvalGetProvider{ 460 Name: n.ProvidedBy()[0], 461 Output: &provider, 462 }, 463 &EvalReadState{ 464 Name: n.stateId(), 465 Output: &state, 466 }, 467 &EvalApply{ 468 Info: info, 469 State: &state, 470 Diff: &diffApply, 471 Provider: &provider, 472 Output: &state, 473 Error: &err, 474 CreateNew: &createNew, 475 }, 476 &EvalWriteState{ 477 Name: n.stateId(), 478 ResourceType: n.Resource.Type, 479 Provider: n.Resource.Provider, 480 Dependencies: n.StateDependencies(), 481 State: &state, 482 }, 483 &EvalApplyProvisioners{ 484 Info: info, 485 State: &state, 486 Resource: n.Resource, 487 InterpResource: resource, 488 CreateNew: &createNew, 489 Tainted: &tainted, 490 Error: &err, 491 }, 492 &EvalIf{ 493 If: func(ctx EvalContext) (bool, error) { 494 if createBeforeDestroyEnabled { 495 tainted = err != nil 496 } 497 498 failure := tainted || err != nil 499 return createBeforeDestroyEnabled && failure, nil 500 }, 501 Then: &EvalUndeposeState{ 502 Name: n.stateId(), 503 }, 504 }, 505 506 // We clear the diff out here so that future nodes 507 // don't see a diff that is already complete. There 508 // is no longer a diff! 509 &EvalWriteDiff{ 510 Name: n.stateId(), 511 Diff: nil, 512 }, 513 514 &EvalIf{ 515 If: func(ctx EvalContext) (bool, error) { 516 return tainted, nil 517 }, 518 Then: &EvalSequence{ 519 Nodes: []EvalNode{ 520 &EvalWriteStateTainted{ 521 Name: n.stateId(), 522 ResourceType: n.Resource.Type, 523 Provider: n.Resource.Provider, 524 Dependencies: n.StateDependencies(), 525 State: &state, 526 Index: -1, 527 }, 528 &EvalIf{ 529 If: func(ctx EvalContext) (bool, error) { 530 return !n.Resource.Lifecycle.CreateBeforeDestroy, nil 531 }, 532 Then: &EvalClearPrimaryState{ 533 Name: n.stateId(), 534 }, 535 }, 536 }, 537 }, 538 Else: &EvalWriteState{ 539 Name: n.stateId(), 540 ResourceType: n.Resource.Type, 541 Provider: n.Resource.Provider, 542 Dependencies: n.StateDependencies(), 543 State: &state, 544 }, 545 }, 546 &EvalApplyPost{ 547 Info: info, 548 State: &state, 549 Error: &err, 550 }, 551 &EvalUpdateStateHook{}, 552 }, 553 }, 554 }) 555 556 return seq 557 } 558 559 // instanceInfo is used for EvalTree. 560 func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo { 561 return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type} 562 } 563 564 // stateId is the name used for the state key 565 func (n *graphNodeExpandedResource) stateId() string { 566 if n.Index == -1 { 567 return n.Resource.Id() 568 } 569 570 return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index) 571 } 572 573 // GraphNodeStateRepresentative impl. 574 func (n *graphNodeExpandedResource) StateId() []string { 575 return []string{n.stateId()} 576 } 577 578 // graphNodeExpandedResourceDestroy represents an expanded resource that 579 // is to be destroyed. 580 type graphNodeExpandedResourceDestroy struct { 581 *graphNodeExpandedResource 582 } 583 584 func (n *graphNodeExpandedResourceDestroy) Name() string { 585 return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name()) 586 } 587 588 // graphNodeConfig impl. 589 func (n *graphNodeExpandedResourceDestroy) ConfigType() GraphNodeConfigType { 590 return GraphNodeConfigTypeResource 591 } 592 593 // GraphNodeEvalable impl. 594 func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode { 595 info := n.instanceInfo() 596 597 var diffApply *InstanceDiff 598 var provider ResourceProvider 599 var state *InstanceState 600 var err error 601 return &EvalOpFilter{ 602 Ops: []walkOperation{walkApply, walkDestroy}, 603 Node: &EvalSequence{ 604 Nodes: []EvalNode{ 605 // Get the saved diff for apply 606 &EvalReadDiff{ 607 Name: n.stateId(), 608 Diff: &diffApply, 609 }, 610 611 // Filter the diff so we only get the destroy 612 &EvalFilterDiff{ 613 Diff: &diffApply, 614 Output: &diffApply, 615 Destroy: true, 616 }, 617 618 // If we're not destroying, then compare diffs 619 &EvalIf{ 620 If: func(ctx EvalContext) (bool, error) { 621 if diffApply != nil && diffApply.Destroy { 622 return true, nil 623 } 624 625 return true, EvalEarlyExitError{} 626 }, 627 Then: EvalNoop{}, 628 }, 629 630 &EvalGetProvider{ 631 Name: n.ProvidedBy()[0], 632 Output: &provider, 633 }, 634 &EvalReadState{ 635 Name: n.stateId(), 636 Output: &state, 637 }, 638 &EvalRequireState{ 639 State: &state, 640 }, 641 &EvalApply{ 642 Info: info, 643 State: &state, 644 Diff: &diffApply, 645 Provider: &provider, 646 Output: &state, 647 Error: &err, 648 }, 649 &EvalWriteState{ 650 Name: n.stateId(), 651 ResourceType: n.Resource.Type, 652 Provider: n.Resource.Provider, 653 Dependencies: n.StateDependencies(), 654 State: &state, 655 }, 656 &EvalApplyPost{ 657 Info: info, 658 State: &state, 659 Error: &err, 660 }, 661 }, 662 }, 663 } 664 }