package terraform import ( "fmt" "log" "github.com/hashicorp/hcl2/hcl" "github.com/hashicorp/hcl2/hcl/hclsyntax" "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/lang" ) // ConcreteResourceNodeFunc is a callback type used to convert an // abstract resource to a concrete one of some type. type ConcreteResourceNodeFunc func(*NodeAbstractResource) dag.Vertex // GraphNodeResource 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 GraphNodeResource interface { ResourceAddr() addrs.AbsResource } // 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 } // 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.AbsResource // Addr is the address for this resource // 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 Config *configs.Resource // Config is the resource in the config ProvisionerSchemas map[string]*configschema.Block Targets []addrs.Targetable // Set from GraphNodeTargetable // The address of the provider this resource will use ResolvedProvider addrs.AbsProviderConfig } var ( _ GraphNodeSubPath = (*NodeAbstractResource)(nil) _ GraphNodeReferenceable = (*NodeAbstractResource)(nil) _ GraphNodeReferencer = (*NodeAbstractResource)(nil) _ GraphNodeProviderConsumer = (*NodeAbstractResource)(nil) _ GraphNodeProvisionerConsumer = (*NodeAbstractResource)(nil) _ GraphNodeResource = (*NodeAbstractResource)(nil) _ GraphNodeAttachResourceConfig = (*NodeAbstractResource)(nil) _ GraphNodeAttachResourceSchema = (*NodeAbstractResource)(nil) _ GraphNodeAttachProvisionerSchema = (*NodeAbstractResource)(nil) _ GraphNodeTargetable = (*NodeAbstractResource)(nil) _ dag.GraphNodeDotter = (*NodeAbstractResource)(nil) ) // NewNodeAbstractResource creates an abstract resource graph node for // the given absolute resource address. func NewNodeAbstractResource(addr addrs.AbsResource) *NodeAbstractResource { return &NodeAbstractResource{ Addr: addr, } } // NodeAbstractResourceInstance represents a resource instance with no // associated operations. It embeds NodeAbstractResource but additionally // contains an instance key, used to identify one of potentially many // instances that were created from a resource in configuration, e.g. using // the "count" or "for_each" arguments. type NodeAbstractResourceInstance struct { NodeAbstractResource InstanceKey addrs.InstanceKey // 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. ResourceState *ResourceState // the ResourceState for this instance } var ( _ GraphNodeSubPath = (*NodeAbstractResourceInstance)(nil) _ GraphNodeReferenceable = (*NodeAbstractResourceInstance)(nil) _ GraphNodeReferencer = (*NodeAbstractResourceInstance)(nil) _ GraphNodeProviderConsumer = (*NodeAbstractResourceInstance)(nil) _ GraphNodeProvisionerConsumer = (*NodeAbstractResourceInstance)(nil) _ GraphNodeResource = (*NodeAbstractResourceInstance)(nil) _ GraphNodeResourceInstance = (*NodeAbstractResourceInstance)(nil) _ GraphNodeAttachResourceState = (*NodeAbstractResourceInstance)(nil) _ GraphNodeAttachResourceConfig = (*NodeAbstractResourceInstance)(nil) _ GraphNodeAttachResourceSchema = (*NodeAbstractResourceInstance)(nil) _ GraphNodeAttachProvisionerSchema = (*NodeAbstractResourceInstance)(nil) _ GraphNodeTargetable = (*NodeAbstractResourceInstance)(nil) _ dag.GraphNodeDotter = (*NodeAbstractResourceInstance)(nil) ) // NewNodeAbstractResourceInstance creates an abstract resource instance graph // node for the given absolute resource instance address. func NewNodeAbstractResourceInstance(addr addrs.AbsResourceInstance) *NodeAbstractResourceInstance { // Due to the fact that we embed NodeAbstractResource, the given address // actually ends up split between the resource address in the embedded // object and the InstanceKey field in our own struct. The // ResourceInstanceAddr method will stick these back together again on // request. return &NodeAbstractResourceInstance{ NodeAbstractResource: NodeAbstractResource{ Addr: addr.ContainingResource(), }, InstanceKey: addr.Resource.Key, } } func (n *NodeAbstractResource) Name() string { return n.ResourceAddr().String() } func (n *NodeAbstractResourceInstance) Name() string { return n.ResourceInstanceAddr().String() } // GraphNodeSubPath func (n *NodeAbstractResource) Path() addrs.ModuleInstance { return n.Addr.Module } // GraphNodeReferenceable func (n *NodeAbstractResource) ReferenceableAddrs() []addrs.Referenceable { return []addrs.Referenceable{n.Addr.Resource} } // GraphNodeReferenceable func (n *NodeAbstractResourceInstance) ReferenceableAddrs() []addrs.Referenceable { addr := n.ResourceInstanceAddr() return []addrs.Referenceable{ addr.Resource, // A resource instance can also be referenced by the address of its // containing resource, so that e.g. a reference to aws_instance.foo // would match both aws_instance.foo[0] and aws_instance.foo[1]. addr.ContainingResource().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 for _, traversal := range c.DependsOn { ref, err := addrs.ParseRef(traversal) if err != nil { // 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, err) continue } result = append(result, ref) } if n.Schema == nil { // Should never happens, 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 references cannot be detected", n.Name()) } refs, _ := lang.ReferencesInExpr(c.Count) result = append(result, refs...) refs, _ = lang.ReferencesInBlock(c.Config, n.Schema) result = append(result, refs...) if c.Managed != nil { 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 } // GraphNodeReferencer func (n *NodeAbstractResourceInstance) References() []*addrs.Reference { // If we have a configuration attached then we'll delegate to our // embedded abstract resource, which knows how to extract dependencies // from configuration. if n.Config != nil { return n.NodeAbstractResource.References() } // Otherwise, if we have state then we'll use the values stored in state // as a fallback. if s := n.ResourceState; s != nil { // State is still storing dependencies as old-style strings, so we'll // need to do a little work here to massage this to the form we now // want. var result []*addrs.Reference for _, legacyDep := range s.Dependencies { traversal, diags := hclsyntax.ParseTraversalAbs([]byte(legacyDep), "", hcl.Pos{}) if diags.HasErrors() { log.Printf("[ERROR] Can't parse %q from dependencies in state as a reference: invalid syntax", legacyDep) continue } ref, err := addrs.ParseRef(traversal) if err != nil { log.Printf("[ERROR] Can't parse %q from dependencies in state as a reference: invalid syntax", legacyDep) continue } result = append(result, ref) } return result } // If we have neither config nor state then we have no references. return nil } // converts an instance address to the legacy dotted notation func dottedInstanceAddr(tr addrs.ResourceInstance) string { // For historical reasons, state uses dot-separated instance keys, // rather than bracketed as in our modern syntax. var suffix string switch tk := tr.Key.(type) { case addrs.IntKey: suffix = fmt.Sprintf(".%d", int(tk)) case addrs.StringKey: suffix = fmt.Sprintf(".%s", string(tk)) } return tr.Resource.String() + suffix } // StateReferences returns the dependencies to put into the state for // this resource. func (n *NodeAbstractResource) StateReferences() []string { selfAddrs := n.ReferenceableAddrs() depsRaw := n.References() deps := make([]string, 0, len(depsRaw)) for _, d := range depsRaw { switch tr := d.Subject.(type) { case addrs.ResourceInstance: key := dottedInstanceAddr(tr) deps = append(deps, key) case addrs.Resource: depStr := tr.String() selfRef := false for _, selfAddr := range selfAddrs { if selfAddr.String() == depStr { selfRef = true break } } if !selfRef { // Don't create self-references deps = append(deps, tr.String()) } case addrs.ModuleCallInstance: deps = append(deps, tr.String()) case addrs.ModuleCallOutput: // For state dependencies, we simplify outputs to just refer // to the module as a whole. It's not really clear why we do this, // but this logic is preserved from before the 0.12 rewrite of // this function. deps = append(deps, tr.Call.String()) default: // No other reference types are recorded in the state. } } return deps } func (n *NodeAbstractResource) SetProvider(p addrs.AbsProviderConfig) { n.ResolvedProvider = p } // GraphNodeProviderConsumer func (n *NodeAbstractResource) ProvidedBy() (addrs.AbsProviderConfig, bool) { // If we have a config we prefer that above all else if n.Config != nil { relAddr := n.Config.ProviderConfigAddr() return relAddr.Absolute(n.Path()), false } // Use our type and containing module path to guess a provider configuration address return n.Addr.Resource.DefaultProviderConfig().Absolute(n.Addr.Module), false } // GraphNodeProviderConsumer func (n *NodeAbstractResourceInstance) ProvidedBy() (addrs.AbsProviderConfig, bool) { // If we have a config we prefer that above all else if n.Config != nil { relAddr := n.Config.ProviderConfigAddr() return relAddr.Absolute(n.Path()), false } // If we have state, then we will use the provider from there if n.ResourceState != nil && n.ResourceState.Provider != "" { traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(n.ResourceState.Provider), "", hcl.Pos{}) if parseDiags.HasErrors() { log.Printf("[ERROR] %s has syntax-invalid provider address %q", n.Addr, n.ResourceState.Provider) goto Guess } addr, diags := addrs.ParseAbsProviderConfig(traversal) if diags.HasErrors() { log.Printf("[ERROR] %s has content-invalid provider address %q", n.Addr, n.ResourceState.Provider) goto Guess } // An address from the state must match exactly, since we must ensure // we refresh/destroy a resource with the same provider configuration // that created it. return addr, true } Guess: // Use our type and containing module path to guess a provider configuration address return n.Addr.Resource.DefaultProviderConfig().Absolute(n.Path()), false } // 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.AbsResource { return n.Addr } // GraphNodeResourceInstance func (n *NodeAbstractResourceInstance) ResourceInstanceAddr() addrs.AbsResourceInstance { return n.NodeAbstractResource.Addr.Instance(n.InstanceKey) } // GraphNodeAddressable, TODO: remove, used by target, should unify func (n *NodeAbstractResource) ResourceAddress() *ResourceAddress { return NewLegacyResourceAddress(n.Addr) } // GraphNodeTargetable func (n *NodeAbstractResource) SetTargets(targets []addrs.Targetable) { n.Targets = targets } // GraphNodeAttachResourceState func (n *NodeAbstractResourceInstance) AttachResourceState(s *ResourceState) { n.ResourceState = s } // GraphNodeAttachResourceConfig func (n *NodeAbstractResource) AttachResourceConfig(c *configs.Resource) { n.Config = c } // GraphNodeAttachResourceSchema impl func (n *NodeAbstractResource) AttachResourceSchema(schema *configschema.Block) { n.Schema = schema } // 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", }, } }