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  }