]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "sort" | |
6 | ) | |
7 | ||
8 | // StateFilter is responsible for filtering and searching a state. | |
9 | // | |
10 | // This is a separate struct from State rather than a method on State | |
11 | // because StateFilter might create sidecar data structures to optimize | |
12 | // filtering on the state. | |
13 | // | |
14 | // If you change the State, the filter created is invalid and either | |
15 | // Reset should be called or a new one should be allocated. StateFilter | |
16 | // will not watch State for changes and do this for you. If you filter after | |
17 | // changing the State without calling Reset, the behavior is not defined. | |
18 | type StateFilter struct { | |
19 | State *State | |
20 | } | |
21 | ||
22 | // Filter takes the addresses specified by fs and finds all the matches. | |
23 | // The values of fs are resource addressing syntax that can be parsed by | |
24 | // ParseResourceAddress. | |
25 | func (f *StateFilter) Filter(fs ...string) ([]*StateFilterResult, error) { | |
26 | // Parse all the addresses | |
27 | as := make([]*ResourceAddress, len(fs)) | |
28 | for i, v := range fs { | |
29 | a, err := ParseResourceAddress(v) | |
30 | if err != nil { | |
31 | return nil, fmt.Errorf("Error parsing address '%s': %s", v, err) | |
32 | } | |
33 | ||
34 | as[i] = a | |
35 | } | |
36 | ||
37 | // If we weren't given any filters, then we list all | |
38 | if len(fs) == 0 { | |
39 | as = append(as, &ResourceAddress{Index: -1}) | |
40 | } | |
41 | ||
42 | // Filter each of the address. We keep track of this in a map to | |
43 | // strip duplicates. | |
44 | resultSet := make(map[string]*StateFilterResult) | |
45 | for _, a := range as { | |
46 | for _, r := range f.filterSingle(a) { | |
47 | resultSet[r.String()] = r | |
48 | } | |
49 | } | |
50 | ||
51 | // Make the result list | |
52 | results := make([]*StateFilterResult, 0, len(resultSet)) | |
53 | for _, v := range resultSet { | |
54 | results = append(results, v) | |
55 | } | |
56 | ||
57 | // Sort them and return | |
58 | sort.Sort(StateFilterResultSlice(results)) | |
59 | return results, nil | |
60 | } | |
61 | ||
62 | func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult { | |
63 | // The slice to keep track of results | |
64 | var results []*StateFilterResult | |
65 | ||
66 | // Go through modules first. | |
67 | modules := make([]*ModuleState, 0, len(f.State.Modules)) | |
68 | for _, m := range f.State.Modules { | |
69 | if f.relevant(a, m) { | |
70 | modules = append(modules, m) | |
71 | ||
72 | // Only add the module to the results if we haven't specified a type. | |
73 | // We also ignore the root module. | |
74 | if a.Type == "" && len(m.Path) > 1 { | |
75 | results = append(results, &StateFilterResult{ | |
76 | Path: m.Path[1:], | |
77 | Address: (&ResourceAddress{Path: m.Path[1:]}).String(), | |
78 | Value: m, | |
79 | }) | |
80 | } | |
81 | } | |
82 | } | |
83 | ||
84 | // With the modules set, go through all the resources within | |
85 | // the modules to find relevant resources. | |
86 | for _, m := range modules { | |
87 | for n, r := range m.Resources { | |
88 | // The name in the state contains valuable information. Parse. | |
89 | key, err := ParseResourceStateKey(n) | |
90 | if err != nil { | |
91 | // If we get an error parsing, then just ignore it | |
92 | // out of the state. | |
93 | continue | |
94 | } | |
95 | ||
96 | // Older states and test fixtures often don't contain the | |
97 | // type directly on the ResourceState. We add this so StateFilter | |
98 | // is a bit more robust. | |
99 | if r.Type == "" { | |
100 | r.Type = key.Type | |
101 | } | |
102 | ||
103 | if f.relevant(a, r) { | |
104 | if a.Name != "" && a.Name != key.Name { | |
105 | // Name doesn't match | |
106 | continue | |
107 | } | |
108 | ||
109 | if a.Index >= 0 && key.Index != a.Index { | |
110 | // Index doesn't match | |
111 | continue | |
112 | } | |
113 | ||
114 | if a.Name != "" && a.Name != key.Name { | |
115 | continue | |
116 | } | |
117 | ||
118 | // Build the address for this resource | |
119 | addr := &ResourceAddress{ | |
120 | Path: m.Path[1:], | |
121 | Name: key.Name, | |
122 | Type: key.Type, | |
123 | Index: key.Index, | |
124 | } | |
125 | ||
126 | // Add the resource level result | |
127 | resourceResult := &StateFilterResult{ | |
128 | Path: addr.Path, | |
129 | Address: addr.String(), | |
130 | Value: r, | |
131 | } | |
132 | if !a.InstanceTypeSet { | |
133 | results = append(results, resourceResult) | |
134 | } | |
135 | ||
136 | // Add the instances | |
137 | if r.Primary != nil { | |
138 | addr.InstanceType = TypePrimary | |
139 | addr.InstanceTypeSet = false | |
140 | results = append(results, &StateFilterResult{ | |
141 | Path: addr.Path, | |
142 | Address: addr.String(), | |
143 | Parent: resourceResult, | |
144 | Value: r.Primary, | |
145 | }) | |
146 | } | |
147 | ||
148 | for _, instance := range r.Deposed { | |
149 | if f.relevant(a, instance) { | |
150 | addr.InstanceType = TypeDeposed | |
151 | addr.InstanceTypeSet = true | |
152 | results = append(results, &StateFilterResult{ | |
153 | Path: addr.Path, | |
154 | Address: addr.String(), | |
155 | Parent: resourceResult, | |
156 | Value: instance, | |
157 | }) | |
158 | } | |
159 | } | |
160 | } | |
161 | } | |
162 | } | |
163 | ||
164 | return results | |
165 | } | |
166 | ||
167 | // relevant checks for relevance of this address against the given value. | |
168 | func (f *StateFilter) relevant(addr *ResourceAddress, raw interface{}) bool { | |
169 | switch v := raw.(type) { | |
170 | case *ModuleState: | |
171 | path := v.Path[1:] | |
172 | ||
173 | if len(addr.Path) > len(path) { | |
174 | // Longer path in address means there is no way we match. | |
175 | return false | |
176 | } | |
177 | ||
178 | // Check for a prefix match | |
179 | for i, p := range addr.Path { | |
180 | if path[i] != p { | |
181 | // Any mismatches don't match. | |
182 | return false | |
183 | } | |
184 | } | |
185 | ||
186 | return true | |
187 | case *ResourceState: | |
188 | if addr.Type == "" { | |
189 | // If we have no resource type, then we're interested in all! | |
190 | return true | |
191 | } | |
192 | ||
193 | // If the type doesn't match we fail immediately | |
194 | if v.Type != addr.Type { | |
195 | return false | |
196 | } | |
197 | ||
198 | return true | |
199 | default: | |
200 | // If we don't know about it, let's just say no | |
201 | return false | |
202 | } | |
203 | } | |
204 | ||
205 | // StateFilterResult is a single result from a filter operation. Filter | |
206 | // can match multiple things within a state (module, resource, instance, etc.) | |
207 | // and this unifies that. | |
208 | type StateFilterResult struct { | |
209 | // Module path of the result | |
210 | Path []string | |
211 | ||
212 | // Address is the address that can be used to reference this exact result. | |
213 | Address string | |
214 | ||
215 | // Parent, if non-nil, is a parent of this result. For instances, the | |
216 | // parent would be a resource. For resources, the parent would be | |
217 | // a module. For modules, this is currently nil. | |
218 | Parent *StateFilterResult | |
219 | ||
220 | // Value is the actual value. This must be type switched on. It can be | |
221 | // any data structures that `State` can hold: `ModuleState`, | |
222 | // `ResourceState`, `InstanceState`. | |
223 | Value interface{} | |
224 | } | |
225 | ||
226 | func (r *StateFilterResult) String() string { | |
227 | return fmt.Sprintf("%T: %s", r.Value, r.Address) | |
228 | } | |
229 | ||
230 | func (r *StateFilterResult) sortedType() int { | |
231 | switch r.Value.(type) { | |
232 | case *ModuleState: | |
233 | return 0 | |
234 | case *ResourceState: | |
235 | return 1 | |
236 | case *InstanceState: | |
237 | return 2 | |
238 | default: | |
239 | return 50 | |
240 | } | |
241 | } | |
242 | ||
243 | // StateFilterResultSlice is a slice of results that implements | |
244 | // sort.Interface. The sorting goal is what is most appealing to | |
245 | // human output. | |
246 | type StateFilterResultSlice []*StateFilterResult | |
247 | ||
248 | func (s StateFilterResultSlice) Len() int { return len(s) } | |
249 | func (s StateFilterResultSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } | |
250 | func (s StateFilterResultSlice) Less(i, j int) bool { | |
251 | a, b := s[i], s[j] | |
252 | ||
253 | // if these address contain an index, we want to sort by index rather than name | |
254 | addrA, errA := ParseResourceAddress(a.Address) | |
255 | addrB, errB := ParseResourceAddress(b.Address) | |
256 | if errA == nil && errB == nil && addrA.Name == addrB.Name && addrA.Index != addrB.Index { | |
257 | return addrA.Index < addrB.Index | |
258 | } | |
259 | ||
260 | // If the addresses are different it is just lexographic sorting | |
261 | if a.Address != b.Address { | |
262 | return a.Address < b.Address | |
263 | } | |
264 | ||
265 | // Addresses are the same, which means it matters on the type | |
266 | return a.sortedType() < b.sortedType() | |
267 | } |