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