import (
"fmt"
- "strings"
+ "log"
+ "sort"
- "github.com/hashicorp/terraform/config"
+ "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
// The type of operation cannot be assumed, only that this node represents
// the given resource.
type GraphNodeResource interface {
- ResourceAddr() *ResourceAddress
+ 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 *ResourceAddress // Addr is the address for this resource
+ 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.
- Config *config.Resource // Config is the resource in the config
- ResourceState *ResourceState // ResourceState is the ResourceState for this
+ 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
- Targets []ResourceAddress // Set from GraphNodeTargetable
+ ProvisionerSchemas map[string]*configschema.Block
+
+ Targets []addrs.Targetable // Set from GraphNodeTargetable
// The address of the provider this resource will use
- ResolvedProvider string
+ 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 *states.Resource
+}
+
+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.Addr.String()
+ return n.ResourceAddr().String()
+}
+
+func (n *NodeAbstractResourceInstance) Name() string {
+ return n.ResourceInstanceAddr().String()
}
// GraphNodeSubPath
-func (n *NodeAbstractResource) Path() []string {
- return n.Addr.Path
+func (n *NodeAbstractResource) Path() addrs.ModuleInstance {
+ return n.Addr.Module
}
// GraphNodeReferenceable
-func (n *NodeAbstractResource) ReferenceableName() []string {
- // We always are referenceable as "type.name" as long as
- // we have a config or address. Determine what that value is.
- var id string
- if n.Config != nil {
- id = n.Config.Id()
- } else if n.Addr != nil {
- addrCopy := n.Addr.Copy()
- addrCopy.Path = nil // ReferenceTransformer handles paths
- addrCopy.Index = -1 // We handle indexes below
- id = addrCopy.String()
- } else {
- // No way to determine our type.name, just return
- return nil
- }
+func (n *NodeAbstractResource) ReferenceableAddrs() []addrs.Referenceable {
+ return []addrs.Referenceable{n.Addr.Resource}
+}
- var result []string
+// 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,
+ }
+}
- // Always include our own ID. This is primarily for backwards
- // compatibility with states that didn't yet support the more
- // specific dep string.
- result = append(result, id)
+// 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
+ }
- // We represent all multi-access
- result = append(result, fmt.Sprintf("%s.*", id))
+ result = append(result, ref)
+ }
- // We represent either a specific number, or all numbers
- suffix := "N"
- if n.Addr != nil {
- idx := n.Addr.Index
- if idx == -1 {
- idx = 0
+ 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 config references cannot be detected", n.Name())
}
- suffix = fmt.Sprintf("%d", idx)
+ 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
}
- result = append(result, fmt.Sprintf("%s.%s", id, suffix))
- return result
+ // Otherwise, we have no references.
+ return nil
}
// GraphNodeReferencer
-func (n *NodeAbstractResource) References() []string {
- // If we have a config, that is our source of truth
- if c := n.Config; c != nil {
- // Grab all the references
- var result []string
- result = append(result, c.DependsOn...)
- result = append(result, ReferencesFromConfig(c.RawCount)...)
- result = append(result, ReferencesFromConfig(c.RawConfig)...)
- for _, p := range c.Provisioners {
- if p.When == config.ProvisionerWhenCreate {
- result = append(result, ReferencesFromConfig(p.ConnInfo)...)
- result = append(result, ReferencesFromConfig(p.RawConfig)...)
- }
+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 {
+ if n.Schema == nil {
+ // We'll produce a log message about this out here so that
+ // we can include the full instance address, since the equivalent
+ // message in NodeAbstractResource.References cannot see it.
+ log.Printf("[WARN] no schema is attached to %s, so config references cannot be detected", n.Name())
+ return nil
}
-
- return uniqueStrings(result)
+ return n.NodeAbstractResource.References()
}
- // If we have state, that is our next source
- if s := n.ResourceState; s != nil {
- return s.Dependencies
+ // Otherwise, if we have state then we'll use the values stored in state
+ // as a fallback.
+ if rs := n.ResourceState; rs != nil {
+ if s := rs.Instance(n.InstanceKey); 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 _, addr := range s.Current.Dependencies {
+ if addr == nil {
+ // Should never happen; indicates a bug in the state loader
+ panic(fmt.Sprintf("dependencies for current object on %s contains nil address", n.ResourceInstanceAddr()))
+ }
+
+ // This is a little weird: we need to manufacture an addrs.Reference
+ // with a fake range here because the state isn't something we can
+ // make source references into.
+ result = append(result, &addrs.Reference{
+ Subject: addr,
+ SourceRange: tfdiags.SourceRange{
+ Filename: "(state file)",
+ },
+ })
+ }
+ 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 {
+ // The legacy state format 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 {
- self := n.ReferenceableName()
-
- // Determine what our "prefix" is for checking for references to
- // ourself.
- addrCopy := n.Addr.Copy()
- addrCopy.Index = -1
- selfPrefix := addrCopy.String() + "."
+func (n *NodeAbstractResourceInstance) StateReferences() []addrs.Referenceable {
+ selfAddrs := n.ReferenceableAddrs()
+
+ // Since we don't include the source location references in our
+ // results from this method, we'll also filter out duplicates:
+ // there's no point in listing the same object twice without
+ // that additional context.
+ seen := map[string]struct{}{}
+
+ // Pretend that we've already "seen" all of our own addresses so that we
+ // won't record self-references in the state. This can arise if, for
+ // example, a provisioner for a resource refers to the resource itself,
+ // which is valid (since provisioners always run after apply) but should
+ // not create an explicit dependency edge.
+ for _, selfAddr := range selfAddrs {
+ seen[selfAddr.String()] = struct{}{}
+ if riAddr, ok := selfAddr.(addrs.ResourceInstance); ok {
+ seen[riAddr.ContainingResource().String()] = struct{}{}
+ }
+ }
depsRaw := n.References()
- deps := make([]string, 0, len(depsRaw))
+ deps := make([]addrs.Referenceable, 0, len(depsRaw))
for _, d := range depsRaw {
- // Ignore any variable dependencies
- if strings.HasPrefix(d, "var.") {
- continue
+ subj := d.Subject
+ if mco, isOutput := subj.(addrs.ModuleCallOutput); isOutput {
+ // 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.
+ subj = mco.Call
}
- // If this has a backup ref, ignore those for now. The old state
- // file never contained those and I'd rather store the rich types we
- // add in the future.
- if idx := strings.IndexRune(d, '/'); idx != -1 {
- d = d[:idx]
- }
-
- // If we're referencing ourself, then ignore it
- found := false
- for _, s := range self {
- if d == s {
- found = true
- }
- }
- if found {
+ k := subj.String()
+ if _, exists := seen[k]; exists {
continue
}
-
- // If this is a reference to ourself and a specific index, we keep
- // it. For example, if this resource is "foo.bar" and the reference
- // is "foo.bar.0" then we keep it exact. Otherwise, we strip it.
- if strings.HasSuffix(d, ".0") && !strings.HasPrefix(d, selfPrefix) {
- d = d[:len(d)-2]
- }
-
- // This is sad. The dependencies are currently in the format of
- // "module.foo.bar" (the full field). This strips the field off.
- if strings.HasPrefix(d, "module.") {
- parts := strings.SplitN(d, ".", 3)
- d = strings.Join(parts[0:2], ".")
+ seen[k] = struct{}{}
+ switch tr := subj.(type) {
+ case addrs.ResourceInstance:
+ deps = append(deps, tr)
+ case addrs.Resource:
+ deps = append(deps, tr)
+ case addrs.ModuleCallInstance:
+ deps = append(deps, tr)
+ default:
+ // No other reference types are recorded in the state.
}
-
- deps = append(deps, d)
}
+ // We'll also sort them, since that'll avoid creating changes in the
+ // serialized state that make no semantic difference.
+ sort.Slice(deps, func(i, j int) bool {
+ // Simple string-based sort because we just care about consistency,
+ // not user-friendliness.
+ return deps[i].String() < deps[j].String()
+ })
+
return deps
}
-func (n *NodeAbstractResource) SetProvider(p string) {
+func (n *NodeAbstractResource) SetProvider(p addrs.AbsProviderConfig) {
n.ResolvedProvider = p
}
// GraphNodeProviderConsumer
-func (n *NodeAbstractResource) ProvidedBy() string {
+func (n *NodeAbstractResource) ProvidedBy() (addrs.AbsProviderConfig, bool) {
// If we have a config we prefer that above all else
if n.Config != nil {
- return resourceProvider(n.Config.Type, n.Config.Provider)
+ 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 != "" {
- return n.ResourceState.Provider
+ if n.ResourceState != nil {
+ // 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 n.ResourceState.ProviderConfig, true
}
- // Use our type
- return resourceProvider(n.Addr.Type, "")
+ // 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 {
+ 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.Provisioners))
- for i, p := range n.Config.Provisioners {
+ result := make([]string, len(n.Config.Managed.Provisioners))
+ for i, p := range n.Config.Managed.Provisioners {
result[i] = p.Type
}
return result
}
-// GraphNodeResource, GraphNodeAttachResourceState
-func (n *NodeAbstractResource) ResourceAddr() *ResourceAddress {
+// 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 n.ResourceAddr()
+ return NewLegacyResourceAddress(n.Addr)
}
// GraphNodeTargetable
-func (n *NodeAbstractResource) SetTargets(targets []ResourceAddress) {
+func (n *NodeAbstractResource) SetTargets(targets []addrs.Targetable) {
n.Targets = targets
}
// GraphNodeAttachResourceState
-func (n *NodeAbstractResource) AttachResourceState(s *ResourceState) {
+func (n *NodeAbstractResourceInstance) AttachResourceState(s *states.Resource) {
n.ResourceState = s
}
// GraphNodeAttachResourceConfig
-func (n *NodeAbstractResource) AttachResourceConfig(c *config.Resource) {
+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
+}
+
// GraphNodeDotter impl.
func (n *NodeAbstractResource) DotNode(name string, opts *dag.DotOpts) *dag.DotNode {
return &dag.DotNode{