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