github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/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  // ReferenceMap is a structure that can be used to efficiently check
    80  // for references on a graph.
    81  type ReferenceMap struct {
    82  	// m is the mapping of referenceable name to list of verticies that
    83  	// implement that name. This is built on initialization.
    84  	references   map[string][]dag.Vertex
    85  	referencedBy map[string][]dag.Vertex
    86  }
    87  
    88  // References returns the list of vertices that this vertex
    89  // references along with any missing references.
    90  func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []string) {
    91  	rn, ok := v.(GraphNodeReferencer)
    92  	if !ok {
    93  		return nil, nil
    94  	}
    95  
    96  	var matches []dag.Vertex
    97  	var missing []string
    98  	prefix := m.prefix(v)
    99  	for _, ns := range rn.References() {
   100  		found := false
   101  		for _, n := range strings.Split(ns, "/") {
   102  			n = prefix + n
   103  			parents, ok := m.references[n]
   104  			if !ok {
   105  				continue
   106  			}
   107  
   108  			// Mark that we found a match
   109  			found = true
   110  
   111  			// Make sure this isn't a self reference, which isn't included
   112  			selfRef := false
   113  			for _, p := range parents {
   114  				if p == v {
   115  					selfRef = true
   116  					break
   117  				}
   118  			}
   119  			if selfRef {
   120  				continue
   121  			}
   122  
   123  			matches = append(matches, parents...)
   124  			break
   125  		}
   126  
   127  		if !found {
   128  			missing = append(missing, ns)
   129  		}
   130  	}
   131  
   132  	return matches, missing
   133  }
   134  
   135  // ReferencedBy returns the list of vertices that reference the
   136  // vertex passed in.
   137  func (m *ReferenceMap) ReferencedBy(v dag.Vertex) []dag.Vertex {
   138  	rn, ok := v.(GraphNodeReferenceable)
   139  	if !ok {
   140  		return nil
   141  	}
   142  
   143  	var matches []dag.Vertex
   144  	prefix := m.prefix(v)
   145  	for _, n := range rn.ReferenceableName() {
   146  		n = prefix + n
   147  		children, ok := m.referencedBy[n]
   148  		if !ok {
   149  			continue
   150  		}
   151  
   152  		// Make sure this isn't a self reference, which isn't included
   153  		selfRef := false
   154  		for _, p := range children {
   155  			if p == v {
   156  				selfRef = true
   157  				break
   158  			}
   159  		}
   160  		if selfRef {
   161  			continue
   162  		}
   163  
   164  		matches = append(matches, children...)
   165  	}
   166  
   167  	return matches
   168  }
   169  
   170  func (m *ReferenceMap) prefix(v dag.Vertex) string {
   171  	// If the node is stating it is already fully qualified then
   172  	// we don't have to create the prefix!
   173  	if gn, ok := v.(GraphNodeReferenceGlobal); ok && gn.ReferenceGlobal() {
   174  		return ""
   175  	}
   176  
   177  	// Create the prefix based on the path
   178  	var prefix string
   179  	if pn, ok := v.(GraphNodeSubPath); ok {
   180  		if path := normalizeModulePath(pn.Path()); len(path) > 1 {
   181  			prefix = modulePrefixStr(path) + "."
   182  		}
   183  	}
   184  
   185  	return prefix
   186  }
   187  
   188  // NewReferenceMap is used to create a new reference map for the
   189  // given set of vertices.
   190  func NewReferenceMap(vs []dag.Vertex) *ReferenceMap {
   191  	var m ReferenceMap
   192  
   193  	// Build the lookup table
   194  	refMap := make(map[string][]dag.Vertex)
   195  	for _, v := range vs {
   196  		// We're only looking for referenceable nodes
   197  		rn, ok := v.(GraphNodeReferenceable)
   198  		if !ok {
   199  			continue
   200  		}
   201  
   202  		// Go through and cache them
   203  		prefix := m.prefix(v)
   204  		for _, n := range rn.ReferenceableName() {
   205  			n = prefix + n
   206  			refMap[n] = append(refMap[n], v)
   207  		}
   208  
   209  		// If there is a path, it is always referenceable by that. For
   210  		// example, if this is a referenceable thing at path []string{"foo"},
   211  		// then it can be referenced at "module.foo"
   212  		if pn, ok := v.(GraphNodeSubPath); ok {
   213  			for _, p := range ReferenceModulePath(pn.Path()) {
   214  				refMap[p] = append(refMap[p], v)
   215  			}
   216  		}
   217  	}
   218  
   219  	// Build the lookup table for referenced by
   220  	refByMap := make(map[string][]dag.Vertex)
   221  	for _, v := range vs {
   222  		// We're only looking for referenceable nodes
   223  		rn, ok := v.(GraphNodeReferencer)
   224  		if !ok {
   225  			continue
   226  		}
   227  
   228  		// Go through and cache them
   229  		prefix := m.prefix(v)
   230  		for _, n := range rn.References() {
   231  			n = prefix + n
   232  			refByMap[n] = append(refByMap[n], v)
   233  		}
   234  	}
   235  
   236  	m.references = refMap
   237  	m.referencedBy = refByMap
   238  	return &m
   239  }
   240  
   241  // Returns the reference name for a module path. The path "foo" would return
   242  // "module.foo". If this is a deeply nested module, it will be every parent
   243  // as well. For example: ["foo", "bar"] would return both "module.foo" and
   244  // "module.foo.module.bar"
   245  func ReferenceModulePath(p []string) []string {
   246  	p = normalizeModulePath(p)
   247  	if len(p) == 1 {
   248  		// Root, no name
   249  		return nil
   250  	}
   251  
   252  	result := make([]string, 0, len(p)-1)
   253  	for i := len(p); i > 1; i-- {
   254  		result = append(result, modulePrefixStr(p[:i]))
   255  	}
   256  
   257  	return result
   258  }
   259  
   260  // ReferencesFromConfig returns the references that a configuration has
   261  // based on the interpolated variables in a configuration.
   262  func ReferencesFromConfig(c *config.RawConfig) []string {
   263  	var result []string
   264  	for _, v := range c.Variables {
   265  		if r := ReferenceFromInterpolatedVar(v); len(r) > 0 {
   266  			result = append(result, r...)
   267  		}
   268  	}
   269  
   270  	return result
   271  }
   272  
   273  // ReferenceFromInterpolatedVar returns the reference from this variable,
   274  // or an empty string if there is no reference.
   275  func ReferenceFromInterpolatedVar(v config.InterpolatedVariable) []string {
   276  	switch v := v.(type) {
   277  	case *config.ModuleVariable:
   278  		return []string{fmt.Sprintf("module.%s.output.%s", v.Name, v.Field)}
   279  	case *config.ResourceVariable:
   280  		id := v.ResourceId()
   281  
   282  		// If we have a multi-reference (splat), then we depend on ALL
   283  		// resources with this type/name.
   284  		if v.Multi && v.Index == -1 {
   285  			return []string{fmt.Sprintf("%s.*", id)}
   286  		}
   287  
   288  		// Otherwise, we depend on a specific index.
   289  		idx := v.Index
   290  		if !v.Multi || v.Index == -1 {
   291  			idx = 0
   292  		}
   293  
   294  		// Depend on the index, as well as "N" which represents the
   295  		// un-expanded set of resources.
   296  		return []string{fmt.Sprintf("%s.%d/%s.N", id, idx, id)}
   297  	case *config.UserVariable:
   298  		return []string{fmt.Sprintf("var.%s", v.Name)}
   299  	default:
   300  		return nil
   301  	}
   302  }
   303  
   304  func modulePrefixStr(p []string) string {
   305  	parts := make([]string, 0, len(p)*2)
   306  	for _, p := range p[1:] {
   307  		parts = append(parts, "module", p)
   308  	}
   309  
   310  	return strings.Join(parts, ".")
   311  }
   312  
   313  func modulePrefixList(result []string, prefix string) []string {
   314  	if prefix != "" {
   315  		for i, v := range result {
   316  			result[i] = fmt.Sprintf("%s.%s", prefix, v)
   317  		}
   318  	}
   319  
   320  	return result
   321  }