]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/terraform/transform_targets.go
vendor: github.com/hashicorp/terraform/...@v0.10.0
[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
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.
14type 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.
29type 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.
36type 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
c680a8e1
RS
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
bae9f6d2
JC
50 // Set to true when we're in a `terraform destroy` or a
51 // `terraform plan -destroy`
52 Destroy bool
53}
54
55func (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
89func (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.
105func (t *TargetsTransformer) selectTargetedNodes(
106 g *Graph, addrs []ResourceAddress) (*dag.Set, error) {
107 targetedNodes := new(dag.Set)
9b12e4fe
JC
108
109 vertices := g.Vertices()
110
111 for _, v := range vertices {
bae9f6d2
JC
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
9b12e4fe
JC
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
bae9f6d2
JC
196 return targetedNodes, nil
197}
198
199func (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 {
c680a8e1
RS
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) {
bae9f6d2
JC
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.
228type RemovableIfNotTargeted interface {
229 RemoveIfNotTargeted() bool
230}