github.com/alexissmirnov/terraform@v0.4.3-0.20150423153700-1ef9731a2f14/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 // graphNodeConfig impl. 123 func (n *graphNodeExpandedResource) ConfigType() GraphNodeConfigType { 124 return GraphNodeConfigTypeResource 125 } 126 127 // GraphNodeDependable impl. 128 func (n *graphNodeExpandedResource) DependableName() []string { 129 return []string{ 130 n.Resource.Id(), 131 n.stateId(), 132 } 133 } 134 135 // GraphNodeDependent impl. 136 func (n *graphNodeExpandedResource) DependentOn() []string { 137 configNode := &GraphNodeConfigResource{Resource: n.Resource} 138 result := configNode.DependentOn() 139 140 // Walk the variables to find any count-specific variables we depend on. 141 configNode.VarWalk(func(v config.InterpolatedVariable) { 142 rv, ok := v.(*config.ResourceVariable) 143 if !ok { 144 return 145 } 146 147 // We only want ourselves 148 if rv.ResourceId() != n.Resource.Id() { 149 return 150 } 151 152 // If this isn't a multi-access (which shouldn't be allowed but 153 // is verified elsewhere), then we depend on the specific count 154 // of this resource, ignoring ourself (which again should be 155 // validated elsewhere). 156 if rv.Index > -1 { 157 id := fmt.Sprintf("%s.%d", rv.ResourceId(), rv.Index) 158 if id != n.stateId() && id != n.stateId()+".0" { 159 result = append(result, id) 160 } 161 } 162 }) 163 164 return result 165 } 166 167 // GraphNodeProviderConsumer 168 func (n *graphNodeExpandedResource) ProvidedBy() []string { 169 return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)} 170 } 171 172 // GraphNodeEvalable impl. 173 func (n *graphNodeExpandedResource) EvalTree() EvalNode { 174 var diff *InstanceDiff 175 var provider ResourceProvider 176 var resourceConfig *ResourceConfig 177 var state *InstanceState 178 179 // Build the resource. If we aren't part of a multi-resource, then 180 // we still consider ourselves as count index zero. 181 index := n.Index 182 if index < 0 { 183 index = 0 184 } 185 resource := &Resource{ 186 Name: n.Resource.Name, 187 Type: n.Resource.Type, 188 CountIndex: index, 189 } 190 191 seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} 192 193 // Validate the resource 194 vseq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} 195 vseq.Nodes = append(vseq.Nodes, &EvalGetProvider{ 196 Name: n.ProvidedBy()[0], 197 Output: &provider, 198 }) 199 vseq.Nodes = append(vseq.Nodes, &EvalInterpolate{ 200 Config: n.Resource.RawConfig.Copy(), 201 Resource: resource, 202 Output: &resourceConfig, 203 }) 204 vseq.Nodes = append(vseq.Nodes, &EvalValidateResource{ 205 Provider: &provider, 206 Config: &resourceConfig, 207 ResourceName: n.Resource.Name, 208 ResourceType: n.Resource.Type, 209 }) 210 211 // Validate all the provisioners 212 for _, p := range n.Resource.Provisioners { 213 var provisioner ResourceProvisioner 214 vseq.Nodes = append(vseq.Nodes, &EvalGetProvisioner{ 215 Name: p.Type, 216 Output: &provisioner, 217 }, &EvalInterpolate{ 218 Config: p.RawConfig.Copy(), 219 Resource: resource, 220 Output: &resourceConfig, 221 }, &EvalValidateProvisioner{ 222 Provisioner: &provisioner, 223 Config: &resourceConfig, 224 }) 225 } 226 227 // Add the validation operations 228 seq.Nodes = append(seq.Nodes, &EvalOpFilter{ 229 Ops: []walkOperation{walkValidate}, 230 Node: vseq, 231 }) 232 233 // Build instance info 234 info := n.instanceInfo() 235 seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info}) 236 237 // Refresh the resource 238 seq.Nodes = append(seq.Nodes, &EvalOpFilter{ 239 Ops: []walkOperation{walkRefresh}, 240 Node: &EvalSequence{ 241 Nodes: []EvalNode{ 242 &EvalGetProvider{ 243 Name: n.ProvidedBy()[0], 244 Output: &provider, 245 }, 246 &EvalReadState{ 247 Name: n.stateId(), 248 Output: &state, 249 }, 250 &EvalRefresh{ 251 Info: info, 252 Provider: &provider, 253 State: &state, 254 Output: &state, 255 }, 256 &EvalWriteState{ 257 Name: n.stateId(), 258 ResourceType: n.Resource.Type, 259 Provider: n.Resource.Provider, 260 Dependencies: n.DependentOn(), 261 State: &state, 262 }, 263 }, 264 }, 265 }) 266 267 // Diff the resource 268 seq.Nodes = append(seq.Nodes, &EvalOpFilter{ 269 Ops: []walkOperation{walkPlan}, 270 Node: &EvalSequence{ 271 Nodes: []EvalNode{ 272 &EvalInterpolate{ 273 Config: n.Resource.RawConfig.Copy(), 274 Resource: resource, 275 Output: &resourceConfig, 276 }, 277 &EvalGetProvider{ 278 Name: n.ProvidedBy()[0], 279 Output: &provider, 280 }, 281 &EvalReadState{ 282 Name: n.stateId(), 283 Output: &state, 284 }, 285 &EvalDiff{ 286 Info: info, 287 Config: &resourceConfig, 288 Provider: &provider, 289 State: &state, 290 Output: &diff, 291 OutputState: &state, 292 }, 293 &EvalCheckPreventDestroy{ 294 Resource: n.Resource, 295 Diff: &diff, 296 }, 297 &EvalWriteState{ 298 Name: n.stateId(), 299 ResourceType: n.Resource.Type, 300 Provider: n.Resource.Provider, 301 Dependencies: n.DependentOn(), 302 State: &state, 303 }, 304 &EvalDiffTainted{ 305 Diff: &diff, 306 Name: n.stateId(), 307 }, 308 &EvalWriteDiff{ 309 Name: n.stateId(), 310 Diff: &diff, 311 }, 312 }, 313 }, 314 }) 315 316 // Diff the resource for destruction 317 seq.Nodes = append(seq.Nodes, &EvalOpFilter{ 318 Ops: []walkOperation{walkPlanDestroy}, 319 Node: &EvalSequence{ 320 Nodes: []EvalNode{ 321 &EvalReadState{ 322 Name: n.stateId(), 323 Output: &state, 324 }, 325 &EvalDiffDestroy{ 326 Info: info, 327 State: &state, 328 Output: &diff, 329 }, 330 &EvalCheckPreventDestroy{ 331 Resource: n.Resource, 332 Diff: &diff, 333 }, 334 &EvalWriteDiff{ 335 Name: n.stateId(), 336 Diff: &diff, 337 }, 338 }, 339 }, 340 }) 341 342 // Apply 343 var diffApply *InstanceDiff 344 var err error 345 var createNew, tainted bool 346 var createBeforeDestroyEnabled bool 347 seq.Nodes = append(seq.Nodes, &EvalOpFilter{ 348 Ops: []walkOperation{walkApply}, 349 Node: &EvalSequence{ 350 Nodes: []EvalNode{ 351 // Get the saved diff for apply 352 &EvalReadDiff{ 353 Name: n.stateId(), 354 Diff: &diffApply, 355 }, 356 357 // We don't want to do any destroys 358 &EvalIf{ 359 If: func(ctx EvalContext) (bool, error) { 360 if diffApply == nil { 361 return true, EvalEarlyExitError{} 362 } 363 364 if diffApply.Destroy && len(diffApply.Attributes) == 0 { 365 return true, EvalEarlyExitError{} 366 } 367 368 diffApply.Destroy = false 369 return true, nil 370 }, 371 Then: EvalNoop{}, 372 }, 373 374 &EvalIf{ 375 If: func(ctx EvalContext) (bool, error) { 376 destroy := false 377 if diffApply != nil { 378 destroy = diffApply.Destroy || diffApply.RequiresNew() 379 } 380 381 createBeforeDestroyEnabled = 382 n.Resource.Lifecycle.CreateBeforeDestroy && 383 destroy 384 385 return createBeforeDestroyEnabled, nil 386 }, 387 Then: &EvalDeposeState{ 388 Name: n.stateId(), 389 }, 390 }, 391 392 &EvalInterpolate{ 393 Config: n.Resource.RawConfig.Copy(), 394 Resource: resource, 395 Output: &resourceConfig, 396 }, 397 &EvalGetProvider{ 398 Name: n.ProvidedBy()[0], 399 Output: &provider, 400 }, 401 &EvalReadState{ 402 Name: n.stateId(), 403 Output: &state, 404 }, 405 406 &EvalDiff{ 407 Info: info, 408 Config: &resourceConfig, 409 Provider: &provider, 410 State: &state, 411 Output: &diffApply, 412 }, 413 414 // Get the saved diff 415 &EvalReadDiff{ 416 Name: n.stateId(), 417 Diff: &diff, 418 }, 419 420 // Compare the diffs 421 &EvalCompareDiff{ 422 Info: info, 423 One: &diff, 424 Two: &diffApply, 425 }, 426 427 &EvalGetProvider{ 428 Name: n.ProvidedBy()[0], 429 Output: &provider, 430 }, 431 &EvalReadState{ 432 Name: n.stateId(), 433 Output: &state, 434 }, 435 &EvalApply{ 436 Info: info, 437 State: &state, 438 Diff: &diffApply, 439 Provider: &provider, 440 Output: &state, 441 Error: &err, 442 CreateNew: &createNew, 443 }, 444 &EvalWriteState{ 445 Name: n.stateId(), 446 ResourceType: n.Resource.Type, 447 Provider: n.Resource.Provider, 448 Dependencies: n.DependentOn(), 449 State: &state, 450 }, 451 &EvalApplyProvisioners{ 452 Info: info, 453 State: &state, 454 Resource: n.Resource, 455 InterpResource: resource, 456 CreateNew: &createNew, 457 Tainted: &tainted, 458 Error: &err, 459 }, 460 &EvalIf{ 461 If: func(ctx EvalContext) (bool, error) { 462 if createBeforeDestroyEnabled { 463 tainted = err != nil 464 } 465 466 failure := tainted || err != nil 467 return createBeforeDestroyEnabled && failure, nil 468 }, 469 Then: &EvalUndeposeState{ 470 Name: n.stateId(), 471 }, 472 }, 473 474 // We clear the diff out here so that future nodes 475 // don't see a diff that is already complete. There 476 // is no longer a diff! 477 &EvalWriteDiff{ 478 Name: n.stateId(), 479 Diff: nil, 480 }, 481 482 &EvalIf{ 483 If: func(ctx EvalContext) (bool, error) { 484 return tainted, nil 485 }, 486 Then: &EvalSequence{ 487 Nodes: []EvalNode{ 488 &EvalWriteStateTainted{ 489 Name: n.stateId(), 490 ResourceType: n.Resource.Type, 491 Provider: n.Resource.Provider, 492 Dependencies: n.DependentOn(), 493 State: &state, 494 Index: -1, 495 }, 496 &EvalIf{ 497 If: func(ctx EvalContext) (bool, error) { 498 return !n.Resource.Lifecycle.CreateBeforeDestroy, nil 499 }, 500 Then: &EvalClearPrimaryState{ 501 Name: n.stateId(), 502 }, 503 }, 504 }, 505 }, 506 Else: &EvalWriteState{ 507 Name: n.stateId(), 508 ResourceType: n.Resource.Type, 509 Provider: n.Resource.Provider, 510 Dependencies: n.DependentOn(), 511 State: &state, 512 }, 513 }, 514 &EvalApplyPost{ 515 Info: info, 516 State: &state, 517 Error: &err, 518 }, 519 &EvalUpdateStateHook{}, 520 }, 521 }, 522 }) 523 524 return seq 525 } 526 527 // instanceInfo is used for EvalTree. 528 func (n *graphNodeExpandedResource) instanceInfo() *InstanceInfo { 529 return &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type} 530 } 531 532 // stateId is the name used for the state key 533 func (n *graphNodeExpandedResource) stateId() string { 534 if n.Index == -1 { 535 return n.Resource.Id() 536 } 537 538 return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index) 539 } 540 541 // GraphNodeStateRepresentative impl. 542 func (n *graphNodeExpandedResource) StateId() []string { 543 return []string{n.stateId()} 544 } 545 546 // graphNodeExpandedResourceDestroy represents an expanded resource that 547 // is to be destroyed. 548 type graphNodeExpandedResourceDestroy struct { 549 *graphNodeExpandedResource 550 } 551 552 func (n *graphNodeExpandedResourceDestroy) Name() string { 553 return fmt.Sprintf("%s (destroy)", n.graphNodeExpandedResource.Name()) 554 } 555 556 // graphNodeConfig impl. 557 func (n *graphNodeExpandedResourceDestroy) ConfigType() GraphNodeConfigType { 558 return GraphNodeConfigTypeResource 559 } 560 561 // GraphNodeEvalable impl. 562 func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode { 563 info := n.instanceInfo() 564 565 var diffApply *InstanceDiff 566 var provider ResourceProvider 567 var state *InstanceState 568 var err error 569 return &EvalOpFilter{ 570 Ops: []walkOperation{walkApply}, 571 Node: &EvalSequence{ 572 Nodes: []EvalNode{ 573 // Get the saved diff for apply 574 &EvalReadDiff{ 575 Name: n.stateId(), 576 Diff: &diffApply, 577 }, 578 579 // Filter the diff so we only get the destroy 580 &EvalFilterDiff{ 581 Diff: &diffApply, 582 Output: &diffApply, 583 Destroy: true, 584 }, 585 586 // If we're not destroying, then compare diffs 587 &EvalIf{ 588 If: func(ctx EvalContext) (bool, error) { 589 if diffApply != nil && diffApply.Destroy { 590 return true, nil 591 } 592 593 return true, EvalEarlyExitError{} 594 }, 595 Then: EvalNoop{}, 596 }, 597 598 &EvalGetProvider{ 599 Name: n.ProvidedBy()[0], 600 Output: &provider, 601 }, 602 &EvalReadState{ 603 Name: n.stateId(), 604 Output: &state, 605 }, 606 &EvalRequireState{ 607 State: &state, 608 }, 609 &EvalApply{ 610 Info: info, 611 State: &state, 612 Diff: &diffApply, 613 Provider: &provider, 614 Output: &state, 615 Error: &err, 616 }, 617 &EvalWriteState{ 618 Name: n.stateId(), 619 ResourceType: n.Resource.Type, 620 Provider: n.Resource.Provider, 621 Dependencies: n.DependentOn(), 622 State: &state, 623 }, 624 &EvalApplyPost{ 625 Info: info, 626 State: &state, 627 Error: &err, 628 }, 629 }, 630 }, 631 } 632 }