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