summaryrefslogtreecommitdiff
path: root/vendor/github.com/hashicorp/terraform/terraform/transform_reference.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/terraform/transform_reference.go')
-rw-r--r--vendor/github.com/hashicorp/terraform/terraform/transform_reference.go321
1 files changed, 321 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/terraform/transform_reference.go b/vendor/github.com/hashicorp/terraform/terraform/transform_reference.go
new file mode 100644
index 00000000..c5452354
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/terraform/transform_reference.go
@@ -0,0 +1,321 @@
+package terraform
+
+import (
+ "fmt"
+ "log"
+ "strings"
+
+ "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 {
+ // ReferenceableName is the name by which this can be referenced.
+ // This can be either just the type, or include the field. Example:
+ // "aws_instance.bar" or "aws_instance.bar.id".
+ ReferenceableName() []string
+}
+
+// GraphNodeReferencer must be implemented by nodes that reference other
+// Terraform items and therefore depend on them.
+type GraphNodeReferencer interface {
+ // References are the list of things that this node references. This
+ // can include fields or just the type, just like GraphNodeReferenceable
+ // above.
+ References() []string
+}
+
+// GraphNodeReferenceGlobal is an interface that can optionally be
+// implemented. If ReferenceGlobal returns true, then the References()
+// and ReferenceableName() must be _fully qualified_ with "module.foo.bar"
+// etc.
+//
+// This allows a node to reference and be referenced by a specific name
+// that may cross module boundaries. This can be very dangerous so use
+// this wisely.
+//
+// The primary use case for this is module boundaries (variables coming in).
+type GraphNodeReferenceGlobal interface {
+ // Set to true to signal that references and name are fully
+ // qualified. See the above docs for more information.
+ ReferenceGlobal() bool
+}
+
+// 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
+}
+
+// ReferenceMap is a structure that can be used to efficiently check
+// for references on a graph.
+type ReferenceMap struct {
+ // m is the mapping of referenceable name to list of verticies that
+ // implement that name. This is built on initialization.
+ references map[string][]dag.Vertex
+ referencedBy map[string][]dag.Vertex
+}
+
+// References returns the list of vertices that this vertex
+// references along with any missing references.
+func (m *ReferenceMap) References(v dag.Vertex) ([]dag.Vertex, []string) {
+ rn, ok := v.(GraphNodeReferencer)
+ if !ok {
+ return nil, nil
+ }
+
+ var matches []dag.Vertex
+ var missing []string
+ prefix := m.prefix(v)
+ for _, ns := range rn.References() {
+ found := false
+ for _, n := range strings.Split(ns, "/") {
+ n = prefix + n
+ parents, ok := m.references[n]
+ if !ok {
+ continue
+ }
+
+ // Mark that we found a match
+ found = true
+
+ // Make sure this isn't a self reference, which isn't included
+ selfRef := false
+ for _, p := range parents {
+ if p == v {
+ selfRef = true
+ break
+ }
+ }
+ if selfRef {
+ continue
+ }
+
+ matches = append(matches, parents...)
+ break
+ }
+
+ if !found {
+ missing = append(missing, ns)
+ }
+ }
+
+ return matches, missing
+}
+
+// ReferencedBy returns the list of vertices that reference the
+// vertex passed in.
+func (m *ReferenceMap) ReferencedBy(v dag.Vertex) []dag.Vertex {
+ rn, ok := v.(GraphNodeReferenceable)
+ if !ok {
+ return nil
+ }
+
+ var matches []dag.Vertex
+ prefix := m.prefix(v)
+ for _, n := range rn.ReferenceableName() {
+ n = prefix + n
+ children, ok := m.referencedBy[n]
+ if !ok {
+ continue
+ }
+
+ // Make sure this isn't a self reference, which isn't included
+ selfRef := false
+ for _, p := range children {
+ if p == v {
+ selfRef = true
+ break
+ }
+ }
+ if selfRef {
+ continue
+ }
+
+ matches = append(matches, children...)
+ }
+
+ return matches
+}
+
+func (m *ReferenceMap) prefix(v dag.Vertex) string {
+ // If the node is stating it is already fully qualified then
+ // we don't have to create the prefix!
+ if gn, ok := v.(GraphNodeReferenceGlobal); ok && gn.ReferenceGlobal() {
+ return ""
+ }
+
+ // Create the prefix based on the path
+ var prefix string
+ if pn, ok := v.(GraphNodeSubPath); ok {
+ if path := normalizeModulePath(pn.Path()); len(path) > 1 {
+ prefix = modulePrefixStr(path) + "."
+ }
+ }
+
+ return prefix
+}
+
+// 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
+ refMap := make(map[string][]dag.Vertex)
+ for _, v := range vs {
+ // We're only looking for referenceable nodes
+ rn, ok := v.(GraphNodeReferenceable)
+ if !ok {
+ continue
+ }
+
+ // Go through and cache them
+ prefix := m.prefix(v)
+ for _, n := range rn.ReferenceableName() {
+ n = prefix + n
+ refMap[n] = append(refMap[n], v)
+ }
+
+ // If there is a path, it is always referenceable by that. For
+ // example, if this is a referenceable thing at path []string{"foo"},
+ // then it can be referenced at "module.foo"
+ if pn, ok := v.(GraphNodeSubPath); ok {
+ for _, p := range ReferenceModulePath(pn.Path()) {
+ refMap[p] = append(refMap[p], v)
+ }
+ }
+ }
+
+ // Build the lookup table for referenced by
+ refByMap := make(map[string][]dag.Vertex)
+ for _, v := range vs {
+ // We're only looking for referenceable nodes
+ rn, ok := v.(GraphNodeReferencer)
+ if !ok {
+ continue
+ }
+
+ // Go through and cache them
+ prefix := m.prefix(v)
+ for _, n := range rn.References() {
+ n = prefix + n
+ refByMap[n] = append(refByMap[n], v)
+ }
+ }
+
+ m.references = refMap
+ m.referencedBy = refByMap
+ return &m
+}
+
+// Returns the reference name for a module path. The path "foo" would return
+// "module.foo". If this is a deeply nested module, it will be every parent
+// as well. For example: ["foo", "bar"] would return both "module.foo" and
+// "module.foo.module.bar"
+func ReferenceModulePath(p []string) []string {
+ p = normalizeModulePath(p)
+ if len(p) == 1 {
+ // Root, no name
+ return nil
+ }
+
+ result := make([]string, 0, len(p)-1)
+ for i := len(p); i > 1; i-- {
+ result = append(result, modulePrefixStr(p[:i]))
+ }
+
+ return result
+}
+
+// ReferencesFromConfig returns the references that a configuration has
+// based on the interpolated variables in a configuration.
+func ReferencesFromConfig(c *config.RawConfig) []string {
+ var result []string
+ for _, v := range c.Variables {
+ if r := ReferenceFromInterpolatedVar(v); len(r) > 0 {
+ result = append(result, r...)
+ }
+ }
+
+ return result
+}
+
+// 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)}
+ default:
+ return nil
+ }
+}
+
+func modulePrefixStr(p []string) string {
+ parts := make([]string, 0, len(p)*2)
+ for _, p := range p[1:] {
+ parts = append(parts, "module", p)
+ }
+
+ return strings.Join(parts, ".")
+}
+
+func modulePrefixList(result []string, prefix string) []string {
+ if prefix != "" {
+ for i, v := range result {
+ result[i] = fmt.Sprintf("%s.%s", prefix, v)
+ }
+ }
+
+ return result
+}