]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "log" | |
5 | ||
107c1cdb | 6 | "github.com/hashicorp/terraform/addrs" |
bae9f6d2 JC |
7 | "github.com/hashicorp/terraform/dag" |
8 | ) | |
9 | ||
10 | // GraphNodeTargetable is an interface for graph nodes to implement when they | |
11 | // need to be told about incoming targets. This is useful for nodes that need | |
12 | // to respect targets as they dynamically expand. Note that the list of targets | |
13 | // provided will contain every target provided, and each implementing graph | |
14 | // node must filter this list to targets considered relevant. | |
15 | type GraphNodeTargetable interface { | |
107c1cdb | 16 | SetTargets([]addrs.Targetable) |
bae9f6d2 JC |
17 | } |
18 | ||
9b12e4fe JC |
19 | // GraphNodeTargetDownstream is an interface for graph nodes that need to |
20 | // be remain present under targeting if any of their dependencies are targeted. | |
21 | // TargetDownstream is called with the set of vertices that are direct | |
22 | // dependencies for the node, and it should return true if the node must remain | |
23 | // in the graph in support of those dependencies. | |
24 | // | |
25 | // This is used in situations where the dependency edges are representing an | |
26 | // ordering relationship but the dependency must still be visited if its | |
27 | // dependencies are visited. This is true for outputs, for example, since | |
28 | // they must get updated if any of their dependent resources get updated, | |
29 | // which would not normally be true if one of their dependencies were targeted. | |
30 | type GraphNodeTargetDownstream interface { | |
31 | TargetDownstream(targeted, untargeted *dag.Set) bool | |
32 | } | |
33 | ||
bae9f6d2 JC |
34 | // TargetsTransformer is a GraphTransformer that, when the user specifies a |
35 | // list of resources to target, limits the graph to only those resources and | |
36 | // their dependencies. | |
37 | type TargetsTransformer struct { | |
38 | // List of targeted resource names specified by the user | |
107c1cdb | 39 | Targets []addrs.Targetable |
bae9f6d2 | 40 | |
c680a8e1 RS |
41 | // If set, the index portions of resource addresses will be ignored |
42 | // for comparison. This is used when transforming a graph where | |
43 | // counted resources have not yet been expanded, since otherwise | |
44 | // the unexpanded nodes (which never have indices) would not match. | |
45 | IgnoreIndices bool | |
46 | ||
bae9f6d2 JC |
47 | // Set to true when we're in a `terraform destroy` or a |
48 | // `terraform plan -destroy` | |
49 | Destroy bool | |
50 | } | |
51 | ||
52 | func (t *TargetsTransformer) Transform(g *Graph) error { | |
107c1cdb ND |
53 | if len(t.Targets) > 0 { |
54 | targetedNodes, err := t.selectTargetedNodes(g, t.Targets) | |
bae9f6d2 JC |
55 | if err != nil { |
56 | return err | |
57 | } | |
58 | ||
59 | for _, v := range g.Vertices() { | |
60 | removable := false | |
61 | if _, ok := v.(GraphNodeResource); ok { | |
62 | removable = true | |
63 | } | |
15c0b25d | 64 | |
bae9f6d2 JC |
65 | if vr, ok := v.(RemovableIfNotTargeted); ok { |
66 | removable = vr.RemoveIfNotTargeted() | |
67 | } | |
15c0b25d | 68 | |
bae9f6d2 JC |
69 | if removable && !targetedNodes.Include(v) { |
70 | log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v)) | |
71 | g.Remove(v) | |
72 | } | |
73 | } | |
74 | } | |
75 | ||
76 | return nil | |
77 | } | |
78 | ||
107c1cdb ND |
79 | // Returns a set of targeted nodes. A targeted node is either addressed |
80 | // directly, address indirectly via its container, or it's a dependency of a | |
81 | // targeted node. Destroy mode keeps dependents instead of dependencies. | |
82 | func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targetable) (*dag.Set, error) { | |
bae9f6d2 | 83 | targetedNodes := new(dag.Set) |
9b12e4fe JC |
84 | |
85 | vertices := g.Vertices() | |
86 | ||
87 | for _, v := range vertices { | |
bae9f6d2 JC |
88 | if t.nodeIsTarget(v, addrs) { |
89 | targetedNodes.Add(v) | |
90 | ||
91 | // We inform nodes that ask about the list of targets - helps for nodes | |
92 | // that need to dynamically expand. Note that this only occurs for nodes | |
93 | // that are already directly targeted. | |
94 | if tn, ok := v.(GraphNodeTargetable); ok { | |
95 | tn.SetTargets(addrs) | |
96 | } | |
97 | ||
98 | var deps *dag.Set | |
99 | var err error | |
100 | if t.Destroy { | |
101 | deps, err = g.Descendents(v) | |
102 | } else { | |
103 | deps, err = g.Ancestors(v) | |
104 | } | |
105 | if err != nil { | |
106 | return nil, err | |
107 | } | |
108 | ||
109 | for _, d := range deps.List() { | |
110 | targetedNodes.Add(d) | |
111 | } | |
112 | } | |
113 | } | |
15c0b25d AP |
114 | return t.addDependencies(targetedNodes, g) |
115 | } | |
bae9f6d2 | 116 | |
15c0b25d | 117 | func (t *TargetsTransformer) addDependencies(targetedNodes *dag.Set, g *Graph) (*dag.Set, error) { |
9b12e4fe JC |
118 | // Handle nodes that need to be included if their dependencies are included. |
119 | // This requires multiple passes since we need to catch transitive | |
120 | // dependencies if and only if they are via other nodes that also | |
121 | // support TargetDownstream. For example: | |
122 | // output -> output -> targeted-resource: both outputs need to be targeted | |
123 | // output -> non-targeted-resource -> targeted-resource: output not targeted | |
124 | // | |
125 | // We'll keep looping until we stop targeting more nodes. | |
126 | queue := targetedNodes.List() | |
127 | for len(queue) > 0 { | |
128 | vertices := queue | |
129 | queue = nil // ready to append for next iteration if neccessary | |
130 | for _, v := range vertices { | |
107c1cdb ND |
131 | // providers don't cause transitive dependencies, so don't target |
132 | // downstream from them. | |
133 | if _, ok := v.(GraphNodeProvider); ok { | |
134 | continue | |
135 | } | |
136 | ||
9b12e4fe JC |
137 | dependers := g.UpEdges(v) |
138 | if dependers == nil { | |
139 | // indicates that there are no up edges for this node, so | |
140 | // we have nothing to do here. | |
141 | continue | |
142 | } | |
143 | ||
144 | dependers = dependers.Filter(func(dv interface{}) bool { | |
9b12e4fe JC |
145 | _, ok := dv.(GraphNodeTargetDownstream) |
146 | return ok | |
147 | }) | |
148 | ||
149 | if dependers.Len() == 0 { | |
150 | continue | |
151 | } | |
152 | ||
153 | for _, dv := range dependers.List() { | |
154 | if targetedNodes.Include(dv) { | |
155 | // Already present, so nothing to do | |
156 | continue | |
157 | } | |
158 | ||
159 | // We'll give the node some information about what it's | |
160 | // depending on in case that informs its decision about whether | |
161 | // it is safe to be targeted. | |
162 | deps := g.DownEdges(v) | |
15c0b25d | 163 | |
9b12e4fe JC |
164 | depsTargeted := deps.Intersection(targetedNodes) |
165 | depsUntargeted := deps.Difference(depsTargeted) | |
166 | ||
167 | if dv.(GraphNodeTargetDownstream).TargetDownstream(depsTargeted, depsUntargeted) { | |
168 | targetedNodes.Add(dv) | |
169 | // Need to visit this node on the next pass to see if it | |
170 | // has any transitive dependers. | |
171 | queue = append(queue, dv) | |
172 | } | |
173 | } | |
174 | } | |
175 | } | |
176 | ||
15c0b25d AP |
177 | return targetedNodes.Filter(func(dv interface{}) bool { |
178 | return filterPartialOutputs(dv, targetedNodes, g) | |
179 | }), nil | |
180 | } | |
181 | ||
182 | // Outputs may have been included transitively, but if any of their | |
183 | // dependencies have been pruned they won't be resolvable. | |
184 | // If nothing depends on the output, and the output is missing any | |
185 | // dependencies, remove it from the graph. | |
186 | // This essentially maintains the previous behavior where interpolation in | |
187 | // outputs would fail silently, but can now surface errors where the output | |
188 | // is required. | |
189 | func filterPartialOutputs(v interface{}, targetedNodes *dag.Set, g *Graph) bool { | |
190 | // should this just be done with TargetDownstream? | |
191 | if _, ok := v.(*NodeApplyableOutput); !ok { | |
192 | return true | |
193 | } | |
194 | ||
195 | dependers := g.UpEdges(v) | |
196 | for _, d := range dependers.List() { | |
197 | if _, ok := d.(*NodeCountBoundary); ok { | |
198 | continue | |
199 | } | |
200 | ||
201 | if !targetedNodes.Include(d) { | |
202 | // this one is going to be removed, so it doesn't count | |
203 | continue | |
204 | } | |
205 | ||
206 | // as soon as we see a real dependency, we mark this as | |
207 | // non-removable | |
208 | return true | |
209 | } | |
210 | ||
211 | depends := g.DownEdges(v) | |
212 | ||
213 | for _, d := range depends.List() { | |
214 | if !targetedNodes.Include(d) { | |
215 | log.Printf("[WARN] %s missing targeted dependency %s, removing from the graph", | |
216 | dag.VertexName(v), dag.VertexName(d)) | |
217 | return false | |
218 | } | |
219 | } | |
220 | return true | |
bae9f6d2 JC |
221 | } |
222 | ||
107c1cdb ND |
223 | func (t *TargetsTransformer) nodeIsTarget(v dag.Vertex, targets []addrs.Targetable) bool { |
224 | var vertexAddr addrs.Targetable | |
225 | switch r := v.(type) { | |
226 | case GraphNodeResourceInstance: | |
227 | vertexAddr = r.ResourceInstanceAddr() | |
228 | case GraphNodeResource: | |
229 | vertexAddr = r.ResourceAddr() | |
230 | default: | |
231 | // Only resource and resource instance nodes can be targeted. | |
232 | return false | |
233 | } | |
234 | _, ok := v.(GraphNodeResource) | |
bae9f6d2 JC |
235 | if !ok { |
236 | return false | |
237 | } | |
238 | ||
107c1cdb | 239 | for _, targetAddr := range targets { |
c680a8e1 | 240 | if t.IgnoreIndices { |
107c1cdb ND |
241 | // If we're ignoring indices then we'll convert any resource instance |
242 | // addresses into resource addresses. We don't need to convert | |
243 | // vertexAddr because instance addresses are contained within | |
244 | // their associated resources, and so .TargetContains will take | |
245 | // care of this for us. | |
246 | if instance, isInstance := targetAddr.(addrs.AbsResourceInstance); isInstance { | |
247 | targetAddr = instance.ContainingResource() | |
248 | } | |
c680a8e1 | 249 | } |
107c1cdb | 250 | if targetAddr.TargetContains(vertexAddr) { |
bae9f6d2 JC |
251 | return true |
252 | } | |
253 | } | |
254 | ||
255 | return false | |
256 | } | |
257 | ||
258 | // RemovableIfNotTargeted is a special interface for graph nodes that | |
259 | // aren't directly addressable, but need to be removed from the graph when they | |
260 | // are not targeted. (Nodes that are not directly targeted end up in the set of | |
261 | // targeted nodes because something that _is_ targeted depends on them.) The | |
262 | // initial use case for this interface is GraphNodeConfigVariable, which was | |
263 | // having trouble interpolating for module variables in targeted scenarios that | |
264 | // filtered out the resource node being referenced. | |
265 | type RemovableIfNotTargeted interface { | |
266 | RemoveIfNotTargeted() bool | |
267 | } |