terraform-provider-gitea/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/terraform/state_filter.go
dependabot[bot] 910ccdb092
Bump github.com/hashicorp/terraform-plugin-sdk/v2 from 2.26.1 to 2.27.0
Bumps [github.com/hashicorp/terraform-plugin-sdk/v2](https://github.com/hashicorp/terraform-plugin-sdk) from 2.26.1 to 2.27.0.
- [Release notes](https://github.com/hashicorp/terraform-plugin-sdk/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-sdk/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-sdk/compare/v2.26.1...v2.27.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-sdk/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 20:21:30 +00:00

274 lines
7.1 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package terraform
import (
"fmt"
"sort"
)
// stateFilter is responsible for filtering and searching a state.
//
// This is a separate struct from State rather than a method on State
// because StateFilter 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. StateFilter
// 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 stateFilter 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 *stateFilter) filter(fs ...string) ([]*stateFilterResult, error) {
// Parse all the addresses
var as []*resourceAddress
if len(fs) == 0 {
// If we weren't given any filters, then we list all
as = []*resourceAddress{{Index: -1}}
} else {
as = make([]*resourceAddress, len(fs))
}
for i, v := range fs {
a, err := parseResourceAddress(v)
if err != nil {
return nil, fmt.Errorf("Error parsing address '%s': %s", v, err)
}
as[i] = a
}
// Filter each of the address. We keep track of this in a map to
// strip duplicates.
resultSet := make(map[string]*stateFilterResult)
for _, a := range as {
for _, r := range f.filterSingle(a) {
resultSet[r.String()] = r
}
}
// Make the result list
results := make([]*stateFilterResult, 0, len(resultSet))
for _, v := range resultSet {
results = append(results, v)
}
// Sort them and return
sort.Sort(stateFilterResultSlice(results))
return results, nil
}
func (f *stateFilter) filterSingle(a *resourceAddress) []*stateFilterResult {
// The slice to keep track of results
var results []*stateFilterResult
// Go through modules first.
modules := make([]*ModuleState, 0, len(f.State.Modules))
for _, m := range f.State.Modules {
if f.relevant(a, m) {
modules = append(modules, m)
// Only add the module to the results if we haven't specified a type.
// We also ignore the root module.
if a.Type == "" && len(m.Path) > 1 {
results = append(results, &stateFilterResult{
Path: m.Path[1:],
Address: (&resourceAddress{Path: m.Path[1:]}).String(),
Value: m,
})
}
}
}
// With the modules set, go through all the resources within
// the modules to find relevant resources.
for _, m := range modules {
for n, r := range m.Resources {
// The name in the state contains valuable information. Parse.
key, err := parseResourceStateKey(n)
if err != nil {
// If we get an error parsing, then just ignore it
// out of the state.
continue
}
// Older states and test fixtures often don't contain the
// type directly on the ResourceState. We add this so StateFilter
// is a bit more robust.
if r.Type == "" {
r.Type = key.Type
}
if f.relevant(a, r) {
if a.Name != "" && a.Name != key.Name {
// Name doesn't match
continue
}
if a.Index >= 0 && key.Index != a.Index {
// Index doesn't match
continue
}
if a.Name != "" && a.Name != key.Name {
continue
}
// Build the address for this resource
addr := &resourceAddress{
Path: m.Path[1:],
Name: key.Name,
Type: key.Type,
Index: key.Index,
}
// Add the resource level result
resourceResult := &stateFilterResult{
Path: addr.Path,
Address: addr.String(),
Value: r,
}
if !a.InstanceTypeSet {
results = append(results, resourceResult)
}
// Add the instances
if r.Primary != nil {
addr.InstanceType = typePrimary
addr.InstanceTypeSet = false
results = append(results, &stateFilterResult{
Path: addr.Path,
Address: addr.String(),
Parent: resourceResult,
Value: r.Primary,
})
}
for _, instance := range r.Deposed {
if f.relevant(a, instance) {
addr.InstanceType = typeDeposed
addr.InstanceTypeSet = true
results = append(results, &stateFilterResult{
Path: addr.Path,
Address: addr.String(),
Parent: resourceResult,
Value: instance,
})
}
}
}
}
}
return results
}
// relevant checks for relevance of this address against the given value.
func (f *stateFilter) relevant(addr *resourceAddress, raw interface{}) bool {
switch v := raw.(type) {
case *ModuleState:
path := v.Path[1:]
if len(addr.Path) > len(path) {
// Longer path in address means there is no way we match.
return false
}
// Check for a prefix match
for i, p := range addr.Path {
if path[i] != p {
// Any mismatches don't match.
return false
}
}
return true
case *ResourceState:
if addr.Type == "" {
// If we have no resource type, then we're interested in all!
return true
}
// If the type doesn't match we fail immediately
if v.Type != addr.Type {
return false
}
return true
default:
// If we don't know about it, let's just say no
return false
}
}
// stateFilterResult is a single result from a filter operation. Filter
// can match multiple things within a state (module, resource, instance, etc.)
// and this unifies that.
type stateFilterResult struct {
// Module path of the result
Path []string
// Address is the address that can be used to reference this exact result.
Address string
// Parent, if non-nil, is a parent of this result. For instances, the
// parent would be a resource. For resources, the parent would be
// a module. For modules, this is currently nil.
Parent *stateFilterResult
// Value is the actual value. This must be type switched on. It can be
// any data structures that `State` can hold: `ModuleState`,
// `ResourceState`, `InstanceState`.
Value interface{}
}
func (r *stateFilterResult) String() string {
return fmt.Sprintf("%T: %s", r.Value, r.Address)
}
func (r *stateFilterResult) sortedType() int {
switch r.Value.(type) {
case *ModuleState:
return 0
case *ResourceState:
return 1
case *InstanceState:
return 2
default:
return 50
}
}
// stateFilterResultSlice is a slice of results that implements
// sort.Interface. The sorting goal is what is most appealing to
// human output.
type stateFilterResultSlice []*stateFilterResult
func (s stateFilterResultSlice) Len() int { return len(s) }
func (s stateFilterResultSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s stateFilterResultSlice) Less(i, j int) bool {
a, b := s[i], s[j]
// if these address contain an index, we want to sort by index rather than name
addrA, errA := parseResourceAddress(a.Address)
addrB, errB := parseResourceAddress(b.Address)
if errA == nil && errB == nil && addrA.Name == addrB.Name && addrA.Index != addrB.Index {
return addrA.Index < addrB.Index
}
// If the addresses are different it is just lexographic sorting
if a.Address != b.Address {
return a.Address < b.Address
}
// Addresses are the same, which means it matters on the type
return a.sortedType() < b.sortedType()
}