]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/terraform/transform_targets.go
Merge pull request #3 from terraform-providers/vendor-tf-0.10
[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 // If set, the index portions of resource addresses will be ignored
45 // for comparison. This is used when transforming a graph where
46 // counted resources have not yet been expanded, since otherwise
47 // the unexpanded nodes (which never have indices) would not match.
48 IgnoreIndices bool
49
50 // Set to true when we're in a `terraform destroy` or a
51 // `terraform plan -destroy`
52 Destroy bool
53 }
54
55 func (t *TargetsTransformer) Transform(g *Graph) error {
56 if len(t.Targets) > 0 && len(t.ParsedTargets) == 0 {
57 addrs, err := t.parseTargetAddresses()
58 if err != nil {
59 return err
60 }
61
62 t.ParsedTargets = addrs
63 }
64
65 if len(t.ParsedTargets) > 0 {
66 targetedNodes, err := t.selectTargetedNodes(g, t.ParsedTargets)
67 if err != nil {
68 return err
69 }
70
71 for _, v := range g.Vertices() {
72 removable := false
73 if _, ok := v.(GraphNodeResource); ok {
74 removable = true
75 }
76 if vr, ok := v.(RemovableIfNotTargeted); ok {
77 removable = vr.RemoveIfNotTargeted()
78 }
79 if removable && !targetedNodes.Include(v) {
80 log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v))
81 g.Remove(v)
82 }
83 }
84 }
85
86 return nil
87 }
88
89 func (t *TargetsTransformer) parseTargetAddresses() ([]ResourceAddress, error) {
90 addrs := make([]ResourceAddress, len(t.Targets))
91 for i, target := range t.Targets {
92 ta, err := ParseResourceAddress(target)
93 if err != nil {
94 return nil, err
95 }
96 addrs[i] = *ta
97 }
98
99 return addrs, nil
100 }
101
102 // Returns the list of targeted nodes. A targeted node is either addressed
103 // directly, or is an Ancestor of a targeted node. Destroy mode keeps
104 // Descendents instead of Ancestors.
105 func (t *TargetsTransformer) selectTargetedNodes(
106 g *Graph, addrs []ResourceAddress) (*dag.Set, error) {
107 targetedNodes := new(dag.Set)
108
109 vertices := g.Vertices()
110
111 for _, v := range vertices {
112 if t.nodeIsTarget(v, addrs) {
113 targetedNodes.Add(v)
114
115 // We inform nodes that ask about the list of targets - helps for nodes
116 // that need to dynamically expand. Note that this only occurs for nodes
117 // that are already directly targeted.
118 if tn, ok := v.(GraphNodeTargetable); ok {
119 tn.SetTargets(addrs)
120 }
121
122 var deps *dag.Set
123 var err error
124 if t.Destroy {
125 deps, err = g.Descendents(v)
126 } else {
127 deps, err = g.Ancestors(v)
128 }
129 if err != nil {
130 return nil, err
131 }
132
133 for _, d := range deps.List() {
134 targetedNodes.Add(d)
135 }
136 }
137 }
138
139 // Handle nodes that need to be included if their dependencies are included.
140 // This requires multiple passes since we need to catch transitive
141 // dependencies if and only if they are via other nodes that also
142 // support TargetDownstream. For example:
143 // output -> output -> targeted-resource: both outputs need to be targeted
144 // output -> non-targeted-resource -> targeted-resource: output not targeted
145 //
146 // We'll keep looping until we stop targeting more nodes.
147 queue := targetedNodes.List()
148 for len(queue) > 0 {
149 vertices := queue
150 queue = nil // ready to append for next iteration if neccessary
151 for _, v := range vertices {
152 dependers := g.UpEdges(v)
153 if dependers == nil {
154 // indicates that there are no up edges for this node, so
155 // we have nothing to do here.
156 continue
157 }
158
159 dependers = dependers.Filter(func(dv interface{}) bool {
160 // Can ignore nodes that are already targeted
161 /*if targetedNodes.Include(dv) {
162 return false
163 }*/
164
165 _, ok := dv.(GraphNodeTargetDownstream)
166 return ok
167 })
168
169 if dependers.Len() == 0 {
170 continue
171 }
172
173 for _, dv := range dependers.List() {
174 if targetedNodes.Include(dv) {
175 // Already present, so nothing to do
176 continue
177 }
178
179 // We'll give the node some information about what it's
180 // depending on in case that informs its decision about whether
181 // it is safe to be targeted.
182 deps := g.DownEdges(v)
183 depsTargeted := deps.Intersection(targetedNodes)
184 depsUntargeted := deps.Difference(depsTargeted)
185
186 if dv.(GraphNodeTargetDownstream).TargetDownstream(depsTargeted, depsUntargeted) {
187 targetedNodes.Add(dv)
188 // Need to visit this node on the next pass to see if it
189 // has any transitive dependers.
190 queue = append(queue, dv)
191 }
192 }
193 }
194 }
195
196 return targetedNodes, nil
197 }
198
199 func (t *TargetsTransformer) nodeIsTarget(
200 v dag.Vertex, addrs []ResourceAddress) bool {
201 r, ok := v.(GraphNodeResource)
202 if !ok {
203 return false
204 }
205
206 addr := r.ResourceAddr()
207 for _, targetAddr := range addrs {
208 if t.IgnoreIndices {
209 // targetAddr is not a pointer, so we can safely mutate it without
210 // interfering with references elsewhere.
211 targetAddr.Index = -1
212 }
213 if targetAddr.Contains(addr) {
214 return true
215 }
216 }
217
218 return false
219 }
220
221 // RemovableIfNotTargeted is a special interface for graph nodes that
222 // aren't directly addressable, but need to be removed from the graph when they
223 // are not targeted. (Nodes that are not directly targeted end up in the set of
224 // targeted nodes because something that _is_ targeted depends on them.) The
225 // initial use case for this interface is GraphNodeConfigVariable, which was
226 // having trouble interpolating for module variables in targeted scenarios that
227 // filtered out the resource node being referenced.
228 type RemovableIfNotTargeted interface {
229 RemoveIfNotTargeted() bool
230 }