/
transform_targets.go
161 lines (138 loc) · 4.5 KB
/
transform_targets.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package terraform
import (
"log"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/dag"
)
// GraphNodeTargetable is an interface for graph nodes to implement when they
// need to be told about incoming targets. This is useful for nodes that need
// to respect targets as they dynamically expand. Note that the list of targets
// provided will contain every target provided, and each implementing graph
// node must filter this list to targets considered relevant.
type GraphNodeTargetable interface {
SetTargets([]addrs.Targetable)
}
// TargetsTransformer is a GraphTransformer that, when the user specifies a
// list of resources to target, limits the graph to only those resources and
// their dependencies.
type TargetsTransformer struct {
// List of targeted resource names specified by the user
Targets []addrs.Targetable
// If set, the index portions of resource addresses will be ignored
// for comparison. This is used when transforming a graph where
// counted resources have not yet been expanded, since otherwise
// the unexpanded nodes (which never have indices) would not match.
IgnoreIndices bool
}
func (t *TargetsTransformer) Transform(g *Graph) error {
if len(t.Targets) > 0 {
targetedNodes, err := t.selectTargetedNodes(g, t.Targets)
if err != nil {
return err
}
for _, v := range g.Vertices() {
if !targetedNodes.Include(v) {
log.Printf("[DEBUG] Removing %q, filtered by targeting.", dag.VertexName(v))
g.Remove(v)
}
}
}
return nil
}
// Returns a set of targeted nodes. A targeted node is either addressed
// directly, address indirectly via its container, or it's a dependency of a
// targeted node.
func (t *TargetsTransformer) selectTargetedNodes(g *Graph, addrs []addrs.Targetable) (dag.Set, error) {
targetedNodes := make(dag.Set)
vertices := g.Vertices()
for _, v := range vertices {
if t.nodeIsTarget(v, addrs) {
targetedNodes.Add(v)
// We inform nodes that ask about the list of targets - helps for nodes
// that need to dynamically expand. Note that this only occurs for nodes
// that are already directly targeted.
if tn, ok := v.(GraphNodeTargetable); ok {
tn.SetTargets(addrs)
}
deps, _ := g.Ancestors(v)
for _, d := range deps {
targetedNodes.Add(d)
}
}
}
// It is expected that outputs which are only derived from targeted
// resources are also updated. While we don't include any other possible
// side effects from the targeted nodes, these are added because outputs
// cannot be targeted on their own.
// Start by finding the root module output nodes themselves
for _, v := range vertices {
// outputs are all temporary value types
tv, ok := v.(graphNodeTemporaryValue)
if !ok {
continue
}
// root module outputs indicate that while they are an output type,
// they not temporary and will return false here.
if tv.temporaryValue() {
continue
}
// If this output is descended only from targeted resources, then we
// will keep it
deps, _ := g.Ancestors(v)
found := 0
for _, d := range deps {
switch d.(type) {
case GraphNodeResourceInstance:
case GraphNodeConfigResource:
default:
continue
}
if !targetedNodes.Include(d) {
// this dependency isn't being targeted, so we can't process this
// output
found = 0
break
}
found++
}
if found > 0 {
// we found an output we can keep; add it, and all it's dependencies
targetedNodes.Add(v)
for _, d := range deps {
targetedNodes.Add(d)
}
}
}
return targetedNodes, nil
}
func (t *TargetsTransformer) nodeIsTarget(v dag.Vertex, targets []addrs.Targetable) bool {
var vertexAddr addrs.Targetable
switch r := v.(type) {
case GraphNodeResourceInstance:
vertexAddr = r.ResourceInstanceAddr()
case GraphNodeConfigResource:
vertexAddr = r.ResourceAddr()
default:
// Only resource and resource instance nodes can be targeted.
return false
}
for _, targetAddr := range targets {
if t.IgnoreIndices {
// If we're ignoring indices then we'll convert any resource instance
// addresses into resource addresses. We don't need to convert
// vertexAddr because instance addresses are contained within
// their associated resources, and so .TargetContains will take
// care of this for us.
switch instance := targetAddr.(type) {
case addrs.AbsResourceInstance:
targetAddr = instance.ContainingResource().Config()
case addrs.ModuleInstance:
targetAddr = instance.Module()
}
}
if targetAddr.TargetContains(vertexAddr) {
return true
}
}
return false
}