github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/terraform/node_resource_abstract.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/iaas-resource-provision/iaas-rpc/internal/addrs" 8 "github.com/iaas-resource-provision/iaas-rpc/internal/configs" 9 "github.com/iaas-resource-provision/iaas-rpc/internal/configs/configschema" 10 "github.com/iaas-resource-provision/iaas-rpc/internal/dag" 11 "github.com/iaas-resource-provision/iaas-rpc/internal/lang" 12 "github.com/iaas-resource-provision/iaas-rpc/internal/states" 13 "github.com/iaas-resource-provision/iaas-rpc/internal/tfdiags" 14 ) 15 16 // ConcreteResourceNodeFunc is a callback type used to convert an 17 // abstract resource to a concrete one of some type. 18 type ConcreteResourceNodeFunc func(*NodeAbstractResource) dag.Vertex 19 20 // GraphNodeConfigResource is implemented by any nodes that represent a resource. 21 // The type of operation cannot be assumed, only that this node represents 22 // the given resource. 23 type GraphNodeConfigResource interface { 24 ResourceAddr() addrs.ConfigResource 25 } 26 27 // ConcreteResourceInstanceNodeFunc is a callback type used to convert an 28 // abstract resource instance to a concrete one of some type. 29 type ConcreteResourceInstanceNodeFunc func(*NodeAbstractResourceInstance) dag.Vertex 30 31 // GraphNodeResourceInstance is implemented by any nodes that represent 32 // a resource instance. A single resource may have multiple instances if, 33 // for example, the "count" or "for_each" argument is used for it in 34 // configuration. 35 type GraphNodeResourceInstance interface { 36 ResourceInstanceAddr() addrs.AbsResourceInstance 37 38 // StateDependencies returns any inter-resource dependencies that are 39 // stored in the state. 40 StateDependencies() []addrs.ConfigResource 41 } 42 43 // NodeAbstractResource represents a resource that has no associated 44 // operations. It registers all the interfaces for a resource that common 45 // across multiple operation types. 46 type NodeAbstractResource struct { 47 Addr addrs.ConfigResource 48 49 // The fields below will be automatically set using the Attach 50 // interfaces if you're running those transforms, but also be explicitly 51 // set if you already have that information. 52 53 Schema *configschema.Block // Schema for processing the configuration body 54 SchemaVersion uint64 // Schema version of "Schema", as decided by the provider 55 Config *configs.Resource // Config is the resource in the config 56 57 // ProviderMetas is the provider_meta configs for the module this resource belongs to 58 ProviderMetas map[addrs.Provider]*configs.ProviderMeta 59 60 ProvisionerSchemas map[string]*configschema.Block 61 62 // Set from GraphNodeTargetable 63 Targets []addrs.Targetable 64 65 // Set from AttachDataResourceDependsOn 66 dependsOn []addrs.ConfigResource 67 forceDependsOn bool 68 69 // The address of the provider this resource will use 70 ResolvedProvider addrs.AbsProviderConfig 71 } 72 73 var ( 74 _ GraphNodeReferenceable = (*NodeAbstractResource)(nil) 75 _ GraphNodeReferencer = (*NodeAbstractResource)(nil) 76 _ GraphNodeProviderConsumer = (*NodeAbstractResource)(nil) 77 _ GraphNodeProvisionerConsumer = (*NodeAbstractResource)(nil) 78 _ GraphNodeConfigResource = (*NodeAbstractResource)(nil) 79 _ GraphNodeAttachResourceConfig = (*NodeAbstractResource)(nil) 80 _ GraphNodeAttachResourceSchema = (*NodeAbstractResource)(nil) 81 _ GraphNodeAttachProvisionerSchema = (*NodeAbstractResource)(nil) 82 _ GraphNodeAttachProviderMetaConfigs = (*NodeAbstractResource)(nil) 83 _ GraphNodeTargetable = (*NodeAbstractResource)(nil) 84 _ graphNodeAttachDataResourceDependsOn = (*NodeAbstractResource)(nil) 85 _ dag.GraphNodeDotter = (*NodeAbstractResource)(nil) 86 ) 87 88 // NewNodeAbstractResource creates an abstract resource graph node for 89 // the given absolute resource address. 90 func NewNodeAbstractResource(addr addrs.ConfigResource) *NodeAbstractResource { 91 return &NodeAbstractResource{ 92 Addr: addr, 93 } 94 } 95 96 var ( 97 _ GraphNodeModuleInstance = (*NodeAbstractResourceInstance)(nil) 98 _ GraphNodeReferenceable = (*NodeAbstractResourceInstance)(nil) 99 _ GraphNodeReferencer = (*NodeAbstractResourceInstance)(nil) 100 _ GraphNodeProviderConsumer = (*NodeAbstractResourceInstance)(nil) 101 _ GraphNodeProvisionerConsumer = (*NodeAbstractResourceInstance)(nil) 102 _ GraphNodeConfigResource = (*NodeAbstractResourceInstance)(nil) 103 _ GraphNodeResourceInstance = (*NodeAbstractResourceInstance)(nil) 104 _ GraphNodeAttachResourceState = (*NodeAbstractResourceInstance)(nil) 105 _ GraphNodeAttachResourceConfig = (*NodeAbstractResourceInstance)(nil) 106 _ GraphNodeAttachResourceSchema = (*NodeAbstractResourceInstance)(nil) 107 _ GraphNodeAttachProvisionerSchema = (*NodeAbstractResourceInstance)(nil) 108 _ GraphNodeAttachProviderMetaConfigs = (*NodeAbstractResourceInstance)(nil) 109 _ GraphNodeTargetable = (*NodeAbstractResourceInstance)(nil) 110 _ dag.GraphNodeDotter = (*NodeAbstractResourceInstance)(nil) 111 ) 112 113 func (n *NodeAbstractResource) Name() string { 114 return n.ResourceAddr().String() 115 } 116 117 // GraphNodeModulePath 118 func (n *NodeAbstractResource) ModulePath() addrs.Module { 119 return n.Addr.Module 120 } 121 122 // GraphNodeReferenceable 123 func (n *NodeAbstractResource) ReferenceableAddrs() []addrs.Referenceable { 124 return []addrs.Referenceable{n.Addr.Resource} 125 } 126 127 // GraphNodeReferencer 128 func (n *NodeAbstractResource) References() []*addrs.Reference { 129 // If we have a config then we prefer to use that. 130 if c := n.Config; c != nil { 131 var result []*addrs.Reference 132 133 result = append(result, n.DependsOn()...) 134 135 if n.Schema == nil { 136 // Should never happen, but we'll log if it does so that we can 137 // see this easily when debugging. 138 log.Printf("[WARN] no schema is attached to %s, so config references cannot be detected", n.Name()) 139 } 140 141 refs, _ := lang.ReferencesInExpr(c.Count) 142 result = append(result, refs...) 143 refs, _ = lang.ReferencesInExpr(c.ForEach) 144 result = append(result, refs...) 145 146 // ReferencesInBlock() requires a schema 147 if n.Schema != nil { 148 refs, _ = lang.ReferencesInBlock(c.Config, n.Schema) 149 } 150 151 result = append(result, refs...) 152 if c.Managed != nil { 153 if c.Managed.Connection != nil { 154 refs, _ = lang.ReferencesInBlock(c.Managed.Connection.Config, connectionBlockSupersetSchema) 155 result = append(result, refs...) 156 } 157 158 for _, p := range c.Managed.Provisioners { 159 if p.When != configs.ProvisionerWhenCreate { 160 continue 161 } 162 if p.Connection != nil { 163 refs, _ = lang.ReferencesInBlock(p.Connection.Config, connectionBlockSupersetSchema) 164 result = append(result, refs...) 165 } 166 167 schema := n.ProvisionerSchemas[p.Type] 168 if schema == nil { 169 log.Printf("[WARN] no schema for provisioner %q is attached to %s, so provisioner block references cannot be detected", p.Type, n.Name()) 170 } 171 refs, _ = lang.ReferencesInBlock(p.Config, schema) 172 result = append(result, refs...) 173 } 174 } 175 return result 176 } 177 178 // Otherwise, we have no references. 179 return nil 180 } 181 182 func (n *NodeAbstractResource) DependsOn() []*addrs.Reference { 183 var result []*addrs.Reference 184 if c := n.Config; c != nil { 185 186 for _, traversal := range c.DependsOn { 187 ref, diags := addrs.ParseRef(traversal) 188 if diags.HasErrors() { 189 // We ignore this here, because this isn't a suitable place to return 190 // errors. This situation should be caught and rejected during 191 // validation. 192 log.Printf("[ERROR] Can't parse %#v from depends_on as reference: %s", traversal, diags.Err()) 193 continue 194 } 195 196 result = append(result, ref) 197 } 198 } 199 return result 200 } 201 202 func (n *NodeAbstractResource) SetProvider(p addrs.AbsProviderConfig) { 203 n.ResolvedProvider = p 204 } 205 206 // GraphNodeProviderConsumer 207 func (n *NodeAbstractResource) ProvidedBy() (addrs.ProviderConfig, bool) { 208 // If we have a config we prefer that above all else 209 if n.Config != nil { 210 relAddr := n.Config.ProviderConfigAddr() 211 return addrs.LocalProviderConfig{ 212 LocalName: relAddr.LocalName, 213 Alias: relAddr.Alias, 214 }, false 215 } 216 217 // No provider configuration found; return a default address 218 return addrs.AbsProviderConfig{ 219 Provider: n.Provider(), 220 Module: n.ModulePath(), 221 }, false 222 } 223 224 // GraphNodeProviderConsumer 225 func (n *NodeAbstractResource) Provider() addrs.Provider { 226 if n.Config != nil { 227 return n.Config.Provider 228 } 229 return addrs.ImpliedProviderForUnqualifiedType(n.Addr.Resource.ImpliedProvider()) 230 } 231 232 // GraphNodeProvisionerConsumer 233 func (n *NodeAbstractResource) ProvisionedBy() []string { 234 // If we have no configuration, then we have no provisioners 235 if n.Config == nil || n.Config.Managed == nil { 236 return nil 237 } 238 239 // Build the list of provisioners we need based on the configuration. 240 // It is okay to have duplicates here. 241 result := make([]string, len(n.Config.Managed.Provisioners)) 242 for i, p := range n.Config.Managed.Provisioners { 243 result[i] = p.Type 244 } 245 246 return result 247 } 248 249 // GraphNodeProvisionerConsumer 250 func (n *NodeAbstractResource) AttachProvisionerSchema(name string, schema *configschema.Block) { 251 if n.ProvisionerSchemas == nil { 252 n.ProvisionerSchemas = make(map[string]*configschema.Block) 253 } 254 n.ProvisionerSchemas[name] = schema 255 } 256 257 // GraphNodeResource 258 func (n *NodeAbstractResource) ResourceAddr() addrs.ConfigResource { 259 return n.Addr 260 } 261 262 // GraphNodeTargetable 263 func (n *NodeAbstractResource) SetTargets(targets []addrs.Targetable) { 264 n.Targets = targets 265 } 266 267 // graphNodeAttachDataResourceDependsOn 268 func (n *NodeAbstractResource) AttachDataResourceDependsOn(deps []addrs.ConfigResource, force bool) { 269 n.dependsOn = deps 270 n.forceDependsOn = force 271 } 272 273 // GraphNodeAttachResourceConfig 274 func (n *NodeAbstractResource) AttachResourceConfig(c *configs.Resource) { 275 n.Config = c 276 } 277 278 // GraphNodeAttachResourceSchema impl 279 func (n *NodeAbstractResource) AttachResourceSchema(schema *configschema.Block, version uint64) { 280 n.Schema = schema 281 n.SchemaVersion = version 282 } 283 284 // GraphNodeAttachProviderMetaConfigs impl 285 func (n *NodeAbstractResource) AttachProviderMetaConfigs(c map[addrs.Provider]*configs.ProviderMeta) { 286 n.ProviderMetas = c 287 } 288 289 // GraphNodeDotter impl. 290 func (n *NodeAbstractResource) DotNode(name string, opts *dag.DotOpts) *dag.DotNode { 291 return &dag.DotNode{ 292 Name: name, 293 Attrs: map[string]string{ 294 "label": n.Name(), 295 "shape": "box", 296 }, 297 } 298 } 299 300 // writeResourceState ensures that a suitable resource-level state record is 301 // present in the state, if that's required for the "each mode" of that 302 // resource. 303 // 304 // This is important primarily for the situation where count = 0, since this 305 // eval is the only change we get to set the resource "each mode" to list 306 // in that case, allowing expression evaluation to see it as a zero-element list 307 // rather than as not set at all. 308 func (n *NodeAbstractResource) writeResourceState(ctx EvalContext, addr addrs.AbsResource) (diags tfdiags.Diagnostics) { 309 state := ctx.State() 310 311 // We'll record our expansion decision in the shared "expander" object 312 // so that later operations (i.e. DynamicExpand and expression evaluation) 313 // can refer to it. Since this node represents the abstract module, we need 314 // to expand the module here to create all resources. 315 expander := ctx.InstanceExpander() 316 317 switch { 318 case n.Config.Count != nil: 319 count, countDiags := evaluateCountExpression(n.Config.Count, ctx) 320 diags = diags.Append(countDiags) 321 if countDiags.HasErrors() { 322 return diags 323 } 324 325 state.SetResourceProvider(addr, n.ResolvedProvider) 326 expander.SetResourceCount(addr.Module, n.Addr.Resource, count) 327 328 case n.Config.ForEach != nil: 329 forEach, forEachDiags := evaluateForEachExpression(n.Config.ForEach, ctx) 330 diags = diags.Append(forEachDiags) 331 if forEachDiags.HasErrors() { 332 return diags 333 } 334 335 // This method takes care of all of the business logic of updating this 336 // while ensuring that any existing instances are preserved, etc. 337 state.SetResourceProvider(addr, n.ResolvedProvider) 338 expander.SetResourceForEach(addr.Module, n.Addr.Resource, forEach) 339 340 default: 341 state.SetResourceProvider(addr, n.ResolvedProvider) 342 expander.SetResourceSingle(addr.Module, n.Addr.Resource) 343 } 344 345 return diags 346 } 347 348 // readResourceInstanceState reads the current object for a specific instance in 349 // the state. 350 func (n *NodeAbstractResource) readResourceInstanceState(ctx EvalContext, addr addrs.AbsResourceInstance) (*states.ResourceInstanceObject, tfdiags.Diagnostics) { 351 var diags tfdiags.Diagnostics 352 provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider) 353 if err != nil { 354 diags = diags.Append(err) 355 return nil, diags 356 } 357 358 log.Printf("[TRACE] readResourceInstanceState: reading state for %s", addr) 359 360 src := ctx.State().ResourceInstanceObject(addr, states.CurrentGen) 361 if src == nil { 362 // Presumably we only have deposed objects, then. 363 log.Printf("[TRACE] readResourceInstanceState: no state present for %s", addr) 364 return nil, nil 365 } 366 367 schema, currentVersion := (providerSchema).SchemaForResourceAddr(addr.Resource.ContainingResource()) 368 if schema == nil { 369 // Shouldn't happen since we should've failed long ago if no schema is present 370 return nil, diags.Append(fmt.Errorf("no schema available for %s while reading state; this is a bug in Terraform and should be reported", addr)) 371 } 372 src, upgradeDiags := upgradeResourceState(addr, provider, src, schema, currentVersion) 373 if n.Config != nil { 374 upgradeDiags = upgradeDiags.InConfigBody(n.Config.Config, addr.String()) 375 } 376 diags = diags.Append(upgradeDiags) 377 if diags.HasErrors() { 378 // Note that we don't have any channel to return warnings here. We'll 379 // accept that for now since warnings during a schema upgrade would 380 // be pretty weird anyway, since this operation is supposed to seem 381 // invisible to the user. 382 return nil, diags 383 } 384 385 obj, err := src.Decode(schema.ImpliedType()) 386 if err != nil { 387 diags = diags.Append(err) 388 } 389 390 return obj, diags 391 } 392 393 // readResourceInstanceStateDeposed reads the deposed object for a specific 394 // instance in the state. 395 func (n *NodeAbstractResource) readResourceInstanceStateDeposed(ctx EvalContext, addr addrs.AbsResourceInstance, key states.DeposedKey) (*states.ResourceInstanceObject, tfdiags.Diagnostics) { 396 var diags tfdiags.Diagnostics 397 provider, providerSchema, err := getProvider(ctx, n.ResolvedProvider) 398 if err != nil { 399 diags = diags.Append(err) 400 return nil, diags 401 } 402 403 if key == states.NotDeposed { 404 return nil, diags.Append(fmt.Errorf("readResourceInstanceStateDeposed used with no instance key; this is a bug in Terraform and should be reported")) 405 } 406 407 log.Printf("[TRACE] readResourceInstanceStateDeposed: reading state for %s deposed object %s", addr, key) 408 409 src := ctx.State().ResourceInstanceObject(addr, key) 410 if src == nil { 411 // Presumably we only have deposed objects, then. 412 log.Printf("[TRACE] readResourceInstanceStateDeposed: no state present for %s deposed object %s", addr, key) 413 return nil, diags 414 } 415 416 schema, currentVersion := (providerSchema).SchemaForResourceAddr(addr.Resource.ContainingResource()) 417 if schema == nil { 418 // Shouldn't happen since we should've failed long ago if no schema is present 419 return nil, diags.Append(fmt.Errorf("no schema available for %s while reading state; this is a bug in Terraform and should be reported", addr)) 420 421 } 422 423 src, upgradeDiags := upgradeResourceState(addr, provider, src, schema, currentVersion) 424 if n.Config != nil { 425 upgradeDiags = upgradeDiags.InConfigBody(n.Config.Config, addr.String()) 426 } 427 diags = diags.Append(upgradeDiags) 428 if diags.HasErrors() { 429 // Note that we don't have any channel to return warnings here. We'll 430 // accept that for now since warnings during a schema upgrade would 431 // be pretty weird anyway, since this operation is supposed to seem 432 // invisible to the user. 433 return nil, diags 434 } 435 436 obj, err := src.Decode(schema.ImpliedType()) 437 if err != nil { 438 diags = diags.Append(err) 439 } 440 441 return obj, diags 442 } 443 444 // graphNodesAreResourceInstancesInDifferentInstancesOfSameModule is an 445 // annoyingly-task-specific helper function that returns true if and only if 446 // the following conditions hold: 447 // - Both of the given vertices represent specific resource instances, as 448 // opposed to unexpanded resources or any other non-resource-related object. 449 // - The module instance addresses for both of the resource instances belong 450 // to the same static module. 451 // - The module instance addresses for both of the resource instances are 452 // not equal, indicating that they belong to different instances of the 453 // same module. 454 // 455 // This result can be used as a way to compensate for the effects of 456 // conservative analyses passes in our graph builders which make their 457 // decisions based only on unexpanded addresses, often so that they can behave 458 // correctly for interactions between expanded and not-yet-expanded objects. 459 // 460 // Callers of this helper function will typically skip adding an edge between 461 // the two given nodes if this function returns true. 462 func graphNodesAreResourceInstancesInDifferentInstancesOfSameModule(a, b dag.Vertex) bool { 463 aRI, aOK := a.(GraphNodeResourceInstance) 464 bRI, bOK := b.(GraphNodeResourceInstance) 465 if !(aOK && bOK) { 466 return false 467 } 468 aModInst := aRI.ResourceInstanceAddr().Module 469 bModInst := bRI.ResourceInstanceAddr().Module 470 aMod := aModInst.Module() 471 bMod := bModInst.Module() 472 if !aMod.Equal(bMod) { 473 return false 474 } 475 return !aModInst.Equal(bModInst) 476 }