]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/terraform/transform_targets.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / transform_targets.go
CommitLineData
bae9f6d2
JC
1package terraform
2
3import (
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.
15type 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.
30type 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.
37type 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
52func (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.
82func (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 117func (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.
189func 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
223func (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.
265type RemovableIfNotTargeted interface {
266 RemoveIfNotTargeted() bool
267}