github.com/magodo/terraform@v0.11.12-beta1/terraform/transform_reference.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/terraform/config"
     9  	"github.com/hashicorp/terraform/dag"
    10  )
    11  
    12  // GraphNodeReferenceable must be implemented by any node that represents
    13  // a Terraform thing that can be referenced (resource, module, etc.).
    14  //
    15  // Even if the thing has no name, this should return an empty list. By
    16  // implementing this and returning a non-nil result, you say that this CAN
    17  // be referenced and other methods of referencing may still be possible (such
    18  // as by path!)
    19  type GraphNodeReferenceable interface {
    20  	// ReferenceableName is the name by which this can be referenced.
    21  	// This can be either just the type, or include the field. Example:
    22  	// "aws_instance.bar" or "aws_instance.bar.id".
    23  	ReferenceableName() []string
    24  }
    25  
    26  // GraphNodeReferencer must be implemented by nodes that reference other
    27  // Terraform items and therefore depend on them.
    28  type GraphNodeReferencer interface {
    29  	// References are the list of things that this node references. This
    30  	// can include fields or just the type, just like GraphNodeReferenceable
    31  	// above.
    32  	References() []string
    33  }
    34  
    35  // GraphNodeReferenceGlobal is an interface that can optionally be
    36  // implemented. If ReferenceGlobal returns true, then the References()
    37  // and ReferenceableName() must be _fully qualified_ with "module.foo.bar"
    38  // etc.
    39  //
    40  // This allows a node to reference and be referenced by a specific name
    41  // that may cross module boundaries. This can be very dangerous so use
    42  // this wisely.
    43  //
    44  // The primary use case for this is module boundaries (variables coming in).
    45  type GraphNodeReferenceGlobal interface {
    46  	// Set to true to signal that references and name are fully
    47  	// qualified. See the above docs for more information.
    48  	ReferenceGlobal() bool
    49  }
    50  
    51  // ReferenceTransformer is a GraphTransformer that connects all the
    52  // nodes that reference each other in order to form the proper ordering.
    53  type ReferenceTransformer struct{}
    54  
    55  func (t *ReferenceTransformer) Transform(g *Graph) error {
    56  	// Build a reference map so we can efficiently look up the references
    57  	vs := g.Vertices()
    58  	m := NewReferenceMap(vs)
    59  
    60  	// Find the things that reference things and connect them
    61  	for _, v := range vs {
    62  		parents, _ := m.References(v)
    63  		parentsDbg := make([]string, len(parents))
    64  		for i, v := range parents {
    65  			parentsDbg[i] = dag.VertexName(v)
    66  		}
    67  		log.Printf(
    68  			"[DEBUG] ReferenceTransformer: %q references: %v",
    69  			dag.VertexName(v), parentsDbg)
    70  
    71  		for _, parent := range parents {
    72  			g.Connect(dag.BasicEdge(v, parent))
    73  		}
    74  	}
    75  
    76  	return nil
    77  }
    78  
    79  // DestroyReferenceTransformer is a GraphTransformer that reverses the edges
    80  // for locals and outputs that depend on other nodes which will be
    81  // removed during destroy. If a destroy node is evaluated before the local or
    82  // output value, it will be removed from the state, and the later interpolation
    83  // will fail.
    84  type DestroyValueReferenceTransformer struct{}
    85  
    86  func (t *DestroyValueReferenceTransformer) Transform(g *Graph) error {
    87  	vs := g.Vertices()
    88  	for _, v := range vs {
    89  		switch v.(type) {
    90  		case *NodeApplyableOutput, *NodeLocal:
    91  			// OK
    92  		default:
    93  			continue
    94  		}
    95  
    96  		// reverse any outgoing edges so that the value is evaluated first.
    97  		for _, e := range g.EdgesFrom(v) {
    98  			target := e.Target()
    99  
   100  			// only destroy nodes will be evaluated in reverse
   101  			if _, ok := target.(GraphNodeDestroyer); !ok {
   102  				continue
   103  			}
   104  
   105  			log.Printf("[TRACE] output dep: %s", dag.VertexName(target))
   106  
   107  			g.RemoveEdge(e)
   108  			g.Connect(&DestroyEdge{S: target, T: v})
   109  		}
   110  	}
   111  
   112  	return nil
   113  }
   114  
   115  // PruneUnusedValuesTransformer is s GraphTransformer that removes local and
   116  // output values which are not referenced in the graph. Since outputs and
   117  // locals always need to be evaluated, if they reference a resource that is not
   118  // available in the state the interpolation could fail.
   119  type PruneUnusedValuesTransformer struct{}
   120  
   121  func (t *PruneUnusedValuesTransformer) Transform(g *Graph) error {
   122  	// this might need multiple runs in order to ensure that pruning a value
   123  	// doesn't effect a previously checked value.
   124  	for removed := 0; ; removed = 0 {
   125  		for _, v := range g.Vertices() {
   126  			switch v.(type) {
   127  			case *NodeApplyableOutput, *NodeLocal:
   128  				// OK
   129  			default:
   130  				continue
   131  			}
   132  
   133  			dependants := g.UpEdges(v)
   134  
   135  			switch dependants.Len() {
   136  			case 0:
   137  				// nothing at all depends on this
   138  				g.Remove(v)
   139  				removed++
   140  			case 1:
   141  				// because an output's destroy node always depends on the output,
   142  				// we need to check for the case of a single destroy node.
   143  				d := dependants.List()[0]
   144  				if _, ok := d.(*NodeDestroyableOutput); ok {
   145  					g.Remove(v)
   146  					removed++
   147  				}
   148  			}
   149  		}
   150  		if removed == 0 {
   151  			break
   152  		}
   153  	}
   154  
   155  	return nil
   156  }
   157  
   158  // ReferenceMap is a structure that can be used to efficiently check
   159  // for references on a graph.
   160  type ReferenceMap struct {
   161  	// m is the mapping of referenceable name to list of verticies that
   162  	// implement that name. This is built on initialization.
   163  	references   map[string][]dag.Vertex
   164  	referencedBy map[string][]dag.Vertex
   165  }
   166  
   167  // References returns the list of vertices that this vertex
   168  // references along with any missing references.
   169  func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []string) {
   170  	rn, ok := v.(GraphNodeReferencer)
   171  	if !ok {
   172  		return nil, nil
   173  	}
   174  
   175  	var matches []dag.Vertex
   176  	var missing []string
   177  	prefix := m.prefix(v)
   178  
   179  	for _, ns := range rn.References() {
   180  		found := false
   181  		for _, n := range strings.Split(ns, "/") {
   182  			n = prefix + n
   183  			parents, ok := m.references[n]
   184  			if !ok {
   185  				continue
   186  			}
   187  
   188  			// Mark that we found a match
   189  			found = true
   190  
   191  			for _, p := range parents {
   192  				// don't include self-references
   193  				if p == v {
   194  					continue
   195  				}
   196  				matches = append(matches, p)
   197  			}
   198  
   199  			break
   200  		}
   201  
   202  		if !found {
   203  			missing = append(missing, ns)
   204  		}
   205  	}
   206  
   207  	return matches, missing
   208  }
   209  
   210  // ReferencedBy returns the list of vertices that reference the
   211  // vertex passed in.
   212  func (m *ReferenceMap) ReferencedBy(v dag.Vertex) []dag.Vertex {
   213  	rn, ok := v.(GraphNodeReferenceable)
   214  	if !ok {
   215  		return nil
   216  	}
   217  
   218  	var matches []dag.Vertex
   219  	prefix := m.prefix(v)
   220  	for _, n := range rn.ReferenceableName() {
   221  		n = prefix + n
   222  		children, ok := m.referencedBy[n]
   223  		if !ok {
   224  			continue
   225  		}
   226  
   227  		// Make sure this isn't a self reference, which isn't included
   228  		selfRef := false
   229  		for _, p := range children {
   230  			if p == v {
   231  				selfRef = true
   232  				break
   233  			}
   234  		}
   235  		if selfRef {
   236  			continue
   237  		}
   238  
   239  		matches = append(matches, children...)
   240  	}
   241  
   242  	return matches
   243  }
   244  
   245  func (m *ReferenceMap) prefix(v dag.Vertex) string {
   246  	// If the node is stating it is already fully qualified then
   247  	// we don't have to create the prefix!
   248  	if gn, ok := v.(GraphNodeReferenceGlobal); ok && gn.ReferenceGlobal() {
   249  		return ""
   250  	}
   251  
   252  	// Create the prefix based on the path
   253  	var prefix string
   254  	if pn, ok := v.(GraphNodeSubPath); ok {
   255  		if path := normalizeModulePath(pn.Path()); len(path) > 1 {
   256  			prefix = modulePrefixStr(path) + "."
   257  		}
   258  	}
   259  
   260  	return prefix
   261  }
   262  
   263  // NewReferenceMap is used to create a new reference map for the
   264  // given set of vertices.
   265  func NewReferenceMap(vs []dag.Vertex) *ReferenceMap {
   266  	var m ReferenceMap
   267  
   268  	// Build the lookup table
   269  	refMap := make(map[string][]dag.Vertex)
   270  	for _, v := range vs {
   271  		// We're only looking for referenceable nodes
   272  		rn, ok := v.(GraphNodeReferenceable)
   273  		if !ok {
   274  			continue
   275  		}
   276  
   277  		// Go through and cache them
   278  		prefix := m.prefix(v)
   279  		for _, n := range rn.ReferenceableName() {
   280  			n = prefix + n
   281  			refMap[n] = append(refMap[n], v)
   282  		}
   283  
   284  		// If there is a path, it is always referenceable by that. For
   285  		// example, if this is a referenceable thing at path []string{"foo"},
   286  		// then it can be referenced at "module.foo"
   287  		if pn, ok := v.(GraphNodeSubPath); ok {
   288  			for _, p := range ReferenceModulePath(pn.Path()) {
   289  				refMap[p] = append(refMap[p], v)
   290  			}
   291  		}
   292  	}
   293  
   294  	// Build the lookup table for referenced by
   295  	refByMap := make(map[string][]dag.Vertex)
   296  	for _, v := range vs {
   297  		// We're only looking for referenceable nodes
   298  		rn, ok := v.(GraphNodeReferencer)
   299  		if !ok {
   300  			continue
   301  		}
   302  
   303  		// Go through and cache them
   304  		prefix := m.prefix(v)
   305  		for _, n := range rn.References() {
   306  			n = prefix + n
   307  			refByMap[n] = append(refByMap[n], v)
   308  		}
   309  	}
   310  
   311  	m.references = refMap
   312  	m.referencedBy = refByMap
   313  	return &m
   314  }
   315  
   316  // Returns the reference name for a module path. The path "foo" would return
   317  // "module.foo". If this is a deeply nested module, it will be every parent
   318  // as well. For example: ["foo", "bar"] would return both "module.foo" and
   319  // "module.foo.module.bar"
   320  func ReferenceModulePath(p []string) []string {
   321  	p = normalizeModulePath(p)
   322  	if len(p) == 1 {
   323  		// Root, no name
   324  		return nil
   325  	}
   326  
   327  	result := make([]string, 0, len(p)-1)
   328  	for i := len(p); i > 1; i-- {
   329  		result = append(result, modulePrefixStr(p[:i]))
   330  	}
   331  
   332  	return result
   333  }
   334  
   335  // ReferencesFromConfig returns the references that a configuration has
   336  // based on the interpolated variables in a configuration.
   337  func ReferencesFromConfig(c *config.RawConfig) []string {
   338  	var result []string
   339  	for _, v := range c.Variables {
   340  		if r := ReferenceFromInterpolatedVar(v); len(r) > 0 {
   341  			result = append(result, r...)
   342  		}
   343  	}
   344  
   345  	return result
   346  }
   347  
   348  // ReferenceFromInterpolatedVar returns the reference from this variable,
   349  // or an empty string if there is no reference.
   350  func ReferenceFromInterpolatedVar(v config.InterpolatedVariable) []string {
   351  	switch v := v.(type) {
   352  	case *config.ModuleVariable:
   353  		return []string{fmt.Sprintf("module.%s.output.%s", v.Name, v.Field)}
   354  	case *config.ResourceVariable:
   355  		id := v.ResourceId()
   356  
   357  		// If we have a multi-reference (splat), then we depend on ALL
   358  		// resources with this type/name.
   359  		if v.Multi && v.Index == -1 {
   360  			return []string{fmt.Sprintf("%s.*", id)}
   361  		}
   362  
   363  		// Otherwise, we depend on a specific index.
   364  		idx := v.Index
   365  		if !v.Multi || v.Index == -1 {
   366  			idx = 0
   367  		}
   368  
   369  		// Depend on the index, as well as "N" which represents the
   370  		// un-expanded set of resources.
   371  		return []string{fmt.Sprintf("%s.%d/%s.N", id, idx, id)}
   372  	case *config.UserVariable:
   373  		return []string{fmt.Sprintf("var.%s", v.Name)}
   374  	case *config.LocalVariable:
   375  		return []string{fmt.Sprintf("local.%s", v.Name)}
   376  	default:
   377  		return nil
   378  	}
   379  }
   380  
   381  func modulePrefixStr(p []string) string {
   382  	// strip "root"
   383  	if len(p) > 0 && p[0] == rootModulePath[0] {
   384  		p = p[1:]
   385  	}
   386  
   387  	parts := make([]string, 0, len(p)*2)
   388  	for _, p := range p {
   389  		parts = append(parts, "module", p)
   390  	}
   391  
   392  	return strings.Join(parts, ".")
   393  }
   394  
   395  func modulePrefixList(result []string, prefix string) []string {
   396  	if prefix != "" {
   397  		for i, v := range result {
   398  			result[i] = fmt.Sprintf("%s.%s", prefix, v)
   399  		}
   400  	}
   401  
   402  	return result
   403  }