459 lines
16 KiB
Go
459 lines
16 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"sort"
|
|
|
|
"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
|
|
|
|
// 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
|
|
SchemaVersion uint64 // Schema version of "Schema", as decided by the provider
|
|
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 *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.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 config 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 {
|
|
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 n.NodeAbstractResource.References()
|
|
}
|
|
|
|
// 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
|
|
|
|
// It is (apparently) possible for s.Current to be nil. This proved
|
|
// difficult to reproduce, so we will fix the symptom here and hope
|
|
// to find the root cause another time.
|
|
//
|
|
// https://github.com/hashicorp/terraform/issues/21407
|
|
if s.Current == nil {
|
|
log.Printf("[WARN] no current state found for %s", n.Name())
|
|
} else {
|
|
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 *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([]addrs.Referenceable, 0, len(depsRaw))
|
|
for _, d := range depsRaw {
|
|
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
|
|
}
|
|
|
|
k := subj.String()
|
|
if _, exists := seen[k]; exists {
|
|
continue
|
|
}
|
|
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.
|
|
}
|
|
}
|
|
|
|
// 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 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 {
|
|
// 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 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 *states.Resource) {
|
|
n.ResourceState = s
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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",
|
|
},
|
|
}
|
|
}
|