opentofu/states/state_filter.go
2018-10-27 15:15:25 +02:00

181 lines
4.9 KiB
Go

package states
import (
"fmt"
"sort"
"github.com/hashicorp/terraform/addrs"
)
// Filter is responsible for filtering and searching a state.
//
// This is a separate struct from State rather than a method on State
// because Filter 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. Filter
// 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 Filter 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 *Filter) Filter(fs ...string) ([]*FilterResult, error) {
// Parse all the addresses
as := make([]addrs.Targetable, len(fs))
for i, v := range fs {
if addr, diags := addrs.ParseModuleInstanceStr(v); !diags.HasErrors() {
as[i] = addr
continue
}
if addr, diags := addrs.ParseAbsResourceStr(v); !diags.HasErrors() {
as[i] = addr
continue
}
if addr, diags := addrs.ParseAbsResourceInstanceStr(v); !diags.HasErrors() {
as[i] = addr
continue
}
return nil, fmt.Errorf("Error parsing address: %s", v)
}
// If we weren't given any filters, then we list all
if len(fs) == 0 {
as = append(as, addrs.Targetable(nil))
}
// Filter each of the address. We keep track of this in a map to
// strip duplicates.
resultSet := make(map[string]*FilterResult)
for _, addr := range as {
for _, v := range f.filterSingle(addr) {
resultSet[v.String()] = v
}
}
// Make the result list
results := make([]*FilterResult, 0, len(resultSet))
for _, v := range resultSet {
results = append(results, v)
}
// Sort the results
sort.Slice(results, func(i, j int) bool {
a, b := results[i], results[j]
// If the addresses are different it is just lexographic sorting
if a.Address.String() != b.Address.String() {
return a.Address.String() < b.Address.String()
}
// Addresses are the same, which means it matters on the type
return a.SortedType() < b.SortedType()
})
return results, nil
}
func (f *Filter) filterSingle(addr addrs.Targetable) []*FilterResult {
// The slice to keep track of results
var results []*FilterResult
// Check if we received a module instance address that
// should be used as module filter, and if not set the
// filter to the root module instance.
filter, ok := addr.(addrs.ModuleInstance)
if !ok {
filter = addrs.RootModuleInstance
}
// Go through modules first.
modules := make([]*Module, 0, len(f.State.Modules))
for _, m := range f.State.Modules {
if filter.IsRoot() || filter.Equal(m.Addr) || filter.IsAncestor(m.Addr) {
modules = append(modules, m)
// Only add the module to the results if we searched
// for a non-root module and found a (partial) match.
if (addr == nil && !m.Addr.IsRoot()) ||
(!filter.IsRoot() && (filter.Equal(m.Addr) || filter.IsAncestor(m.Addr))) {
results = append(results, &FilterResult{
Address: m.Addr,
Value: m,
})
}
}
}
// With the modules set, go through all the resources within
// the modules to find relevant resources.
for _, m := range modules {
for _, rs := range m.Resources {
if f.relevant(addr, rs.Addr.Absolute(m.Addr), addrs.NoKey) {
results = append(results, &FilterResult{
Address: rs.Addr.Absolute(m.Addr),
Value: rs,
})
}
for key, is := range rs.Instances {
if f.relevant(addr, rs.Addr.Absolute(m.Addr), key) {
results = append(results, &FilterResult{
Address: rs.Addr.Absolute(m.Addr).Instance(key),
Value: is,
})
}
}
}
}
return results
}
func (f *Filter) relevant(filter addrs.Targetable, rs addrs.AbsResource, key addrs.InstanceKey) bool {
switch filter := filter.(type) {
case addrs.AbsResource:
if filter.Module != nil {
return filter.Equal(rs)
}
return filter.Resource.Equal(rs.Resource)
case addrs.AbsResourceInstance:
if filter.Module != nil {
return filter.Equal(rs.Instance(key))
}
return filter.Resource.Equal(rs.Resource.Instance(key))
default:
return true
}
}
// FilterResult is a single result from a filter operation. Filter can
// match multiple things within a state (curently modules and resources).
type FilterResult struct {
// Address is the address that can be used to reference this exact result.
Address addrs.Targetable
// Value is the actual value. This must be type switched on. It can be
// any either a `Module` or `ResourceInstance`.
Value interface{}
}
func (r *FilterResult) String() string {
return fmt.Sprintf("%T: %s", r.Value, r.Address)
}
func (r *FilterResult) SortedType() int {
switch r.Value.(type) {
case *Module:
return 0
case *Resource:
return 1
case *ResourceInstance:
return 2
default:
return 50
}
}