mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-02 12:17:39 -06:00
8be864c1c7
If set elements are computed, we can't be certain that they are actually equal. Catch identical computed set hashes when they are added to the set, and alter the set key slightly to keep the set counts correct. In previous versions the interpolation string would be included in the set, and different string values would cause the set to hash differently, so this is change is only activated for the new protocol.
251 lines
5.2 KiB
Go
251 lines
5.2 KiB
Go
package schema
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"sync"
|
|
|
|
"github.com/hashicorp/terraform/helper/hashcode"
|
|
)
|
|
|
|
// HashString hashes strings. If you want a Set of strings, this is the
|
|
// SchemaSetFunc you want.
|
|
func HashString(v interface{}) int {
|
|
return hashcode.String(v.(string))
|
|
}
|
|
|
|
// HashInt hashes integers. If you want a Set of integers, this is the
|
|
// SchemaSetFunc you want.
|
|
func HashInt(v interface{}) int {
|
|
return hashcode.String(strconv.Itoa(v.(int)))
|
|
}
|
|
|
|
// HashResource hashes complex structures that are described using
|
|
// a *Resource. This is the default set implementation used when a set's
|
|
// element type is a full resource.
|
|
func HashResource(resource *Resource) SchemaSetFunc {
|
|
return func(v interface{}) int {
|
|
var buf bytes.Buffer
|
|
SerializeResourceForHash(&buf, v, resource)
|
|
return hashcode.String(buf.String())
|
|
}
|
|
}
|
|
|
|
// HashSchema hashes values that are described using a *Schema. This is the
|
|
// default set implementation used when a set's element type is a single
|
|
// schema.
|
|
func HashSchema(schema *Schema) SchemaSetFunc {
|
|
return func(v interface{}) int {
|
|
var buf bytes.Buffer
|
|
SerializeValueForHash(&buf, v, schema)
|
|
return hashcode.String(buf.String())
|
|
}
|
|
}
|
|
|
|
// Set is a set data structure that is returned for elements of type
|
|
// TypeSet.
|
|
type Set struct {
|
|
F SchemaSetFunc
|
|
|
|
m map[string]interface{}
|
|
once sync.Once
|
|
}
|
|
|
|
// NewSet is a convenience method for creating a new set with the given
|
|
// items.
|
|
func NewSet(f SchemaSetFunc, items []interface{}) *Set {
|
|
s := &Set{F: f}
|
|
for _, i := range items {
|
|
s.Add(i)
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// CopySet returns a copy of another set.
|
|
func CopySet(otherSet *Set) *Set {
|
|
return NewSet(otherSet.F, otherSet.List())
|
|
}
|
|
|
|
// Add adds an item to the set if it isn't already in the set.
|
|
func (s *Set) Add(item interface{}) {
|
|
s.add(item, false)
|
|
}
|
|
|
|
// Remove removes an item if it's already in the set. Idempotent.
|
|
func (s *Set) Remove(item interface{}) {
|
|
s.remove(item)
|
|
}
|
|
|
|
// Contains checks if the set has the given item.
|
|
func (s *Set) Contains(item interface{}) bool {
|
|
_, ok := s.m[s.hash(item)]
|
|
return ok
|
|
}
|
|
|
|
// Len returns the amount of items in the set.
|
|
func (s *Set) Len() int {
|
|
return len(s.m)
|
|
}
|
|
|
|
// List returns the elements of this set in slice format.
|
|
//
|
|
// The order of the returned elements is deterministic. Given the same
|
|
// set, the order of this will always be the same.
|
|
func (s *Set) List() []interface{} {
|
|
result := make([]interface{}, len(s.m))
|
|
for i, k := range s.listCode() {
|
|
result[i] = s.m[k]
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Difference performs a set difference of the two sets, returning
|
|
// a new third set that has only the elements unique to this set.
|
|
func (s *Set) Difference(other *Set) *Set {
|
|
result := &Set{F: s.F}
|
|
result.once.Do(result.init)
|
|
|
|
for k, v := range s.m {
|
|
if _, ok := other.m[k]; !ok {
|
|
result.m[k] = v
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Intersection performs the set intersection of the two sets
|
|
// and returns a new third set.
|
|
func (s *Set) Intersection(other *Set) *Set {
|
|
result := &Set{F: s.F}
|
|
result.once.Do(result.init)
|
|
|
|
for k, v := range s.m {
|
|
if _, ok := other.m[k]; ok {
|
|
result.m[k] = v
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// Union performs the set union of the two sets and returns a new third
|
|
// set.
|
|
func (s *Set) Union(other *Set) *Set {
|
|
result := &Set{F: s.F}
|
|
result.once.Do(result.init)
|
|
|
|
for k, v := range s.m {
|
|
result.m[k] = v
|
|
}
|
|
for k, v := range other.m {
|
|
result.m[k] = v
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (s *Set) Equal(raw interface{}) bool {
|
|
other, ok := raw.(*Set)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
return reflect.DeepEqual(s.m, other.m)
|
|
}
|
|
|
|
// HashEqual simply checks to the keys the top-level map to the keys in the
|
|
// other set's top-level map to see if they are equal. This obviously assumes
|
|
// you have a properly working hash function - use HashResource if in doubt.
|
|
func (s *Set) HashEqual(raw interface{}) bool {
|
|
other, ok := raw.(*Set)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
ks1 := make([]string, 0)
|
|
ks2 := make([]string, 0)
|
|
|
|
for k := range s.m {
|
|
ks1 = append(ks1, k)
|
|
}
|
|
for k := range other.m {
|
|
ks2 = append(ks2, k)
|
|
}
|
|
|
|
sort.Strings(ks1)
|
|
sort.Strings(ks2)
|
|
|
|
return reflect.DeepEqual(ks1, ks2)
|
|
}
|
|
|
|
func (s *Set) GoString() string {
|
|
return fmt.Sprintf("*Set(%#v)", s.m)
|
|
}
|
|
|
|
func (s *Set) init() {
|
|
s.m = make(map[string]interface{})
|
|
}
|
|
|
|
func (s *Set) add(item interface{}, computed bool) string {
|
|
s.once.Do(s.init)
|
|
|
|
code := s.hash(item)
|
|
if computed {
|
|
code = "~" + code
|
|
|
|
if isProto5() {
|
|
tmpCode := code
|
|
count := 0
|
|
for _, exists := s.m[tmpCode]; exists; _, exists = s.m[tmpCode] {
|
|
count++
|
|
tmpCode = fmt.Sprintf("%s%d", code, count)
|
|
}
|
|
code = tmpCode
|
|
}
|
|
}
|
|
|
|
if _, ok := s.m[code]; !ok {
|
|
s.m[code] = item
|
|
}
|
|
|
|
return code
|
|
}
|
|
|
|
func (s *Set) hash(item interface{}) string {
|
|
code := s.F(item)
|
|
// Always return a nonnegative hashcode.
|
|
if code < 0 {
|
|
code = -code
|
|
}
|
|
return strconv.Itoa(code)
|
|
}
|
|
|
|
func (s *Set) remove(item interface{}) string {
|
|
s.once.Do(s.init)
|
|
|
|
code := s.hash(item)
|
|
delete(s.m, code)
|
|
|
|
return code
|
|
}
|
|
|
|
func (s *Set) index(item interface{}) int {
|
|
return sort.SearchStrings(s.listCode(), s.hash(item))
|
|
}
|
|
|
|
func (s *Set) listCode() []string {
|
|
// Sort the hash codes so the order of the list is deterministic
|
|
keys := make([]string, 0, len(s.m))
|
|
for k := range s.m {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Sort(sort.StringSlice(keys))
|
|
return keys
|
|
}
|