]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/terraform/transform_targets.go
125f9e302155a5a8b817d0d3afbdc6b7aff574eb
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / transform_targets.go
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
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
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)
102
103 vertices := g.Vertices()
104
105 for _, v := range vertices {
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
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
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 }