github.com/jonasi/terraform@v0.6.10-0.20160125170522-e865c342cc1f/terraform/graph_config_node_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 "github.com/hashicorp/terraform/dot" 10 ) 11 12 // GraphNodeCountDependent is implemented by resources for giving only 13 // the dependencies they have from the "count" field. 14 type GraphNodeCountDependent interface { 15 CountDependentOn() []string 16 } 17 18 // GraphNodeConfigResource represents a resource within the config graph. 19 type GraphNodeConfigResource struct { 20 Resource *config.Resource 21 22 // If this is set to anything other than destroyModeNone, then this 23 // resource represents a resource that will be destroyed in some way. 24 DestroyMode GraphNodeDestroyMode 25 26 // Used during DynamicExpand to target indexes 27 Targets []ResourceAddress 28 29 Path []string 30 } 31 32 func (n *GraphNodeConfigResource) ConfigType() GraphNodeConfigType { 33 return GraphNodeConfigTypeResource 34 } 35 36 func (n *GraphNodeConfigResource) DependableName() []string { 37 return []string{n.Resource.Id()} 38 } 39 40 // GraphNodeCountDependent impl. 41 func (n *GraphNodeConfigResource) CountDependentOn() []string { 42 result := make([]string, 0, len(n.Resource.RawCount.Variables)) 43 for _, v := range n.Resource.RawCount.Variables { 44 if vn := varNameForVar(v); vn != "" { 45 result = append(result, vn) 46 } 47 } 48 49 return result 50 } 51 52 // GraphNodeDependent impl. 53 func (n *GraphNodeConfigResource) DependentOn() []string { 54 result := make([]string, len(n.Resource.DependsOn), 55 (len(n.Resource.RawCount.Variables)+ 56 len(n.Resource.RawConfig.Variables)+ 57 len(n.Resource.DependsOn))*2) 58 copy(result, n.Resource.DependsOn) 59 60 for _, v := range n.Resource.RawCount.Variables { 61 if vn := varNameForVar(v); vn != "" { 62 result = append(result, vn) 63 } 64 } 65 for _, v := range n.Resource.RawConfig.Variables { 66 if vn := varNameForVar(v); vn != "" { 67 result = append(result, vn) 68 } 69 } 70 for _, p := range n.Resource.Provisioners { 71 for _, v := range p.ConnInfo.Variables { 72 if vn := varNameForVar(v); vn != "" && vn != n.Resource.Id() { 73 result = append(result, vn) 74 } 75 } 76 for _, v := range p.RawConfig.Variables { 77 if vn := varNameForVar(v); vn != "" && vn != n.Resource.Id() { 78 result = append(result, vn) 79 } 80 } 81 } 82 83 return result 84 } 85 86 // VarWalk calls a callback for all the variables that this resource 87 // depends on. 88 func (n *GraphNodeConfigResource) VarWalk(fn func(config.InterpolatedVariable)) { 89 for _, v := range n.Resource.RawCount.Variables { 90 fn(v) 91 } 92 for _, v := range n.Resource.RawConfig.Variables { 93 fn(v) 94 } 95 for _, p := range n.Resource.Provisioners { 96 for _, v := range p.ConnInfo.Variables { 97 fn(v) 98 } 99 for _, v := range p.RawConfig.Variables { 100 fn(v) 101 } 102 } 103 } 104 105 func (n *GraphNodeConfigResource) Name() string { 106 result := n.Resource.Id() 107 switch n.DestroyMode { 108 case DestroyNone: 109 case DestroyPrimary: 110 result += " (destroy)" 111 case DestroyTainted: 112 result += " (destroy tainted)" 113 default: 114 result += " (unknown destroy type)" 115 } 116 117 return result 118 } 119 120 // GraphNodeDotter impl. 121 func (n *GraphNodeConfigResource) DotNode(name string, opts *GraphDotOpts) *dot.Node { 122 if n.DestroyMode != DestroyNone && !opts.Verbose { 123 return nil 124 } 125 return dot.NewNode(name, map[string]string{ 126 "label": n.Name(), 127 "shape": "box", 128 }) 129 } 130 131 // GraphNodeFlattenable impl. 132 func (n *GraphNodeConfigResource) Flatten(p []string) (dag.Vertex, error) { 133 return &GraphNodeConfigResourceFlat{ 134 GraphNodeConfigResource: n, 135 PathValue: p, 136 }, nil 137 } 138 139 // GraphNodeDynamicExpandable impl. 140 func (n *GraphNodeConfigResource) DynamicExpand(ctx EvalContext) (*Graph, error) { 141 state, lock := ctx.State() 142 lock.RLock() 143 defer lock.RUnlock() 144 145 // Start creating the steps 146 steps := make([]GraphTransformer, 0, 5) 147 148 // Primary and non-destroy modes are responsible for creating/destroying 149 // all the nodes, expanding counts. 150 switch n.DestroyMode { 151 case DestroyNone, DestroyPrimary: 152 steps = append(steps, &ResourceCountTransformer{ 153 Resource: n.Resource, 154 Destroy: n.DestroyMode != DestroyNone, 155 Targets: n.Targets, 156 }) 157 } 158 159 // Additional destroy modifications. 160 switch n.DestroyMode { 161 case DestroyPrimary: 162 // If we're destroying the primary instance, then we want to 163 // expand orphans, which have all the same semantics in a destroy 164 // as a primary. 165 steps = append(steps, &OrphanTransformer{ 166 State: state, 167 View: n.Resource.Id(), 168 }) 169 170 steps = append(steps, &DeposedTransformer{ 171 State: state, 172 View: n.Resource.Id(), 173 }) 174 case DestroyTainted: 175 // If we're only destroying tainted resources, then we only 176 // want to find tainted resources and destroy them here. 177 steps = append(steps, &TaintedTransformer{ 178 State: state, 179 View: n.Resource.Id(), 180 }) 181 } 182 183 // We always want to apply targeting 184 steps = append(steps, &TargetsTransformer{ 185 ParsedTargets: n.Targets, 186 Destroy: n.DestroyMode != DestroyNone, 187 }) 188 189 // Always end with the root being added 190 steps = append(steps, &RootTransformer{}) 191 192 // Build the graph 193 b := &BasicGraphBuilder{Steps: steps} 194 return b.Build(ctx.Path()) 195 } 196 197 // GraphNodeAddressable impl. 198 func (n *GraphNodeConfigResource) ResourceAddress() *ResourceAddress { 199 return &ResourceAddress{ 200 Path: n.Path[1:], 201 Index: -1, 202 InstanceType: TypePrimary, 203 Name: n.Resource.Name, 204 Type: n.Resource.Type, 205 } 206 } 207 208 // GraphNodeTargetable impl. 209 func (n *GraphNodeConfigResource) SetTargets(targets []ResourceAddress) { 210 n.Targets = targets 211 } 212 213 // GraphNodeEvalable impl. 214 func (n *GraphNodeConfigResource) EvalTree() EvalNode { 215 return &EvalSequence{ 216 Nodes: []EvalNode{ 217 &EvalInterpolate{Config: n.Resource.RawCount}, 218 &EvalOpFilter{ 219 Ops: []walkOperation{walkValidate}, 220 Node: &EvalValidateCount{Resource: n.Resource}, 221 }, 222 &EvalCountFixZeroOneBoundary{Resource: n.Resource}, 223 }, 224 } 225 } 226 227 // GraphNodeProviderConsumer 228 func (n *GraphNodeConfigResource) ProvidedBy() []string { 229 return []string{resourceProvider(n.Resource.Type, n.Resource.Provider)} 230 } 231 232 // GraphNodeProvisionerConsumer 233 func (n *GraphNodeConfigResource) ProvisionedBy() []string { 234 result := make([]string, len(n.Resource.Provisioners)) 235 for i, p := range n.Resource.Provisioners { 236 result[i] = p.Type 237 } 238 239 return result 240 } 241 242 // GraphNodeDestroyable 243 func (n *GraphNodeConfigResource) DestroyNode(mode GraphNodeDestroyMode) GraphNodeDestroy { 244 // If we're already a destroy node, then don't do anything 245 if n.DestroyMode != DestroyNone { 246 return nil 247 } 248 249 result := &graphNodeResourceDestroy{ 250 GraphNodeConfigResource: *n, 251 Original: n, 252 } 253 result.DestroyMode = mode 254 return result 255 } 256 257 // GraphNodeNoopPrunable 258 func (n *GraphNodeConfigResource) Noop(opts *NoopOpts) bool { 259 // We don't have any noop optimizations for destroy nodes yet 260 if n.DestroyMode != DestroyNone { 261 return false 262 } 263 264 // If there is no diff, then we aren't a noop since something needs to 265 // be done (such as a plan). We only check if we're a noop in a diff. 266 if opts.Diff == nil || opts.Diff.Empty() { 267 return false 268 } 269 270 // If we have no module diff, we're certainly a noop. This is because 271 // it means there is a diff, and that the module we're in just isn't 272 // in it, meaning we're not doing anything. 273 if opts.ModDiff == nil || opts.ModDiff.Empty() { 274 return true 275 } 276 277 // Grab the ID which is the prefix (in the case count > 0 at some point) 278 prefix := n.Resource.Id() 279 280 // Go through the diff and if there are any with our name on it, keep us 281 found := false 282 for k, _ := range opts.ModDiff.Resources { 283 if strings.HasPrefix(k, prefix) { 284 found = true 285 break 286 } 287 } 288 289 return !found 290 } 291 292 // Same as GraphNodeConfigResource, but for flattening 293 type GraphNodeConfigResourceFlat struct { 294 *GraphNodeConfigResource 295 296 PathValue []string 297 } 298 299 func (n *GraphNodeConfigResourceFlat) Name() string { 300 return fmt.Sprintf( 301 "%s.%s", modulePrefixStr(n.PathValue), n.GraphNodeConfigResource.Name()) 302 } 303 304 func (n *GraphNodeConfigResourceFlat) Path() []string { 305 return n.PathValue 306 } 307 308 func (n *GraphNodeConfigResourceFlat) DependableName() []string { 309 return modulePrefixList( 310 n.GraphNodeConfigResource.DependableName(), 311 modulePrefixStr(n.PathValue)) 312 } 313 314 func (n *GraphNodeConfigResourceFlat) DependentOn() []string { 315 prefix := modulePrefixStr(n.PathValue) 316 return modulePrefixList( 317 n.GraphNodeConfigResource.DependentOn(), 318 prefix) 319 } 320 321 func (n *GraphNodeConfigResourceFlat) ProvidedBy() []string { 322 prefix := modulePrefixStr(n.PathValue) 323 return modulePrefixList( 324 n.GraphNodeConfigResource.ProvidedBy(), 325 prefix) 326 } 327 328 func (n *GraphNodeConfigResourceFlat) ProvisionedBy() []string { 329 prefix := modulePrefixStr(n.PathValue) 330 return modulePrefixList( 331 n.GraphNodeConfigResource.ProvisionedBy(), 332 prefix) 333 } 334 335 // GraphNodeDestroyable impl. 336 func (n *GraphNodeConfigResourceFlat) DestroyNode(mode GraphNodeDestroyMode) GraphNodeDestroy { 337 // Get our parent destroy node. If we don't have any, just return 338 raw := n.GraphNodeConfigResource.DestroyNode(mode) 339 if raw == nil { 340 return nil 341 } 342 343 node, ok := raw.(*graphNodeResourceDestroy) 344 if !ok { 345 panic(fmt.Sprintf("unknown destroy node: %s %T", dag.VertexName(raw), raw)) 346 } 347 348 // Otherwise, wrap it so that it gets the proper module treatment. 349 return &graphNodeResourceDestroyFlat{ 350 graphNodeResourceDestroy: node, 351 PathValue: n.PathValue, 352 FlatCreateNode: n, 353 } 354 } 355 356 type graphNodeResourceDestroyFlat struct { 357 *graphNodeResourceDestroy 358 359 PathValue []string 360 361 // Needs to be able to properly yield back a flattened create node to prevent 362 FlatCreateNode *GraphNodeConfigResourceFlat 363 } 364 365 func (n *graphNodeResourceDestroyFlat) Name() string { 366 return fmt.Sprintf( 367 "%s.%s", modulePrefixStr(n.PathValue), n.graphNodeResourceDestroy.Name()) 368 } 369 370 func (n *graphNodeResourceDestroyFlat) Path() []string { 371 return n.PathValue 372 } 373 374 func (n *graphNodeResourceDestroyFlat) CreateNode() dag.Vertex { 375 return n.FlatCreateNode 376 } 377 378 func (n *graphNodeResourceDestroyFlat) ProvidedBy() []string { 379 prefix := modulePrefixStr(n.PathValue) 380 return modulePrefixList( 381 n.GraphNodeConfigResource.ProvidedBy(), 382 prefix) 383 } 384 385 // graphNodeResourceDestroy represents the logical destruction of a 386 // resource. This node doesn't mean it will be destroyed for sure, but 387 // instead that if a destroy were to happen, it must happen at this point. 388 type graphNodeResourceDestroy struct { 389 GraphNodeConfigResource 390 Original *GraphNodeConfigResource 391 } 392 393 func (n *graphNodeResourceDestroy) CreateBeforeDestroy() bool { 394 // CBD is enabled if the resource enables it in addition to us 395 // being responsible for destroying the primary state. The primary 396 // state destroy node is the only destroy node that needs to be 397 // "shuffled" according to the CBD rules, since tainted resources 398 // don't have the same inverse dependencies. 399 return n.Original.Resource.Lifecycle.CreateBeforeDestroy && 400 n.DestroyMode == DestroyPrimary 401 } 402 403 func (n *graphNodeResourceDestroy) CreateNode() dag.Vertex { 404 return n.Original 405 } 406 407 func (n *graphNodeResourceDestroy) DestroyInclude(d *ModuleDiff, s *ModuleState) bool { 408 switch n.DestroyMode { 409 case DestroyPrimary: 410 return n.destroyIncludePrimary(d, s) 411 case DestroyTainted: 412 return n.destroyIncludeTainted(d, s) 413 default: 414 return true 415 } 416 } 417 418 func (n *graphNodeResourceDestroy) destroyIncludeTainted( 419 d *ModuleDiff, s *ModuleState) bool { 420 // If there is no state, there can't by any tainted. 421 if s == nil { 422 return false 423 } 424 425 // Grab the ID which is the prefix (in the case count > 0 at some point) 426 prefix := n.Original.Resource.Id() 427 428 // Go through the resources and find any with our prefix. If there 429 // are any tainted, we need to keep it. 430 for k, v := range s.Resources { 431 if !strings.HasPrefix(k, prefix) { 432 continue 433 } 434 435 if len(v.Tainted) > 0 { 436 return true 437 } 438 } 439 440 // We didn't find any tainted nodes, return 441 return false 442 } 443 444 func (n *graphNodeResourceDestroy) destroyIncludePrimary( 445 d *ModuleDiff, s *ModuleState) bool { 446 // Get the count, and specifically the raw value of the count 447 // (with interpolations and all). If the count is NOT a static "1", 448 // then we keep the destroy node no matter what. 449 // 450 // The reasoning for this is complicated and not intuitively obvious, 451 // but I attempt to explain it below. 452 // 453 // The destroy transform works by generating the worst case graph, 454 // with worst case being the case that every resource already exists 455 // and needs to be destroy/created (force-new). There is a single important 456 // edge case where this actually results in a real-life cycle: if a 457 // create-before-destroy (CBD) resource depends on a non-CBD resource. 458 // Imagine a EC2 instance "foo" with CBD depending on a security 459 // group "bar" without CBD, and conceptualize the worst case destroy 460 // order: 461 // 462 // 1.) SG must be destroyed (non-CBD) 463 // 2.) SG must be created/updated 464 // 3.) EC2 instance must be created (CBD, requires the SG be made) 465 // 4.) EC2 instance must be destroyed (requires SG be destroyed) 466 // 467 // Except, #1 depends on #4, since the SG can't be destroyed while 468 // an EC2 instance is using it (AWS API requirements). As you can see, 469 // this is a real life cycle that can't be automatically reconciled 470 // except under two conditions: 471 // 472 // 1.) SG is also CBD. This doesn't work 100% of the time though 473 // since the non-CBD resource might not support CBD. To make matters 474 // worse, the entire transitive closure of dependencies must be 475 // CBD (if the SG depends on a VPC, you have the same problem). 476 // 2.) EC2 must not CBD. This can't happen automatically because CBD 477 // is used as a way to ensure zero (or minimal) downtime Terraform 478 // applies, and it isn't acceptable for TF to ignore this request, 479 // since it can result in unexpected downtime. 480 // 481 // Therefore, we compromise with this edge case here: if there is 482 // a static count of "1", we prune the diff to remove cycles during a 483 // graph optimization path if we don't see the resource in the diff. 484 // If the count is set to ANYTHING other than a static "1" (variable, 485 // computed attribute, static number greater than 1), then we keep the 486 // destroy, since it is required for dynamic graph expansion to find 487 // orphan/tainted count objects. 488 // 489 // This isn't ideal logic, but its strictly better without introducing 490 // new impossibilities. It breaks the cycle in practical cases, and the 491 // cycle comes back in no cases we've found to be practical, but just 492 // as the cycle would already exist without this anyways. 493 count := n.Original.Resource.RawCount 494 if raw := count.Raw[count.Key]; raw != "1" { 495 return true 496 } 497 498 // Okay, we're dealing with a static count. There are a few ways 499 // to include this resource. 500 prefix := n.Original.Resource.Id() 501 502 // If we're present in the diff proper, then keep it. We're looking 503 // only for resources in the diff that match our resource or a count-index 504 // of our resource that are marked for destroy. 505 if d != nil { 506 for k, d := range d.Resources { 507 match := k == prefix || strings.HasPrefix(k, prefix+".") 508 if match && d.Destroy { 509 return true 510 } 511 } 512 } 513 514 // If we're in the state as a primary in any form, then keep it. 515 // This does a prefix check so it will also catch orphans on count 516 // decreases to "1". 517 if s != nil { 518 for k, v := range s.Resources { 519 // Ignore exact matches 520 if k == prefix { 521 continue 522 } 523 524 // Ignore anything that doesn't have a "." afterwards so that 525 // we only get our own resource and any counts on it. 526 if !strings.HasPrefix(k, prefix+".") { 527 continue 528 } 529 530 // Ignore exact matches and the 0'th index. We only care 531 // about if there is a decrease in count. 532 if k == prefix+".0" { 533 continue 534 } 535 536 if v.Primary != nil { 537 return true 538 } 539 } 540 541 // If we're in the state as _both_ "foo" and "foo.0", then 542 // keep it, since we treat the latter as an orphan. 543 _, okOne := s.Resources[prefix] 544 _, okTwo := s.Resources[prefix+".0"] 545 if okOne && okTwo { 546 return true 547 } 548 } 549 550 return false 551 }