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  }