package terraform import ( "fmt" "log" "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/lang" "github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/tfdiags" ) // ConcreteResourceNodeFunc is a callback type used to convert an // abstract resource to a concrete one of some type. type ConcreteResourceNodeFunc func(*NodeAbstractResource) dag.Vertex // GraphNodeConfigResource is implemented by any nodes that represent a resource. // The type of operation cannot be assumed, only that this node represents // the given resource. type GraphNodeConfigResource interface { ResourceAddr() addrs.ConfigResource } // ConcreteResourceInstanceNodeFunc is a callback type used to convert an // abstract resource instance to a concrete one of some type. type ConcreteResourceInstanceNodeFunc func(*NodeAbstractResourceInstance) dag.Vertex // GraphNodeResourceInstance is implemented by any nodes that represent // a resource instance. A single resource may have multiple instances if, // for example, the "count" or "for_each" argument is used for it in // configuration. type GraphNodeResourceInstance interface { ResourceInstanceAddr() addrs.AbsResourceInstance // StateDependencies returns any inter-resource dependencies that are // stored in the state. StateDependencies() []addrs.ConfigResource } // NodeAbstractResource represents a resource that has no associated // operations. It registers all the interfaces for a resource that common // across multiple operation types. type NodeAbstractResource struct { Addr addrs.ConfigResource // The fields below will be automatically set using the Attach // interfaces if you're running those transforms, but also be explicitly // set if you already have that information. Schema *configschema.Block // Schema for processing the configuration body SchemaVersion uint64 // Schema version of "Schema", as decided by the provider Config *configs.Resource // Config is the resource in the config // ProviderMetas is the provider_meta configs for the module this resource belongs to ProviderMetas map[addrs.Provider]*configs.ProviderMeta ProvisionerSchemas map[string]*configschema.Block // Set from GraphNodeTargetable Targets []addrs.Targetable // Set from AttachResourceDependencies dependsOn []addrs.ConfigResource forceDependsOn bool // The address of the provider this resource will use ResolvedProvider addrs.AbsProviderConfig } var ( _ GraphNodeReferenceable = (*NodeAbstractResource)(nil) _ GraphNodeReferencer = (*NodeAbstractResource)(nil) _ GraphNodeProviderConsumer = (*NodeAbstractResource)(nil) _ GraphNodeProvisionerConsumer = (*NodeAbstractResource)(nil) _ GraphNodeConfigResource = (*NodeAbstractResource)(nil) _ GraphNodeAttachResourceConfig = (*NodeAbstractResource)(nil) _ GraphNodeAttachResourceSchema = (*NodeAbstractResource)(nil) _ GraphNodeAttachProvisionerSchema = (*NodeAbstractResource)(nil) _ GraphNodeAttachProviderMetaConfigs = (*NodeAbstractResource)(nil) _ GraphNodeTargetable = (*NodeAbstractResource)(nil) _ graphNodeAttachResourceDependencies = (*NodeAbstractResource)(nil) _ dag.GraphNodeDotter = (*NodeAbstractResource)(nil) ) // NewNodeAbstractResource creates an abstract resource graph node for // the given absolute resource address. func NewNodeAbstractResource(addr addrs.ConfigResource) *NodeAbstractResource { return &NodeAbstractResource{ Addr: addr, } } var ( _ GraphNodeModuleInstance = (*NodeAbstractResourceInstance)(nil) _ GraphNodeReferenceable = (*NodeAbstractResourceInstance)(nil) _ GraphNodeReferencer = (*NodeAbstractResourceInstance)(nil) _ GraphNodeProviderConsumer = (*NodeAbstractResourceInstance)(nil) _ GraphNodeProvisionerConsumer = (*NodeAbstractResourceInstance)(nil) _ GraphNodeConfigResource = (*NodeAbstractResourceInstance)(nil) _ GraphNodeResourceInstance = (*NodeAbstractResourceInstance)(nil) _ GraphNodeAttachResourceState = (*NodeAbstractResourceInstance)(nil) _ GraphNodeAttachResourceConfig = (*NodeAbstractResourceInstance)(nil) _ GraphNodeAttachResourceSchema = (*NodeAbstractResourceInstance)(nil) _ GraphNodeAttachProvisionerSchema = (*NodeAbstractResourceInstance)(nil) _ GraphNodeAttachProviderMetaConfigs = (*NodeAbstractResourceInstance)(nil) _ GraphNodeTargetable = (*NodeAbstractResourceInstance)(nil) _ dag.GraphNodeDotter = (*NodeAbstractResourceInstance)(nil) ) func (n *NodeAbstractResource) Name() string { return n.ResourceAddr().String() } // GraphNodeModulePath func (n *NodeAbstractResource) ModulePath() addrs.Module { return n.Addr.Module } // GraphNodeReferenceable func (n *NodeAbstractResource) ReferenceableAddrs() []addrs.Referenceable { return []addrs.Referenceable{n.Addr.Resource} } // GraphNodeReferencer func (n *NodeAbstractResource) References() []*addrs.Reference { // If we have a config then we prefer to use that. if c := n.Config; c != nil { var result []*addrs.Reference result = append(result, n.DependsOn()...) if n.Schema == nil { // Should never happen, but we'll log if it does so that we can // see this easily when debugging. log.Printf("[WARN] no schema is attached to %s, so config references cannot be detected", n.Name()) } refs, _ := lang.ReferencesInExpr(c.Count) result = append(result, refs...) refs, _ = lang.ReferencesInExpr(c.ForEach) result = append(result, refs...) // ReferencesInBlock() requires a schema if n.Schema != nil { refs, _ = lang.ReferencesInBlock(c.Config, n.Schema) } result = append(result, refs...) if c.Managed != nil { if c.Managed.Connection != nil { refs, _ = lang.ReferencesInBlock(c.Managed.Connection.Config, connectionBlockSupersetSchema) result = append(result, refs...) } for _, p := range c.Managed.Provisioners { if p.When != configs.ProvisionerWhenCreate { continue } if p.Connection != nil { refs, _ = lang.ReferencesInBlock(p.Connection.Config, connectionBlockSupersetSchema) result = append(result, refs...) } schema := n.ProvisionerSchemas[p.Type] if schema == nil { log.Printf("[WARN] no schema for provisioner %q is attached to %s, so provisioner block references cannot be detected", p.Type, n.Name()) } refs, _ = lang.ReferencesInBlock(p.Config, schema) result = append(result, refs...) } } return result } // Otherwise, we have no references. return nil } func (n *NodeAbstractResource) DependsOn() []*addrs.Reference { var result []*addrs.Reference if c := n.Config; c != nil { for _, traversal := range c.DependsOn { ref, diags := addrs.ParseRef(traversal) if diags.HasErrors() { // We ignore this here, because this isn't a suitable place to return // errors. This situation should be caught and rejected during // validation. log.Printf("[ERROR] Can't parse %#v from depends_on as reference: %s", traversal, diags.Err()) continue } result = append(result, ref) } } return result } func (n *NodeAbstractResource) SetProvider(p addrs.AbsProviderConfig) { n.ResolvedProvider = p } // GraphNodeProviderConsumer func (n *NodeAbstractResource) ProvidedBy() (addrs.ProviderConfig, bool) { // If we have a config we prefer that above all else if n.Config != nil { relAddr := n.Config.ProviderConfigAddr() return addrs.LocalProviderConfig{ LocalName: relAddr.LocalName, Alias: relAddr.Alias, }, false } // No provider configuration found; return a default address return addrs.AbsProviderConfig{ Provider: n.Provider(), Module: n.ModulePath(), }, false } // GraphNodeProviderConsumer func (n *NodeAbstractResource) Provider() addrs.Provider { if n.Config != nil { return n.Config.Provider } return addrs.ImpliedProviderForUnqualifiedType(n.Addr.Resource.ImpliedProvider()) } // GraphNodeProvisionerConsumer func (n *NodeAbstractResource) ProvisionedBy() []string { // If we have no configuration, then we have no provisioners if n.Config == nil || n.Config.Managed == nil { return nil } // Build the list of provisioners we need based on the configuration. // It is okay to have duplicates here. result := make([]string, len(n.Config.Managed.Provisioners)) for i, p := range n.Config.Managed.Provisioners { result[i] = p.Type } return result } // GraphNodeProvisionerConsumer func (n *NodeAbstractResource) AttachProvisionerSchema(name string, schema *configschema.Block) { if n.ProvisionerSchemas == nil { n.ProvisionerSchemas = make(map[string]*configschema.Block) } n.ProvisionerSchemas[name] = schema } // GraphNodeResource func (n *NodeAbstractResource) ResourceAddr() addrs.ConfigResource { return n.Addr } // GraphNodeTargetable func (n *NodeAbstractResource) SetTargets(targets []addrs.Targetable) { n.Targets = targets } // graphNodeAttachResourceDependencies func (n *NodeAbstractResource) AttachResourceDependencies(deps []addrs.ConfigResource, force bool) { n.dependsOn = deps n.forceDependsOn = force } // GraphNodeAttachResourceConfig func (n *NodeAbstractResource) AttachResourceConfig(c *configs.Resource) { n.Config = c } // GraphNodeAttachResourceSchema impl func (n *NodeAbstractResource) AttachResourceSchema(schema *configschema.Block, version uint64) { n.Schema = schema n.SchemaVersion = version } // GraphNodeAttachProviderMetaConfigs impl func (n *NodeAbstractResource) AttachProviderMetaConfigs(c map[addrs.Provider]*configs.ProviderMeta) { n.ProviderMetas = c } // GraphNodeDotter impl. func (n *NodeAbstractResource) DotNode(name string, opts *dag.DotOpts) *dag.DotNode { return &dag.DotNode{ Name: name, Attrs: map[string]string{ "label": n.Name(), "shape": "box", }, } } // writeResourceState ensures that a suitable resource-level state record is // present in the state, if that's required for the "each mode" of that // resource. // // This is important primarily for the situation where count = 0, since this // eval is the only change we get to set the resource "each mode" to list // in that case, allowing expression evaluation to see it as a zero-element list // rather than as not set at all. func (n *NodeAbstractResource) writeResourceState(ctx EvalContext, addr addrs.AbsResource) (diags tfdiags.Diagnostics) { state := ctx.State() // We'll record our expansion decision in the shared "expander" object // so that later operations (i.e. DynamicExpand and expression evaluation) // can refer to it. Since this node represents the abstract module, we need // to expand the module here to create all resources. expander := ctx.InstanceExpander() switch { case n.Config.Count != nil: count, countDiags := evaluateCountExpression(n.Config.Count, ctx) diags = diags.Append(countDiags) if countDiags.HasErrors() { return diags } state.SetResourceProvider(addr, n.ResolvedProvider) expander.SetResourceCount(addr.Module, n.Addr.Resource, count) case n.Config.ForEach != nil: forEach, forEachDiags := evaluateForEachExpression(n.Config.ForEach, ctx) diags = diags.Append(forEachDiags) if forEachDiags.HasErrors() { return diags } // This method takes care of all of the business logic of updating this // while ensuring that any existing instances are preserved, etc. state.SetResourceProvider(addr, n.ResolvedProvider) expander.SetResourceForEach(addr.Module, n.Addr.Resource, forEach) default: state.SetResourceProvider(addr, n.ResolvedProvider) expander.SetResourceSingle(addr.Module, n.Addr.Resource) } return diags } // readResourceInstanceState reads the current object for a specific instance in // the state. func (n *NodeAbstractResource) readResourceInstanceState(ctx EvalContext, addr addrs.AbsResourceInstance) (*states.ResourceInstanceObject, error) { provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider) if err != nil { return nil, err } log.Printf("[TRACE] ReadResourceInstanceState: reading state for %s", addr) src := ctx.State().ResourceInstanceObject(addr, states.CurrentGen) if src == nil { // Presumably we only have deposed objects, then. log.Printf("[TRACE] ReadResourceInstanceState: no state present for %s", addr) return nil, nil } schema, currentVersion := (providerSchema).SchemaForResourceAddr(addr.Resource.ContainingResource()) if schema == nil { // Shouldn't happen since we should've failed long ago if no schema is present return nil, fmt.Errorf("no schema available for %s while reading state; this is a bug in Terraform and should be reported", addr) } var diags tfdiags.Diagnostics src, diags = UpgradeResourceState(addr, provider, src, schema, currentVersion) if diags.HasErrors() { // Note that we don't have any channel to return warnings here. We'll // accept that for now since warnings during a schema upgrade would // be pretty weird anyway, since this operation is supposed to seem // invisible to the user. return nil, diags.Err() } obj, err := src.Decode(schema.ImpliedType()) if err != nil { return nil, err } return obj, nil } // ReadResourceInstanceStateDeposed reads the deposed object for a specific // instance in the state. func (n *NodeAbstractResource) ReadResourceInstanceStateDeposed(ctx EvalContext, addr addrs.AbsResourceInstance, key states.DeposedKey) (*states.ResourceInstanceObject, error) { provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider) if err != nil { return nil, err } if key == states.NotDeposed { return nil, fmt.Errorf("EvalReadStateDeposed used with no instance key; this is a bug in Terraform and should be reported") } log.Printf("[TRACE] EvalReadStateDeposed: reading state for %s deposed object %s", addr, key) src := ctx.State().ResourceInstanceObject(addr, key) if src == nil { // Presumably we only have deposed objects, then. log.Printf("[TRACE] EvalReadStateDeposed: no state present for %s deposed object %s", addr, key) return nil, nil } schema, currentVersion := (providerSchema).SchemaForResourceAddr(addr.Resource.ContainingResource()) if schema == nil { // Shouldn't happen since we should've failed long ago if no schema is present return nil, fmt.Errorf("no schema available for %s while reading state; this is a bug in Terraform and should be reported", addr) } src, diags := UpgradeResourceState(addr, provider, src, schema, currentVersion) if diags.HasErrors() { // Note that we don't have any channel to return warnings here. We'll // accept that for now since warnings during a schema upgrade would // be pretty weird anyway, since this operation is supposed to seem // invisible to the user. return nil, diags.Err() } obj, err := src.Decode(schema.ImpliedType()) if err != nil { return nil, err } return obj, nil } // graphNodesAreResourceInstancesInDifferentInstancesOfSameModule is an // annoyingly-task-specific helper function that returns true if and only if // the following conditions hold: // - Both of the given vertices represent specific resource instances, as // opposed to unexpanded resources or any other non-resource-related object. // - The module instance addresses for both of the resource instances belong // to the same static module. // - The module instance addresses for both of the resource instances are // not equal, indicating that they belong to different instances of the // same module. // // This result can be used as a way to compensate for the effects of // conservative analyses passes in our graph builders which make their // decisions based only on unexpanded addresses, often so that they can behave // correctly for interactions between expanded and not-yet-expanded objects. // // Callers of this helper function will typically skip adding an edge between // the two given nodes if this function returns true. func graphNodesAreResourceInstancesInDifferentInstancesOfSameModule(a, b dag.Vertex) bool { aRI, aOK := a.(GraphNodeResourceInstance) bRI, bOK := b.(GraphNodeResourceInstance) if !(aOK && bOK) { return false } aModInst := aRI.ResourceInstanceAddr().Module bModInst := bRI.ResourceInstanceAddr().Module aMod := aModInst.Module() bMod := bModInst.Module() if !aMod.Equal(bMod) { return false } return !aModInst.Equal(bModInst) }