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