]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "log" | |
5 | ||
6 | "github.com/hashicorp/terraform/dag" | |
7 | ) | |
8 | ||
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) | |
16 | } | |
17 | ||
9b12e4fe JC |
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. | |
23 | // | |
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 | |
31 | } | |
32 | ||
bae9f6d2 JC |
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 | |
38 | Targets []string | |
39 | ||
40 | // List of parsed targets, provided by callers like ResourceCountTransform | |
41 | // that already have the targets parsed | |
42 | ParsedTargets []ResourceAddress | |
43 | ||
44 | // Set to true when we're in a `terraform destroy` or a | |
45 | // `terraform plan -destroy` | |
46 | Destroy bool | |
47 | } | |
48 | ||
49 | func (t *TargetsTransformer) Transform(g *Graph) error { | |
50 | if len(t.Targets) > 0 && len(t.ParsedTargets) == 0 { | |
51 | addrs, err := t.parseTargetAddresses() | |
52 | if err != nil { | |
53 | return err | |
54 | } | |
55 | ||
56 | t.ParsedTargets = addrs | |
57 | } | |
58 | ||
59 | if len(t.ParsedTargets) > 0 { | |
60 | targetedNodes, err := t.selectTargetedNodes(g, t.ParsedTargets) | |
61 | if err != nil { | |
62 | return err | |
63 | } | |
64 | ||
65 | for _, v := range g.Vertices() { | |
66 | removable := false | |
67 | if _, ok := v.(GraphNodeResource); ok { | |
68 | removable = true | |
69 | } | |
70 | if vr, ok := v.(RemovableIfNotTargeted); ok { | |
71 | removable = vr.RemoveIfNotTargeted() | |
72 | } | |
73 | if removable && !targetedNodes.Include(v) { | |
74 | log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v)) | |
75 | g.Remove(v) | |
76 | } | |
77 | } | |
78 | } | |
79 | ||
80 | return nil | |
81 | } | |
82 | ||
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) | |
87 | if err != nil { | |
88 | return nil, err | |
89 | } | |
90 | addrs[i] = *ta | |
91 | } | |
92 | ||
93 | return addrs, nil | |
94 | } | |
95 | ||
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) | |
9b12e4fe JC |
102 | |
103 | vertices := g.Vertices() | |
104 | ||
105 | for _, v := range vertices { | |
bae9f6d2 JC |
106 | if t.nodeIsTarget(v, addrs) { |
107 | targetedNodes.Add(v) | |
108 | ||
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 { | |
113 | tn.SetTargets(addrs) | |
114 | } | |
115 | ||
116 | var deps *dag.Set | |
117 | var err error | |
118 | if t.Destroy { | |
119 | deps, err = g.Descendents(v) | |
120 | } else { | |
121 | deps, err = g.Ancestors(v) | |
122 | } | |
123 | if err != nil { | |
124 | return nil, err | |
125 | } | |
126 | ||
127 | for _, d := range deps.List() { | |
128 | targetedNodes.Add(d) | |
129 | } | |
130 | } | |
131 | } | |
132 | ||
9b12e4fe JC |
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 | |
139 | // | |
140 | // We'll keep looping until we stop targeting more nodes. | |
141 | queue := targetedNodes.List() | |
142 | for len(queue) > 0 { | |
143 | vertices := queue | |
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. | |
150 | continue | |
151 | } | |
152 | ||
153 | dependers = dependers.Filter(func(dv interface{}) bool { | |
154 | // Can ignore nodes that are already targeted | |
155 | /*if targetedNodes.Include(dv) { | |
156 | return false | |
157 | }*/ | |
158 | ||
159 | _, ok := dv.(GraphNodeTargetDownstream) | |
160 | return ok | |
161 | }) | |
162 | ||
163 | if dependers.Len() == 0 { | |
164 | continue | |
165 | } | |
166 | ||
167 | for _, dv := range dependers.List() { | |
168 | if targetedNodes.Include(dv) { | |
169 | // Already present, so nothing to do | |
170 | continue | |
171 | } | |
172 | ||
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) | |
179 | ||
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) | |
185 | } | |
186 | } | |
187 | } | |
188 | } | |
189 | ||
bae9f6d2 JC |
190 | return targetedNodes, nil |
191 | } | |
192 | ||
193 | func (t *TargetsTransformer) nodeIsTarget( | |
194 | v dag.Vertex, addrs []ResourceAddress) bool { | |
195 | r, ok := v.(GraphNodeResource) | |
196 | if !ok { | |
197 | return false | |
198 | } | |
199 | ||
200 | addr := r.ResourceAddr() | |
201 | for _, targetAddr := range addrs { | |
202 | if targetAddr.Equals(addr) { | |
203 | return true | |
204 | } | |
205 | } | |
206 | ||
207 | return false | |
208 | } | |
209 | ||
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 | |
219 | } |