1package terraform
2
3import (
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.
18type 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.
25func (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
62func (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.
168func (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.
208type 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
226func (r *StateFilterResult) String() string {
227	return fmt.Sprintf("%T: %s", r.Value, r.Address)
228}
229
230func (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.
246type StateFilterResultSlice []*StateFilterResult
247
248func (s StateFilterResultSlice) Len() int      { return len(s) }
249func (s StateFilterResultSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
250func (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}
268