github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/node_resource_abstract.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 "sort" 7 8 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 9 "github.com/hashicorp/terraform-plugin-sdk/internal/configs" 10 "github.com/hashicorp/terraform-plugin-sdk/internal/configs/configschema" 11 "github.com/hashicorp/terraform-plugin-sdk/internal/dag" 12 "github.com/hashicorp/terraform-plugin-sdk/internal/lang" 13 "github.com/hashicorp/terraform-plugin-sdk/internal/states" 14 "github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags" 15 ) 16 17 // ConcreteResourceNodeFunc is a callback type used to convert an 18 // abstract resource to a concrete one of some type. 19 type ConcreteResourceNodeFunc func(*NodeAbstractResource) dag.Vertex 20 21 // GraphNodeResource is implemented by any nodes that represent a resource. 22 // The type of operation cannot be assumed, only that this node represents 23 // the given resource. 24 type GraphNodeResource interface { 25 ResourceAddr() addrs.AbsResource 26 } 27 28 // ConcreteResourceInstanceNodeFunc is a callback type used to convert an 29 // abstract resource instance to a concrete one of some type. 30 type ConcreteResourceInstanceNodeFunc func(*NodeAbstractResourceInstance) dag.Vertex 31 32 // GraphNodeResourceInstance is implemented by any nodes that represent 33 // a resource instance. A single resource may have multiple instances if, 34 // for example, the "count" or "for_each" argument is used for it in 35 // configuration. 36 type GraphNodeResourceInstance interface { 37 ResourceInstanceAddr() addrs.AbsResourceInstance 38 } 39 40 // NodeAbstractResource represents a resource that has no associated 41 // operations. It registers all the interfaces for a resource that common 42 // across multiple operation types. 43 type NodeAbstractResource struct { 44 Addr addrs.AbsResource // Addr is the address for this resource 45 46 // The fields below will be automatically set using the Attach 47 // interfaces if you're running those transforms, but also be explicitly 48 // set if you already have that information. 49 50 Schema *configschema.Block // Schema for processing the configuration body 51 SchemaVersion uint64 // Schema version of "Schema", as decided by the provider 52 Config *configs.Resource // Config is the resource in the config 53 54 ProvisionerSchemas map[string]*configschema.Block 55 56 Targets []addrs.Targetable // Set from GraphNodeTargetable 57 58 // The address of the provider this resource will use 59 ResolvedProvider addrs.AbsProviderConfig 60 } 61 62 var ( 63 _ GraphNodeSubPath = (*NodeAbstractResource)(nil) 64 _ GraphNodeReferenceable = (*NodeAbstractResource)(nil) 65 _ GraphNodeReferencer = (*NodeAbstractResource)(nil) 66 _ GraphNodeProviderConsumer = (*NodeAbstractResource)(nil) 67 _ GraphNodeProvisionerConsumer = (*NodeAbstractResource)(nil) 68 _ GraphNodeResource = (*NodeAbstractResource)(nil) 69 _ GraphNodeAttachResourceConfig = (*NodeAbstractResource)(nil) 70 _ GraphNodeAttachResourceSchema = (*NodeAbstractResource)(nil) 71 _ GraphNodeAttachProvisionerSchema = (*NodeAbstractResource)(nil) 72 _ GraphNodeTargetable = (*NodeAbstractResource)(nil) 73 _ dag.GraphNodeDotter = (*NodeAbstractResource)(nil) 74 ) 75 76 // NewNodeAbstractResource creates an abstract resource graph node for 77 // the given absolute resource address. 78 func NewNodeAbstractResource(addr addrs.AbsResource) *NodeAbstractResource { 79 return &NodeAbstractResource{ 80 Addr: addr, 81 } 82 } 83 84 // NodeAbstractResourceInstance represents a resource instance with no 85 // associated operations. It embeds NodeAbstractResource but additionally 86 // contains an instance key, used to identify one of potentially many 87 // instances that were created from a resource in configuration, e.g. using 88 // the "count" or "for_each" arguments. 89 type NodeAbstractResourceInstance struct { 90 NodeAbstractResource 91 InstanceKey addrs.InstanceKey 92 93 // The fields below will be automatically set using the Attach 94 // interfaces if you're running those transforms, but also be explicitly 95 // set if you already have that information. 96 97 ResourceState *states.Resource 98 } 99 100 var ( 101 _ GraphNodeSubPath = (*NodeAbstractResourceInstance)(nil) 102 _ GraphNodeReferenceable = (*NodeAbstractResourceInstance)(nil) 103 _ GraphNodeReferencer = (*NodeAbstractResourceInstance)(nil) 104 _ GraphNodeProviderConsumer = (*NodeAbstractResourceInstance)(nil) 105 _ GraphNodeProvisionerConsumer = (*NodeAbstractResourceInstance)(nil) 106 _ GraphNodeResource = (*NodeAbstractResourceInstance)(nil) 107 _ GraphNodeResourceInstance = (*NodeAbstractResourceInstance)(nil) 108 _ GraphNodeAttachResourceState = (*NodeAbstractResourceInstance)(nil) 109 _ GraphNodeAttachResourceConfig = (*NodeAbstractResourceInstance)(nil) 110 _ GraphNodeAttachResourceSchema = (*NodeAbstractResourceInstance)(nil) 111 _ GraphNodeAttachProvisionerSchema = (*NodeAbstractResourceInstance)(nil) 112 _ GraphNodeTargetable = (*NodeAbstractResourceInstance)(nil) 113 _ dag.GraphNodeDotter = (*NodeAbstractResourceInstance)(nil) 114 ) 115 116 // NewNodeAbstractResourceInstance creates an abstract resource instance graph 117 // node for the given absolute resource instance address. 118 func NewNodeAbstractResourceInstance(addr addrs.AbsResourceInstance) *NodeAbstractResourceInstance { 119 // Due to the fact that we embed NodeAbstractResource, the given address 120 // actually ends up split between the resource address in the embedded 121 // object and the InstanceKey field in our own struct. The 122 // ResourceInstanceAddr method will stick these back together again on 123 // request. 124 return &NodeAbstractResourceInstance{ 125 NodeAbstractResource: NodeAbstractResource{ 126 Addr: addr.ContainingResource(), 127 }, 128 InstanceKey: addr.Resource.Key, 129 } 130 } 131 132 func (n *NodeAbstractResource) Name() string { 133 return n.ResourceAddr().String() 134 } 135 136 func (n *NodeAbstractResourceInstance) Name() string { 137 return n.ResourceInstanceAddr().String() 138 } 139 140 // GraphNodeSubPath 141 func (n *NodeAbstractResource) Path() addrs.ModuleInstance { 142 return n.Addr.Module 143 } 144 145 // GraphNodeReferenceable 146 func (n *NodeAbstractResource) ReferenceableAddrs() []addrs.Referenceable { 147 return []addrs.Referenceable{n.Addr.Resource} 148 } 149 150 // GraphNodeReferenceable 151 func (n *NodeAbstractResourceInstance) ReferenceableAddrs() []addrs.Referenceable { 152 addr := n.ResourceInstanceAddr() 153 return []addrs.Referenceable{ 154 addr.Resource, 155 156 // A resource instance can also be referenced by the address of its 157 // containing resource, so that e.g. a reference to aws_instance.foo 158 // would match both aws_instance.foo[0] and aws_instance.foo[1]. 159 addr.ContainingResource().Resource, 160 } 161 } 162 163 // GraphNodeReferencer 164 func (n *NodeAbstractResource) References() []*addrs.Reference { 165 // If we have a config then we prefer to use that. 166 if c := n.Config; c != nil { 167 var result []*addrs.Reference 168 169 for _, traversal := range c.DependsOn { 170 ref, err := addrs.ParseRef(traversal) 171 if err != nil { 172 // We ignore this here, because this isn't a suitable place to return 173 // errors. This situation should be caught and rejected during 174 // validation. 175 log.Printf("[ERROR] Can't parse %#v from depends_on as reference: %s", traversal, err) 176 continue 177 } 178 179 result = append(result, ref) 180 } 181 182 if n.Schema == nil { 183 // Should never happens, but we'll log if it does so that we can 184 // see this easily when debugging. 185 log.Printf("[WARN] no schema is attached to %s, so config references cannot be detected", n.Name()) 186 } 187 188 refs, _ := lang.ReferencesInExpr(c.Count) 189 result = append(result, refs...) 190 refs, _ = lang.ReferencesInExpr(c.ForEach) 191 result = append(result, refs...) 192 refs, _ = lang.ReferencesInBlock(c.Config, n.Schema) 193 result = append(result, refs...) 194 if c.Managed != nil { 195 for _, p := range c.Managed.Provisioners { 196 if p.When != configs.ProvisionerWhenCreate { 197 continue 198 } 199 if p.Connection != nil { 200 refs, _ = lang.ReferencesInBlock(p.Connection.Config, connectionBlockSupersetSchema) 201 result = append(result, refs...) 202 } 203 204 schema := n.ProvisionerSchemas[p.Type] 205 if schema == nil { 206 log.Printf("[WARN] no schema for provisioner %q is attached to %s, so provisioner block references cannot be detected", p.Type, n.Name()) 207 } 208 refs, _ = lang.ReferencesInBlock(p.Config, schema) 209 result = append(result, refs...) 210 } 211 } 212 return result 213 } 214 215 // Otherwise, we have no references. 216 return nil 217 } 218 219 // GraphNodeReferencer 220 func (n *NodeAbstractResourceInstance) References() []*addrs.Reference { 221 // If we have a configuration attached then we'll delegate to our 222 // embedded abstract resource, which knows how to extract dependencies 223 // from configuration. 224 if n.Config != nil { 225 if n.Schema == nil { 226 // We'll produce a log message about this out here so that 227 // we can include the full instance address, since the equivalent 228 // message in NodeAbstractResource.References cannot see it. 229 log.Printf("[WARN] no schema is attached to %s, so config references cannot be detected", n.Name()) 230 return nil 231 } 232 return n.NodeAbstractResource.References() 233 } 234 235 // Otherwise, if we have state then we'll use the values stored in state 236 // as a fallback. 237 if rs := n.ResourceState; rs != nil { 238 if s := rs.Instance(n.InstanceKey); s != nil { 239 // State is still storing dependencies as old-style strings, so we'll 240 // need to do a little work here to massage this to the form we now 241 // want. 242 var result []*addrs.Reference 243 244 // It is (apparently) possible for s.Current to be nil. This proved 245 // difficult to reproduce, so we will fix the symptom here and hope 246 // to find the root cause another time. 247 // 248 // https://github.com/hashicorp/terraform-plugin-sdk/issues/21407 249 if s.Current == nil { 250 log.Printf("[WARN] no current state found for %s", n.Name()) 251 } else { 252 for _, addr := range s.Current.Dependencies { 253 if addr == nil { 254 // Should never happen; indicates a bug in the state loader 255 panic(fmt.Sprintf("dependencies for current object on %s contains nil address", n.ResourceInstanceAddr())) 256 } 257 258 // This is a little weird: we need to manufacture an addrs.Reference 259 // with a fake range here because the state isn't something we can 260 // make source references into. 261 result = append(result, &addrs.Reference{ 262 Subject: addr, 263 SourceRange: tfdiags.SourceRange{ 264 Filename: "(state file)", 265 }, 266 }) 267 } 268 } 269 return result 270 } 271 } 272 273 // If we have neither config nor state then we have no references. 274 return nil 275 } 276 277 // StateReferences returns the dependencies to put into the state for 278 // this resource. 279 func (n *NodeAbstractResourceInstance) StateReferences() []addrs.Referenceable { 280 selfAddrs := n.ReferenceableAddrs() 281 282 // Since we don't include the source location references in our 283 // results from this method, we'll also filter out duplicates: 284 // there's no point in listing the same object twice without 285 // that additional context. 286 seen := map[string]struct{}{} 287 288 // Pretend that we've already "seen" all of our own addresses so that we 289 // won't record self-references in the state. This can arise if, for 290 // example, a provisioner for a resource refers to the resource itself, 291 // which is valid (since provisioners always run after apply) but should 292 // not create an explicit dependency edge. 293 for _, selfAddr := range selfAddrs { 294 seen[selfAddr.String()] = struct{}{} 295 if riAddr, ok := selfAddr.(addrs.ResourceInstance); ok { 296 seen[riAddr.ContainingResource().String()] = struct{}{} 297 } 298 } 299 300 depsRaw := n.References() 301 deps := make([]addrs.Referenceable, 0, len(depsRaw)) 302 for _, d := range depsRaw { 303 subj := d.Subject 304 if mco, isOutput := subj.(addrs.ModuleCallOutput); isOutput { 305 // For state dependencies, we simplify outputs to just refer 306 // to the module as a whole. It's not really clear why we do this, 307 // but this logic is preserved from before the 0.12 rewrite of 308 // this function. 309 subj = mco.Call 310 } 311 312 k := subj.String() 313 if _, exists := seen[k]; exists { 314 continue 315 } 316 seen[k] = struct{}{} 317 switch tr := subj.(type) { 318 case addrs.ResourceInstance: 319 deps = append(deps, tr) 320 case addrs.Resource: 321 deps = append(deps, tr) 322 case addrs.ModuleCallInstance: 323 deps = append(deps, tr) 324 default: 325 // No other reference types are recorded in the state. 326 } 327 } 328 329 // We'll also sort them, since that'll avoid creating changes in the 330 // serialized state that make no semantic difference. 331 sort.Slice(deps, func(i, j int) bool { 332 // Simple string-based sort because we just care about consistency, 333 // not user-friendliness. 334 return deps[i].String() < deps[j].String() 335 }) 336 337 return deps 338 } 339 340 func (n *NodeAbstractResource) SetProvider(p addrs.AbsProviderConfig) { 341 n.ResolvedProvider = p 342 } 343 344 // GraphNodeProviderConsumer 345 func (n *NodeAbstractResource) ProvidedBy() (addrs.AbsProviderConfig, bool) { 346 // If we have a config we prefer that above all else 347 if n.Config != nil { 348 relAddr := n.Config.ProviderConfigAddr() 349 return relAddr.Absolute(n.Path()), false 350 } 351 352 // Use our type and containing module path to guess a provider configuration address 353 return n.Addr.Resource.DefaultProviderConfig().Absolute(n.Addr.Module), false 354 } 355 356 // GraphNodeProviderConsumer 357 func (n *NodeAbstractResourceInstance) ProvidedBy() (addrs.AbsProviderConfig, bool) { 358 // If we have a config we prefer that above all else 359 if n.Config != nil { 360 relAddr := n.Config.ProviderConfigAddr() 361 return relAddr.Absolute(n.Path()), false 362 } 363 364 // If we have state, then we will use the provider from there 365 if n.ResourceState != nil { 366 // An address from the state must match exactly, since we must ensure 367 // we refresh/destroy a resource with the same provider configuration 368 // that created it. 369 return n.ResourceState.ProviderConfig, true 370 } 371 372 // Use our type and containing module path to guess a provider configuration address 373 return n.Addr.Resource.DefaultProviderConfig().Absolute(n.Path()), false 374 } 375 376 // GraphNodeProvisionerConsumer 377 func (n *NodeAbstractResource) ProvisionedBy() []string { 378 // If we have no configuration, then we have no provisioners 379 if n.Config == nil || n.Config.Managed == nil { 380 return nil 381 } 382 383 // Build the list of provisioners we need based on the configuration. 384 // It is okay to have duplicates here. 385 result := make([]string, len(n.Config.Managed.Provisioners)) 386 for i, p := range n.Config.Managed.Provisioners { 387 result[i] = p.Type 388 } 389 390 return result 391 } 392 393 // GraphNodeProvisionerConsumer 394 func (n *NodeAbstractResource) AttachProvisionerSchema(name string, schema *configschema.Block) { 395 if n.ProvisionerSchemas == nil { 396 n.ProvisionerSchemas = make(map[string]*configschema.Block) 397 } 398 n.ProvisionerSchemas[name] = schema 399 } 400 401 // GraphNodeResource 402 func (n *NodeAbstractResource) ResourceAddr() addrs.AbsResource { 403 return n.Addr 404 } 405 406 // GraphNodeResourceInstance 407 func (n *NodeAbstractResourceInstance) ResourceInstanceAddr() addrs.AbsResourceInstance { 408 return n.NodeAbstractResource.Addr.Instance(n.InstanceKey) 409 } 410 411 // GraphNodeAddressable, TODO: remove, used by target, should unify 412 func (n *NodeAbstractResource) ResourceAddress() *ResourceAddress { 413 return NewLegacyResourceAddress(n.Addr) 414 } 415 416 // GraphNodeTargetable 417 func (n *NodeAbstractResource) SetTargets(targets []addrs.Targetable) { 418 n.Targets = targets 419 } 420 421 // GraphNodeAttachResourceState 422 func (n *NodeAbstractResourceInstance) AttachResourceState(s *states.Resource) { 423 n.ResourceState = s 424 } 425 426 // GraphNodeAttachResourceConfig 427 func (n *NodeAbstractResource) AttachResourceConfig(c *configs.Resource) { 428 n.Config = c 429 } 430 431 // GraphNodeAttachResourceSchema impl 432 func (n *NodeAbstractResource) AttachResourceSchema(schema *configschema.Block, version uint64) { 433 n.Schema = schema 434 n.SchemaVersion = version 435 } 436 437 // GraphNodeDotter impl. 438 func (n *NodeAbstractResource) DotNode(name string, opts *dag.DotOpts) *dag.DotNode { 439 return &dag.DotNode{ 440 Name: name, 441 Attrs: map[string]string{ 442 "label": n.Name(), 443 "shape": "box", 444 }, 445 } 446 }