496 lines
15 KiB
Go
496 lines
15 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/hashicorp/hcl2/hcl"
|
|
"github.com/hashicorp/terraform/configs/configschema"
|
|
"github.com/hashicorp/terraform/lang"
|
|
|
|
"github.com/hashicorp/terraform/addrs"
|
|
"github.com/hashicorp/terraform/config"
|
|
"github.com/hashicorp/terraform/dag"
|
|
)
|
|
|
|
// GraphNodeReferenceable must be implemented by any node that represents
|
|
// a Terraform thing that can be referenced (resource, module, etc.).
|
|
//
|
|
// Even if the thing has no name, this should return an empty list. By
|
|
// implementing this and returning a non-nil result, you say that this CAN
|
|
// be referenced and other methods of referencing may still be possible (such
|
|
// as by path!)
|
|
type GraphNodeReferenceable interface {
|
|
GraphNodeSubPath
|
|
|
|
// ReferenceableAddrs returns a list of addresses through which this can be
|
|
// referenced.
|
|
ReferenceableAddrs() []addrs.Referenceable
|
|
}
|
|
|
|
// GraphNodeReferencer must be implemented by nodes that reference other
|
|
// Terraform items and therefore depend on them.
|
|
type GraphNodeReferencer interface {
|
|
GraphNodeSubPath
|
|
|
|
// References returns a list of references made by this node, which
|
|
// include both a referenced address and source location information for
|
|
// the reference.
|
|
References() []*addrs.Reference
|
|
}
|
|
|
|
// GraphNodeReferenceOutside is an interface that can optionally be implemented.
|
|
// A node that implements it can specify that its own referenceable addresses
|
|
// and/or the addresses it references are in a different module than the
|
|
// node itself.
|
|
//
|
|
// Any referenceable addresses returned by ReferenceableAddrs are interpreted
|
|
// relative to the returned selfPath.
|
|
//
|
|
// Any references returned by References are interpreted relative to the
|
|
// returned referencePath.
|
|
//
|
|
// It is valid but not required for either of these paths to match what is
|
|
// returned by method Path, though if both match the main Path then there
|
|
// is no reason to implement this method.
|
|
//
|
|
// The primary use-case for this is the nodes representing module input
|
|
// variables, since their expressions are resolved in terms of their calling
|
|
// module, but they are still referenced from their own module.
|
|
type GraphNodeReferenceOutside interface {
|
|
// ReferenceOutside returns a path in which any references from this node
|
|
// are resolved.
|
|
ReferenceOutside() (selfPath, referencePath addrs.ModuleInstance)
|
|
}
|
|
|
|
// ReferenceTransformer is a GraphTransformer that connects all the
|
|
// nodes that reference each other in order to form the proper ordering.
|
|
type ReferenceTransformer struct{}
|
|
|
|
func (t *ReferenceTransformer) Transform(g *Graph) error {
|
|
// Build a reference map so we can efficiently look up the references
|
|
vs := g.Vertices()
|
|
m := NewReferenceMap(vs)
|
|
|
|
// Find the things that reference things and connect them
|
|
for _, v := range vs {
|
|
parents, _ := m.References(v)
|
|
parentsDbg := make([]string, len(parents))
|
|
for i, v := range parents {
|
|
parentsDbg[i] = dag.VertexName(v)
|
|
}
|
|
log.Printf(
|
|
"[DEBUG] ReferenceTransformer: %q references: %v",
|
|
dag.VertexName(v), parentsDbg)
|
|
|
|
for _, parent := range parents {
|
|
g.Connect(dag.BasicEdge(v, parent))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DestroyReferenceTransformer is a GraphTransformer that reverses the edges
|
|
// for locals and outputs that depend on other nodes which will be
|
|
// removed during destroy. If a destroy node is evaluated before the local or
|
|
// output value, it will be removed from the state, and the later interpolation
|
|
// will fail.
|
|
type DestroyValueReferenceTransformer struct{}
|
|
|
|
func (t *DestroyValueReferenceTransformer) Transform(g *Graph) error {
|
|
vs := g.Vertices()
|
|
for _, v := range vs {
|
|
switch v.(type) {
|
|
case *NodeApplyableOutput, *NodeLocal:
|
|
// OK
|
|
default:
|
|
continue
|
|
}
|
|
|
|
// reverse any outgoing edges so that the value is evaluated first.
|
|
for _, e := range g.EdgesFrom(v) {
|
|
target := e.Target()
|
|
|
|
// only destroy nodes will be evaluated in reverse
|
|
if _, ok := target.(GraphNodeDestroyer); !ok {
|
|
continue
|
|
}
|
|
|
|
log.Printf("[TRACE] output dep: %s", dag.VertexName(target))
|
|
|
|
g.RemoveEdge(e)
|
|
g.Connect(&DestroyEdge{S: target, T: v})
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PruneUnusedValuesTransformer is s GraphTransformer that removes local and
|
|
// output values which are not referenced in the graph. Since outputs and
|
|
// locals always need to be evaluated, if they reference a resource that is not
|
|
// available in the state the interpolation could fail.
|
|
type PruneUnusedValuesTransformer struct{}
|
|
|
|
func (t *PruneUnusedValuesTransformer) Transform(g *Graph) error {
|
|
// this might need multiple runs in order to ensure that pruning a value
|
|
// doesn't effect a previously checked value.
|
|
for removed := 0; ; removed = 0 {
|
|
for _, v := range g.Vertices() {
|
|
switch v.(type) {
|
|
case *NodeApplyableOutput, *NodeLocal:
|
|
// OK
|
|
default:
|
|
continue
|
|
}
|
|
|
|
dependants := g.UpEdges(v)
|
|
|
|
switch dependants.Len() {
|
|
case 0:
|
|
// nothing at all depends on this
|
|
g.Remove(v)
|
|
removed++
|
|
case 1:
|
|
// because an output's destroy node always depends on the output,
|
|
// we need to check for the case of a single destroy node.
|
|
d := dependants.List()[0]
|
|
if _, ok := d.(*NodeDestroyableOutput); ok {
|
|
g.Remove(v)
|
|
removed++
|
|
}
|
|
}
|
|
}
|
|
if removed == 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReferenceMap is a structure that can be used to efficiently check
|
|
// for references on a graph.
|
|
type ReferenceMap struct {
|
|
// vertices is a map from internal reference keys (as produced by the
|
|
// mapKey method) to one or more vertices that are identified by each key.
|
|
//
|
|
// A particular reference key might actually identify multiple vertices,
|
|
// e.g. in situations where one object is contained inside another.
|
|
vertices map[string][]dag.Vertex
|
|
|
|
// edges is a map whose keys are a subset of the internal reference keys
|
|
// from "vertices", and whose values are the nodes that refer to each
|
|
// key. The values in this map are the referrers, while values in
|
|
// "verticies" are the referents. The keys in both cases are referents.
|
|
edges map[string][]dag.Vertex
|
|
}
|
|
|
|
// References returns the set of vertices that the given vertex refers to,
|
|
// and any referenced addresses that do not have corresponding vertices.
|
|
func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []addrs.Referenceable) {
|
|
rn, ok := v.(GraphNodeReferencer)
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
if _, ok := v.(GraphNodeSubPath); !ok {
|
|
return nil, nil
|
|
}
|
|
|
|
var matches []dag.Vertex
|
|
var missing []addrs.Referenceable
|
|
|
|
for _, ref := range rn.References() {
|
|
subject := ref.Subject
|
|
|
|
key := m.referenceMapKey(v, subject)
|
|
if _, exists := m.vertices[key]; !exists {
|
|
// If what we were looking for was a ResourceInstance then we
|
|
// might be in a resource-oriented graph rather than an
|
|
// instance-oriented graph, and so we'll see if we have the
|
|
// resource itself instead.
|
|
switch ri := subject.(type) {
|
|
case addrs.ResourceInstance:
|
|
subject = ri.ContainingResource()
|
|
case addrs.ResourceInstancePhase:
|
|
subject = ri.ContainingResource()
|
|
}
|
|
key = m.referenceMapKey(v, subject)
|
|
}
|
|
|
|
vertices := m.vertices[key]
|
|
for _, rv := range vertices {
|
|
// don't include self-references
|
|
if rv == v {
|
|
continue
|
|
}
|
|
matches = append(matches, rv)
|
|
}
|
|
if len(vertices) == 0 {
|
|
missing = append(missing, ref.Subject)
|
|
}
|
|
}
|
|
|
|
return matches, missing
|
|
}
|
|
|
|
// Referrers returns the set of vertices that refer to the given vertex.
|
|
func (m *ReferenceMap) Referrers(v dag.Vertex) []dag.Vertex {
|
|
rn, ok := v.(GraphNodeReferenceable)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
sp, ok := v.(GraphNodeSubPath)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
var matches []dag.Vertex
|
|
for _, addr := range rn.ReferenceableAddrs() {
|
|
key := m.mapKey(sp.Path(), addr)
|
|
referrers, ok := m.edges[key]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// If the referrer set includes our own given vertex then we skip,
|
|
// since we don't want to return self-references.
|
|
selfRef := false
|
|
for _, p := range referrers {
|
|
if p == v {
|
|
selfRef = true
|
|
break
|
|
}
|
|
}
|
|
if selfRef {
|
|
continue
|
|
}
|
|
|
|
matches = append(matches, referrers...)
|
|
}
|
|
|
|
return matches
|
|
}
|
|
|
|
func (m *ReferenceMap) mapKey(path addrs.ModuleInstance, addr addrs.Referenceable) string {
|
|
return fmt.Sprintf("%s|%s", path.String(), addr.String())
|
|
}
|
|
|
|
// vertexReferenceablePath returns the path in which the given vertex can be
|
|
// referenced. This is the path that its results from ReferenceableAddrs
|
|
// are considered to be relative to.
|
|
//
|
|
// Only GraphNodeSubPath implementations can be referenced, so this method will
|
|
// panic if the given vertex does not implement that interface.
|
|
func (m *ReferenceMap) vertexReferenceablePath(v dag.Vertex) addrs.ModuleInstance {
|
|
sp, ok := v.(GraphNodeSubPath)
|
|
if !ok {
|
|
// Only nodes with paths can participate in a reference map.
|
|
panic(fmt.Errorf("vertexMapKey on vertex type %T which doesn't implement GraphNodeSubPath", sp))
|
|
}
|
|
|
|
if outside, ok := v.(GraphNodeReferenceOutside); ok {
|
|
// Vertex is referenced from a different module than where it was
|
|
// declared.
|
|
path, _ := outside.ReferenceOutside()
|
|
return path
|
|
}
|
|
|
|
// Vertex is referenced from the same module as where it was declared.
|
|
return sp.Path()
|
|
}
|
|
|
|
// vertexReferencePath returns the path in which references _from_ the given
|
|
// vertex must be interpreted.
|
|
//
|
|
// Only GraphNodeSubPath implementations can have references, so this method
|
|
// will panic if the given vertex does not implement that interface.
|
|
func vertexReferencePath(referrer dag.Vertex) addrs.ModuleInstance {
|
|
sp, ok := referrer.(GraphNodeSubPath)
|
|
if !ok {
|
|
// Only nodes with paths can participate in a reference map.
|
|
panic(fmt.Errorf("vertexReferencePath on vertex type %T which doesn't implement GraphNodeSubPath", sp))
|
|
}
|
|
|
|
var path addrs.ModuleInstance
|
|
if outside, ok := referrer.(GraphNodeReferenceOutside); ok {
|
|
// Vertex makes references to objects in a different module than where
|
|
// it was declared.
|
|
_, path = outside.ReferenceOutside()
|
|
return path
|
|
}
|
|
|
|
// Vertex makes references to objects in the same module as where it
|
|
// was declared.
|
|
return sp.Path()
|
|
}
|
|
|
|
// referenceMapKey produces keys for the "edges" map. "referrer" is the vertex
|
|
// that the reference is from, and "addr" is the address of the object being
|
|
// referenced.
|
|
//
|
|
// The result is an opaque string that includes both the address of the given
|
|
// object and the address of the module instance that object belongs to.
|
|
//
|
|
// Only GraphNodeSubPath implementations can be referrers, so this method will
|
|
// panic if the given vertex does not implement that interface.
|
|
func (m *ReferenceMap) referenceMapKey(referrer dag.Vertex, addr addrs.Referenceable) string {
|
|
path := vertexReferencePath(referrer)
|
|
return m.mapKey(path, addr)
|
|
}
|
|
|
|
// NewReferenceMap is used to create a new reference map for the
|
|
// given set of vertices.
|
|
func NewReferenceMap(vs []dag.Vertex) *ReferenceMap {
|
|
var m ReferenceMap
|
|
|
|
// Build the lookup table
|
|
vertices := make(map[string][]dag.Vertex)
|
|
for _, v := range vs {
|
|
_, ok := v.(GraphNodeSubPath)
|
|
if !ok {
|
|
// Only nodes with paths can participate in a reference map.
|
|
continue
|
|
}
|
|
|
|
// We're only looking for referenceable nodes
|
|
rn, ok := v.(GraphNodeReferenceable)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
path := m.vertexReferenceablePath(v)
|
|
|
|
// Go through and cache them
|
|
for _, addr := range rn.ReferenceableAddrs() {
|
|
key := m.mapKey(path, addr)
|
|
vertices[key] = append(vertices[key], v)
|
|
}
|
|
|
|
// Any node can be referenced by the address of the module it belongs
|
|
// to or any of that module's ancestors.
|
|
for _, addr := range path.Ancestors()[1:] {
|
|
// Can be referenced either as the specific call instance (with
|
|
// an instance key) or as the bare module call itself (the "module"
|
|
// block in the parent module that created the instance).
|
|
callPath, call := addr.Call()
|
|
callInstPath, callInst := addr.CallInstance()
|
|
callKey := m.mapKey(callPath, call)
|
|
callInstKey := m.mapKey(callInstPath, callInst)
|
|
vertices[callKey] = append(vertices[callKey], v)
|
|
vertices[callInstKey] = append(vertices[callInstKey], v)
|
|
}
|
|
}
|
|
|
|
// Build the lookup table for referenced by
|
|
edges := make(map[string][]dag.Vertex)
|
|
for _, v := range vs {
|
|
_, ok := v.(GraphNodeSubPath)
|
|
if !ok {
|
|
// Only nodes with paths can participate in a reference map.
|
|
continue
|
|
}
|
|
|
|
rn, ok := v.(GraphNodeReferencer)
|
|
if !ok {
|
|
// We're only looking for referenceable nodes
|
|
continue
|
|
}
|
|
|
|
// Go through and cache them
|
|
for _, ref := range rn.References() {
|
|
if ref.Subject == nil {
|
|
// Should never happen
|
|
panic(fmt.Sprintf("%T.References returned reference with nil subject", rn))
|
|
}
|
|
key := m.referenceMapKey(v, ref.Subject)
|
|
edges[key] = append(edges[key], v)
|
|
}
|
|
}
|
|
|
|
m.vertices = vertices
|
|
m.edges = edges
|
|
return &m
|
|
}
|
|
|
|
// ReferencesFromConfig returns the references that a configuration has
|
|
// based on the interpolated variables in a configuration.
|
|
func ReferencesFromConfig(body hcl.Body, schema *configschema.Block) []*addrs.Reference {
|
|
if body == nil {
|
|
return nil
|
|
}
|
|
refs, _ := lang.ReferencesInBlock(body, schema)
|
|
return refs
|
|
}
|
|
|
|
// ReferenceFromInterpolatedVar returns the reference from this variable,
|
|
// or an empty string if there is no reference.
|
|
func ReferenceFromInterpolatedVar(v config.InterpolatedVariable) []string {
|
|
switch v := v.(type) {
|
|
case *config.ModuleVariable:
|
|
return []string{fmt.Sprintf("module.%s.output.%s", v.Name, v.Field)}
|
|
case *config.ResourceVariable:
|
|
id := v.ResourceId()
|
|
|
|
// If we have a multi-reference (splat), then we depend on ALL
|
|
// resources with this type/name.
|
|
if v.Multi && v.Index == -1 {
|
|
return []string{fmt.Sprintf("%s.*", id)}
|
|
}
|
|
|
|
// Otherwise, we depend on a specific index.
|
|
idx := v.Index
|
|
if !v.Multi || v.Index == -1 {
|
|
idx = 0
|
|
}
|
|
|
|
// Depend on the index, as well as "N" which represents the
|
|
// un-expanded set of resources.
|
|
return []string{fmt.Sprintf("%s.%d/%s.N", id, idx, id)}
|
|
case *config.UserVariable:
|
|
return []string{fmt.Sprintf("var.%s", v.Name)}
|
|
case *config.LocalVariable:
|
|
return []string{fmt.Sprintf("local.%s", v.Name)}
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// appendResourceDestroyReferences identifies resource and resource instance
|
|
// references in the given slice and appends to it the "destroy-phase"
|
|
// equivalents of those references, returning the result.
|
|
//
|
|
// This can be used in the References implementation for a node which must also
|
|
// depend on the destruction of anything it references.
|
|
func appendResourceDestroyReferences(refs []*addrs.Reference) []*addrs.Reference {
|
|
given := refs
|
|
for _, ref := range given {
|
|
switch tr := ref.Subject.(type) {
|
|
case addrs.Resource:
|
|
newRef := *ref // shallow copy
|
|
newRef.Subject = tr.Phase(addrs.ResourceInstancePhaseDestroy)
|
|
refs = append(refs, &newRef)
|
|
case addrs.ResourceInstance:
|
|
newRef := *ref // shallow copy
|
|
newRef.Subject = tr.Phase(addrs.ResourceInstancePhaseDestroy)
|
|
refs = append(refs, &newRef)
|
|
}
|
|
}
|
|
return refs
|
|
}
|
|
|
|
func modulePrefixStr(p addrs.ModuleInstance) string {
|
|
return p.String()
|
|
}
|
|
|
|
func modulePrefixList(result []string, prefix string) []string {
|
|
if prefix != "" {
|
|
for i, v := range result {
|
|
result[i] = fmt.Sprintf("%s.%s", prefix, v)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|