summaryrefslogtreecommitdiff
path: root/vendor/github.com/hashicorp/terraform/terraform/state_filter.go
blob: 2dcb11b76b98583092b3cd09f2824067dc96f84f (plain)
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
package terraform

import (
	"fmt"
	"sort"
)

// StateFilter is responsible for filtering and searching a state.
//
// This is a separate struct from State rather than a method on State
// because StateFilter might create sidecar data structures to optimize
// filtering on the state.
//
// If you change the State, the filter created is invalid and either
// Reset should be called or a new one should be allocated. StateFilter
// will not watch State for changes and do this for you. If you filter after
// changing the State without calling Reset, the behavior is not defined.
type StateFilter struct {
	State *State
}

// Filter takes the addresses specified by fs and finds all the matches.
// The values of fs are resource addressing syntax that can be parsed by
// ParseResourceAddress.
func (f *StateFilter) Filter(fs ...string) ([]*StateFilterResult, error) {
	// Parse all the addresses
	as := make([]*ResourceAddress, len(fs))
	for i, v := range fs {
		a, err := ParseResourceAddress(v)
		if err != nil {
			return nil, fmt.Errorf("Error parsing address '%s': %s", v, err)
		}

		as[i] = a
	}

	// If we weren't given any filters, then we list all
	if len(fs) == 0 {
		as = append(as, &ResourceAddress{Index: -1})
	}

	// Filter each of the address. We keep track of this in a map to
	// strip duplicates.
	resultSet := make(map[string]*StateFilterResult)
	for _, a := range as {
		for _, r := range f.filterSingle(a) {
			resultSet[r.String()] = r
		}
	}

	// Make the result list
	results := make([]*StateFilterResult, 0, len(resultSet))
	for _, v := range resultSet {
		results = append(results, v)
	}

	// Sort them and return
	sort.Sort(StateFilterResultSlice(results))
	return results, nil
}

func (f *StateFilter) filterSingle(a *ResourceAddress) []*StateFilterResult {
	// The slice to keep track of results
	var results []*StateFilterResult

	// Go through modules first.
	modules := make([]*ModuleState, 0, len(f.State.Modules))
	for _, m := range f.State.Modules {
		if f.relevant(a, m) {
			modules = append(modules, m)

			// Only add the module to the results if we haven't specified a type.
			// We also ignore the root module.
			if a.Type == "" && len(m.Path) > 1 {
				results = append(results, &StateFilterResult{
					Path:    m.Path[1:],
					Address: (&ResourceAddress{Path: m.Path[1:]}).String(),
					Value:   m,
				})
			}
		}
	}

	// With the modules set, go through all the resources within
	// the modules to find relevant resources.
	for _, m := range modules {
		for n, r := range m.Resources {
			// The name in the state contains valuable information. Parse.
			key, err := ParseResourceStateKey(n)
			if err != nil {
				// If we get an error parsing, then just ignore it
				// out of the state.
				continue
			}

			// Older states and test fixtures often don't contain the
			// type directly on the ResourceState. We add this so StateFilter
			// is a bit more robust.
			if r.Type == "" {
				r.Type = key.Type
			}

			if f.relevant(a, r) {
				if a.Name != "" && a.Name != key.Name {
					// Name doesn't match
					continue
				}

				if a.Index >= 0 && key.Index != a.Index {
					// Index doesn't match
					continue
				}

				if a.Name != "" && a.Name != key.Name {
					continue
				}

				// Build the address for this resource
				addr := &ResourceAddress{
					Path:  m.Path[1:],
					Name:  key.Name,
					Type:  key.Type,
					Index: key.Index,
				}

				// Add the resource level result
				resourceResult := &StateFilterResult{
					Path:    addr.Path,
					Address: addr.String(),
					Value:   r,
				}
				if !a.InstanceTypeSet {
					results = append(results, resourceResult)
				}

				// Add the instances
				if r.Primary != nil {
					addr.InstanceType = TypePrimary
					addr.InstanceTypeSet = false
					results = append(results, &StateFilterResult{
						Path:    addr.Path,
						Address: addr.String(),
						Parent:  resourceResult,
						Value:   r.Primary,
					})
				}

				for _, instance := range r.Deposed {
					if f.relevant(a, instance) {
						addr.InstanceType = TypeDeposed
						addr.InstanceTypeSet = true
						results = append(results, &StateFilterResult{
							Path:    addr.Path,
							Address: addr.String(),
							Parent:  resourceResult,
							Value:   instance,
						})
					}
				}
			}
		}
	}

	return results
}

// relevant checks for relevance of this address against the given value.
func (f *StateFilter) relevant(addr *ResourceAddress, raw interface{}) bool {
	switch v := raw.(type) {
	case *ModuleState:
		path := v.Path[1:]

		if len(addr.Path) > len(path) {
			// Longer path in address means there is no way we match.
			return false
		}

		// Check for a prefix match
		for i, p := range addr.Path {
			if path[i] != p {
				// Any mismatches don't match.
				return false
			}
		}

		return true
	case *ResourceState:
		if addr.Type == "" {
			// If we have no resource type, then we're interested in all!
			return true
		}

		// If the type doesn't match we fail immediately
		if v.Type != addr.Type {
			return false
		}

		return true
	default:
		// If we don't know about it, let's just say no
		return false
	}
}

// StateFilterResult is a single result from a filter operation. Filter
// can match multiple things within a state (module, resource, instance, etc.)
// and this unifies that.
type StateFilterResult struct {
	// Module path of the result
	Path []string

	// Address is the address that can be used to reference this exact result.
	Address string

	// Parent, if non-nil, is a parent of this result. For instances, the
	// parent would be a resource. For resources, the parent would be
	// a module. For modules, this is currently nil.
	Parent *StateFilterResult

	// Value is the actual value. This must be type switched on. It can be
	// any data structures that `State` can hold: `ModuleState`,
	// `ResourceState`, `InstanceState`.
	Value interface{}
}

func (r *StateFilterResult) String() string {
	return fmt.Sprintf("%T: %s", r.Value, r.Address)
}

func (r *StateFilterResult) sortedType() int {
	switch r.Value.(type) {
	case *ModuleState:
		return 0
	case *ResourceState:
		return 1
	case *InstanceState:
		return 2
	default:
		return 50
	}
}

// StateFilterResultSlice is a slice of results that implements
// sort.Interface. The sorting goal is what is most appealing to
// human output.
type StateFilterResultSlice []*StateFilterResult

func (s StateFilterResultSlice) Len() int      { return len(s) }
func (s StateFilterResultSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s StateFilterResultSlice) Less(i, j int) bool {
	a, b := s[i], s[j]

	// if these address contain an index, we want to sort by index rather than name
	addrA, errA := ParseResourceAddress(a.Address)
	addrB, errB := ParseResourceAddress(b.Address)
	if errA == nil && errB == nil && addrA.Name == addrB.Name && addrA.Index != addrB.Index {
		return addrA.Index < addrB.Index
	}

	// If the addresses are different it is just lexographic sorting
	if a.Address != b.Address {
		return a.Address < b.Address
	}

	// Addresses are the same, which means it matters on the type
	return a.sortedType() < b.sortedType()
}