| 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 |
| } |