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