6 "github.com/hashicorp/terraform/dag"
9 // GraphNodeTargetable is an interface for graph nodes to implement when they
10 // need to be told about incoming targets. This is useful for nodes that need
11 // to respect targets as they dynamically expand. Note that the list of targets
12 // provided will contain every target provided, and each implementing graph
13 // node must filter this list to targets considered relevant.
14 type GraphNodeTargetable interface {
15 SetTargets([]ResourceAddress)
18 // GraphNodeTargetDownstream is an interface for graph nodes that need to
19 // be remain present under targeting if any of their dependencies are targeted.
20 // TargetDownstream is called with the set of vertices that are direct
21 // dependencies for the node, and it should return true if the node must remain
22 // in the graph in support of those dependencies.
24 // This is used in situations where the dependency edges are representing an
25 // ordering relationship but the dependency must still be visited if its
26 // dependencies are visited. This is true for outputs, for example, since
27 // they must get updated if any of their dependent resources get updated,
28 // which would not normally be true if one of their dependencies were targeted.
29 type GraphNodeTargetDownstream interface {
30 TargetDownstream(targeted, untargeted *dag.Set) bool
33 // TargetsTransformer is a GraphTransformer that, when the user specifies a
34 // list of resources to target, limits the graph to only those resources and
35 // their dependencies.
36 type TargetsTransformer struct {
37 // List of targeted resource names specified by the user
40 // List of parsed targets, provided by callers like ResourceCountTransform
41 // that already have the targets parsed
42 ParsedTargets []ResourceAddress
44 // Set to true when we're in a `terraform destroy` or a
45 // `terraform plan -destroy`
49 func (t *TargetsTransformer) Transform(g *Graph) error {
50 if len(t.Targets) > 0 && len(t.ParsedTargets) == 0 {
51 addrs, err := t.parseTargetAddresses()
56 t.ParsedTargets = addrs
59 if len(t.ParsedTargets) > 0 {
60 targetedNodes, err := t.selectTargetedNodes(g, t.ParsedTargets)
65 for _, v := range g.Vertices() {
67 if _, ok := v.(GraphNodeResource); ok {
70 if vr, ok := v.(RemovableIfNotTargeted); ok {
71 removable = vr.RemoveIfNotTargeted()
73 if removable && !targetedNodes.Include(v) {
74 log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v))
83 func (t *TargetsTransformer) parseTargetAddresses() ([]ResourceAddress, error) {
84 addrs := make([]ResourceAddress, len(t.Targets))
85 for i, target := range t.Targets {
86 ta, err := ParseResourceAddress(target)
96 // Returns the list of targeted nodes. A targeted node is either addressed
97 // directly, or is an Ancestor of a targeted node. Destroy mode keeps
98 // Descendents instead of Ancestors.
99 func (t *TargetsTransformer) selectTargetedNodes(
100 g *Graph, addrs []ResourceAddress) (*dag.Set, error) {
101 targetedNodes := new(dag.Set)
103 vertices := g.Vertices()
105 for _, v := range vertices {
106 if t.nodeIsTarget(v, addrs) {
109 // We inform nodes that ask about the list of targets - helps for nodes
110 // that need to dynamically expand. Note that this only occurs for nodes
111 // that are already directly targeted.
112 if tn, ok := v.(GraphNodeTargetable); ok {
119 deps, err = g.Descendents(v)
121 deps, err = g.Ancestors(v)
127 for _, d := range deps.List() {
133 // Handle nodes that need to be included if their dependencies are included.
134 // This requires multiple passes since we need to catch transitive
135 // dependencies if and only if they are via other nodes that also
136 // support TargetDownstream. For example:
137 // output -> output -> targeted-resource: both outputs need to be targeted
138 // output -> non-targeted-resource -> targeted-resource: output not targeted
140 // We'll keep looping until we stop targeting more nodes.
141 queue := targetedNodes.List()
144 queue = nil // ready to append for next iteration if neccessary
145 for _, v := range vertices {
146 dependers := g.UpEdges(v)
147 if dependers == nil {
148 // indicates that there are no up edges for this node, so
149 // we have nothing to do here.
153 dependers = dependers.Filter(func(dv interface{}) bool {
154 // Can ignore nodes that are already targeted
155 /*if targetedNodes.Include(dv) {
159 _, ok := dv.(GraphNodeTargetDownstream)
163 if dependers.Len() == 0 {
167 for _, dv := range dependers.List() {
168 if targetedNodes.Include(dv) {
169 // Already present, so nothing to do
173 // We'll give the node some information about what it's
174 // depending on in case that informs its decision about whether
175 // it is safe to be targeted.
176 deps := g.DownEdges(v)
177 depsTargeted := deps.Intersection(targetedNodes)
178 depsUntargeted := deps.Difference(depsTargeted)
180 if dv.(GraphNodeTargetDownstream).TargetDownstream(depsTargeted, depsUntargeted) {
181 targetedNodes.Add(dv)
182 // Need to visit this node on the next pass to see if it
183 // has any transitive dependers.
184 queue = append(queue, dv)
190 return targetedNodes, nil
193 func (t *TargetsTransformer) nodeIsTarget(
194 v dag.Vertex, addrs []ResourceAddress) bool {
195 r, ok := v.(GraphNodeResource)
200 addr := r.ResourceAddr()
201 for _, targetAddr := range addrs {
202 if targetAddr.Equals(addr) {
210 // RemovableIfNotTargeted is a special interface for graph nodes that
211 // aren't directly addressable, but need to be removed from the graph when they
212 // are not targeted. (Nodes that are not directly targeted end up in the set of
213 // targeted nodes because something that _is_ targeted depends on them.) The
214 // initial use case for this interface is GraphNodeConfigVariable, which was
215 // having trouble interpolating for module variables in targeted scenarios that
216 // filtered out the resource node being referenced.
217 type RemovableIfNotTargeted interface {
218 RemoveIfNotTargeted() bool