update vendor

Signed-off-by: Roland.Ma <rolandma@yunify.com>
This commit is contained in:
Roland.Ma
2021-08-11 07:10:14 +00:00
parent a18f72b565
commit ea8f47c73a
2901 changed files with 269317 additions and 43103 deletions

View File

@@ -21,7 +21,7 @@ import (
"sort"
"strings"
"sigs.k8s.io/structured-merge-diff/v3/value"
"sigs.k8s.io/structured-merge-diff/v4/value"
)
// PathElement describes how to select a child field given a containing object.

View File

@@ -17,7 +17,7 @@ limitations under the License.
package fieldpath
import (
"sigs.k8s.io/structured-merge-diff/v3/value"
"sigs.k8s.io/structured-merge-diff/v4/value"
)
// SetFromValue creates a set containing every leaf field mentioned in v.

View File

@@ -20,7 +20,7 @@ import (
"fmt"
"strings"
"sigs.k8s.io/structured-merge-diff/v3/value"
"sigs.k8s.io/structured-merge-diff/v4/value"
)
// Path describes how to select a potentially deeply-nested child field given a

View File

@@ -19,7 +19,7 @@ package fieldpath
import (
"sort"
"sigs.k8s.io/structured-merge-diff/v3/value"
"sigs.k8s.io/structured-merge-diff/v4/value"
)
// PathElementValueMap is a map from PathElement to value.Value.

View File

@@ -24,7 +24,7 @@ import (
"strings"
jsoniter "github.com/json-iterator/go"
"sigs.k8s.io/structured-merge-diff/v3/value"
"sigs.k8s.io/structured-merge-diff/v4/value"
)
var ErrUnknownPathElementType = errors.New("unknown path element type")

View File

@@ -19,6 +19,8 @@ package fieldpath
import (
"sort"
"strings"
"sigs.k8s.io/structured-merge-diff/v4/schema"
)
// Set identifies a set of fields.
@@ -94,6 +96,46 @@ func (s *Set) Difference(s2 *Set) *Set {
}
}
// RecursiveDifference returns a Set containing elements which:
// * appear in s
// * do not appear in s2
//
// Compared to a regular difference,
// this removes every field **and its children** from s that is contained in s2.
//
// For example, with s containing `a.b.c` and s2 containing `a.b`,
// a RecursiveDifference will result in `a`, as the entire node `a.b` gets removed.
func (s *Set) RecursiveDifference(s2 *Set) *Set {
return &Set{
Members: *s.Members.Difference(&s2.Members),
Children: *s.Children.RecursiveDifference(s2),
}
}
// EnsureNamedFieldsAreMembers returns a Set that contains all the
// fields in s, as well as all the named fields that are typically not
// included. For example, a set made of "a.b.c" will end-up also owning
// "a" if it's a named fields but not "a.b" if it's a map.
func (s *Set) EnsureNamedFieldsAreMembers(sc *schema.Schema, tr schema.TypeRef) *Set {
members := PathElementSet{
members: make(sortedPathElements, 0, s.Members.Size()+len(s.Children.members)),
}
atom, _ := sc.Resolve(tr)
members.members = append(members.members, s.Members.members...)
for _, node := range s.Children.members {
// Only insert named fields.
if node.pathElement.FieldName != nil && atom.Map != nil {
if _, has := atom.Map.FindField(*node.pathElement.FieldName); has {
members.Insert(node.pathElement)
}
}
}
return &Set{
Members: members,
Children: *s.Children.EnsureNamedFieldsAreMembers(sc, tr),
}
}
// Size returns the number of members of the set.
func (s *Set) Size() int {
return s.Members.Size() + s.Children.Size()
@@ -164,6 +206,40 @@ func (s *Set) WithPrefix(pe PathElement) *Set {
return subset
}
// Leaves returns a set containing only the leaf paths
// of a set.
func (s *Set) Leaves() *Set {
leaves := PathElementSet{}
im := 0
ic := 0
// any members that are not also children are leaves
outer:
for im < len(s.Members.members) {
member := s.Members.members[im]
for ic < len(s.Children.members) {
d := member.Compare(s.Children.members[ic].pathElement)
if d == 0 {
ic++
im++
continue outer
} else if d < 0 {
break
} else /* if d > 0 */ {
ic++
}
}
leaves.members = append(leaves.members, member)
im++
}
return &Set{
Members: leaves,
Children: *s.Children.Leaves(),
}
}
// setNode is a pair of PathElement / Set, for the purpose of expressing
// nested set membership.
type setNode struct {
@@ -333,6 +409,73 @@ func (s *SetNodeMap) Difference(s2 *Set) *SetNodeMap {
return out
}
// RecursiveDifference returns a SetNodeMap with members that appear in s but not in s2.
//
// Compared to a regular difference,
// this removes every field **and its children** from s that is contained in s2.
//
// For example, with s containing `a.b.c` and s2 containing `a.b`,
// a RecursiveDifference will result in `a`, as the entire node `a.b` gets removed.
func (s *SetNodeMap) RecursiveDifference(s2 *Set) *SetNodeMap {
out := &SetNodeMap{}
i, j := 0, 0
for i < len(s.members) && j < len(s2.Children.members) {
if s.members[i].pathElement.Less(s2.Children.members[j].pathElement) {
if !s2.Members.Has(s.members[i].pathElement) {
out.members = append(out.members, setNode{pathElement: s.members[i].pathElement, set: s.members[i].set})
}
i++
} else {
if !s2.Children.members[j].pathElement.Less(s.members[i].pathElement) {
if !s2.Members.Has(s.members[i].pathElement) {
diff := s.members[i].set.RecursiveDifference(s2.Children.members[j].set)
if !diff.Empty() {
out.members = append(out.members, setNode{pathElement: s.members[i].pathElement, set: diff})
}
}
i++
}
j++
}
}
if i < len(s.members) {
for _, c := range s.members[i:] {
if !s2.Members.Has(c.pathElement) {
out.members = append(out.members, c)
}
}
}
return out
}
// EnsureNamedFieldsAreMembers returns a set that contains all the named fields along with the leaves.
func (s *SetNodeMap) EnsureNamedFieldsAreMembers(sc *schema.Schema, tr schema.TypeRef) *SetNodeMap {
out := make(sortedSetNode, 0, s.Size())
atom, _ := sc.Resolve(tr)
for _, member := range s.members {
tr := schema.TypeRef{}
if member.pathElement.FieldName != nil && atom.Map != nil {
tr = atom.Map.ElementType
if sf, ok := atom.Map.FindField(*member.pathElement.FieldName); ok {
tr = sf.Type
}
} else if member.pathElement.Key != nil && atom.List != nil {
tr = atom.List.ElementType
}
out = append(out, setNode{
pathElement: member.pathElement,
set: member.set.EnsureNamedFieldsAreMembers(sc, tr),
})
}
return &SetNodeMap{
members: out,
}
}
// Iterate calls f for each PathElement in the set.
func (s *SetNodeMap) Iterate(f func(PathElement)) {
for _, n := range s.members {
@@ -346,3 +489,17 @@ func (s *SetNodeMap) iteratePrefix(prefix Path, f func(Path)) {
n.set.iteratePrefix(append(prefix, pe), f)
}
}
// Leaves returns a SetNodeMap containing
// only setNodes with leaf PathElements.
func (s *SetNodeMap) Leaves() *SetNodeMap {
out := &SetNodeMap{}
out.members = make(sortedSetNode, len(s.members))
for i, n := range s.members {
out.members[i] = setNode{
pathElement: n.pathElement,
set: n.set.Leaves(),
}
}
return out
}

View File

@@ -21,7 +21,7 @@ import (
"sort"
"strings"
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// Conflict is a conflict on a specific field with the current manager of
@@ -95,6 +95,15 @@ func (c Conflicts) Equals(c2 Conflicts) bool {
return true
}
// ToSet aggregates conflicts for all managers into a single Set.
func (c Conflicts) ToSet() *fieldpath.Set {
set := fieldpath.NewSet()
for _, conflict := range []Conflict(c) {
set.Insert(conflict.Path)
}
return set
}
// ConflictsFromManagers creates a list of conflicts given Managers sets.
func ConflictsFromManagers(sets fieldpath.ManagedFields) Conflicts {
conflicts := []Conflict{}

View File

@@ -16,8 +16,8 @@ package merge
import (
"fmt"
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
"sigs.k8s.io/structured-merge-diff/v3/typed"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/typed"
)
// Converter is an interface to the conversion logic. The converter
@@ -30,7 +30,8 @@ type Converter interface {
// Updater is the object used to compute updated FieldSets and also
// merge the object on Apply.
type Updater struct {
Converter Converter
Converter Converter
IgnoredFields map[fieldpath.APIVersion]*fieldpath.Set
enableUnions bool
}
@@ -50,7 +51,7 @@ func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpa
}
versions := map[fieldpath.APIVersion]*typed.Comparison{
version: compare,
version: compare.ExcludeFields(s.IgnoredFields[version]),
}
for manager, managerSet := range managers {
@@ -80,7 +81,7 @@ func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpa
if err != nil {
return nil, nil, fmt.Errorf("failed to compare objects: %v", err)
}
versions[managerSet.APIVersion()] = compare
versions[managerSet.APIVersion()] = compare.ExcludeFields(s.IgnoredFields[managerSet.APIVersion()])
}
conflictSet := managerSet.Set().Intersection(compare.Modified.Union(compare.Added))
@@ -121,13 +122,16 @@ func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpa
// this is a CREATE call).
func (s *Updater) Update(liveObject, newObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string) (*typed.TypedValue, fieldpath.ManagedFields, error) {
var err error
managers, err = s.reconcileManagedFieldsWithSchemaChanges(liveObject, managers)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
if s.enableUnions {
newObject, err = liveObject.NormalizeUnions(newObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
}
managers = shallowCopyManagers(managers)
managers, compare, err := s.update(liveObject, newObject, version, managers, manager, true)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
@@ -135,8 +139,13 @@ func (s *Updater) Update(liveObject, newObject *typed.TypedValue, version fieldp
if _, ok := managers[manager]; !ok {
managers[manager] = fieldpath.NewVersionedSet(fieldpath.NewSet(), version, false)
}
ignored := s.IgnoredFields[version]
if ignored == nil {
ignored = fieldpath.NewSet()
}
managers[manager] = fieldpath.NewVersionedSet(
managers[manager].Set().Union(compare.Modified).Union(compare.Added).Difference(compare.Removed),
managers[manager].Set().Union(compare.Modified).Union(compare.Added).Difference(compare.Removed).RecursiveDifference(ignored),
version,
false,
)
@@ -151,8 +160,11 @@ func (s *Updater) Update(liveObject, newObject *typed.TypedValue, version fieldp
// and return it. If the object hasn't changed, nil is returned (the
// managers can still have changed though).
func (s *Updater) Apply(liveObject, configObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string, force bool) (*typed.TypedValue, fieldpath.ManagedFields, error) {
managers = shallowCopyManagers(managers)
var err error
managers, err = s.reconcileManagedFieldsWithSchemaChanges(liveObject, managers)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
if s.enableUnions {
configObject, err = configObject.NormalizeUnionsApply(configObject)
if err != nil {
@@ -174,6 +186,15 @@ func (s *Updater) Apply(liveObject, configObject *typed.TypedValue, version fiel
if err != nil {
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to get field set: %v", err)
}
ignored := s.IgnoredFields[version]
if ignored != nil {
set = set.RecursiveDifference(ignored)
// TODO: is this correct. If we don't remove from lastSet pruning might remove the fields?
if lastSet != nil {
lastSet.Set().RecursiveDifference(ignored)
}
}
managers[manager] = fieldpath.NewVersionedSet(set, version, true)
newObject, err = s.prune(newObject, managers, manager, lastSet)
if err != nil {
@@ -189,15 +210,7 @@ func (s *Updater) Apply(liveObject, configObject *typed.TypedValue, version fiel
return newObject, managers, nil
}
func shallowCopyManagers(managers fieldpath.ManagedFields) fieldpath.ManagedFields {
newManagers := fieldpath.ManagedFields{}
for manager, set := range managers {
newManagers[manager] = set
}
return newManagers
}
// prune will remove a list or map item, iff:
// prune will remove a field, list or map item, iff:
// * applyingManager applied it last time
// * applyingManager didn't apply it this time
// * no other applier claims to manage it
@@ -213,7 +226,8 @@ func (s *Updater) prune(merged *typed.TypedValue, managers fieldpath.ManagedFiel
return nil, fmt.Errorf("failed to convert merged object to last applied version: %v", err)
}
pruned := convertedMerged.RemoveItems(lastSet.Set())
sc, tr := convertedMerged.Schema(), convertedMerged.TypeRef()
pruned := convertedMerged.RemoveItems(lastSet.Set().EnsureNamedFieldsAreMembers(sc, tr))
pruned, err = s.addBackOwnedItems(convertedMerged, pruned, managers, applyingManager)
if err != nil {
return nil, fmt.Errorf("failed add back owned items: %v", err)
@@ -225,18 +239,16 @@ func (s *Updater) prune(merged *typed.TypedValue, managers fieldpath.ManagedFiel
return s.Converter.Convert(pruned, managers[applyingManager].APIVersion())
}
// addBackOwnedItems adds back any list and map items that were removed by prune,
// but other appliers (or the current applier's new config) claim to own.
// addBackOwnedItems adds back any fields, list and map items that were removed by prune,
// but other appliers or updaters (or the current applier's new config) claim to own.
func (s *Updater) addBackOwnedItems(merged, pruned *typed.TypedValue, managedFields fieldpath.ManagedFields, applyingManager string) (*typed.TypedValue, error) {
var err error
managedAtVersion := map[fieldpath.APIVersion]*fieldpath.Set{}
for _, managerSet := range managedFields {
if managerSet.Applied() {
if _, ok := managedAtVersion[managerSet.APIVersion()]; !ok {
managedAtVersion[managerSet.APIVersion()] = fieldpath.NewSet()
}
managedAtVersion[managerSet.APIVersion()] = managedAtVersion[managerSet.APIVersion()].Union(managerSet.Set())
if _, ok := managedAtVersion[managerSet.APIVersion()]; !ok {
managedAtVersion[managerSet.APIVersion()] = fieldpath.NewSet()
}
managedAtVersion[managerSet.APIVersion()] = managedAtVersion[managerSet.APIVersion()].Union(managerSet.Set())
}
for version, managed := range managedAtVersion {
merged, err = s.Converter.Convert(merged, version)
@@ -261,14 +273,15 @@ func (s *Updater) addBackOwnedItems(merged, pruned *typed.TypedValue, managedFie
if err != nil {
return nil, fmt.Errorf("failed to create field set from pruned object at version %v: %v", version, err)
}
pruned = merged.RemoveItems(mergedSet.Difference(prunedSet.Union(managed)))
sc, tr := merged.Schema(), merged.TypeRef()
pruned = merged.RemoveItems(mergedSet.EnsureNamedFieldsAreMembers(sc, tr).Difference(prunedSet.EnsureNamedFieldsAreMembers(sc, tr).Union(managed.EnsureNamedFieldsAreMembers(sc, tr))))
}
return pruned, nil
}
// addBackDanglingItems makes sure that the only items removed by prune are items that were
// previously owned by the currently applying manager. This will add back unowned items and items
// which are owned by Updaters that shouldn't be removed.
// addBackDanglingItems makes sure that the fields list and map items removed by prune were
// previously owned by the currently applying manager. This will add back fields list and map items
// that are unowned or that are owned by Updaters and shouldn't be removed.
func (s *Updater) addBackDanglingItems(merged, pruned *typed.TypedValue, lastSet fieldpath.VersionedSet) (*typed.TypedValue, error) {
convertedPruned, err := s.Converter.Convert(pruned, lastSet.APIVersion())
if err != nil {
@@ -285,5 +298,38 @@ func (s *Updater) addBackDanglingItems(merged, pruned *typed.TypedValue, lastSet
if err != nil {
return nil, fmt.Errorf("failed to create field set from merged object in last applied version: %v", err)
}
return merged.RemoveItems(mergedSet.Difference(prunedSet).Intersection(lastSet.Set())), nil
sc, tr := merged.Schema(), merged.TypeRef()
prunedSet = prunedSet.EnsureNamedFieldsAreMembers(sc, tr)
mergedSet = mergedSet.EnsureNamedFieldsAreMembers(sc, tr)
last := lastSet.Set().EnsureNamedFieldsAreMembers(sc, tr)
return merged.RemoveItems(mergedSet.Difference(prunedSet).Intersection(last)), nil
}
// reconcileManagedFieldsWithSchemaChanges reconciles the managed fields with any changes to the
// object's schema since the managed fields were written.
//
// Supports:
// - changing types from atomic to granular
// - changing types from granular to atomic
func (s *Updater) reconcileManagedFieldsWithSchemaChanges(liveObject *typed.TypedValue, managers fieldpath.ManagedFields) (fieldpath.ManagedFields, error) {
result := fieldpath.ManagedFields{}
for manager, versionedSet := range managers {
tv, err := s.Converter.Convert(liveObject, versionedSet.APIVersion())
if s.Converter.IsMissingVersionError(err) { // okay to skip, obsolete versions will be deleted automatically anyway
continue
}
if err != nil {
return nil, err
}
reconciled, err := typed.ReconcileFieldSetWithSchema(versionedSet.Set(), tv)
if err != nil {
return nil, err
}
if reconciled != nil {
result[manager] = fieldpath.NewVersionedSet(reconciled, versionedSet.APIVersion(), versionedSet.Applied())
} else {
result[manager] = versionedSet
}
}
return result, nil
}

View File

@@ -196,6 +196,8 @@ type StructField struct {
Name string `yaml:"name,omitempty"`
// Type is the field type.
Type TypeRef `yaml:"type,omitempty"`
// Default value for the field, nil if not present.
Default interface{} `yaml:"default,omitempty"`
}
// List represents a type which contains a zero or more elements, all of the

View File

@@ -16,6 +16,8 @@ limitations under the License.
package schema
import "reflect"
// Equals returns true iff the two Schemas are equal.
func (a *Schema) Equals(b *Schema) bool {
if a == nil || b == nil {
@@ -168,6 +170,9 @@ func (a *StructField) Equals(b *StructField) bool {
if a.Name != b.Name {
return false
}
if !reflect.DeepEqual(a.Default, b.Default) {
return false
}
return a.Type.Equals(&b.Type)
}

View File

@@ -125,6 +125,9 @@ var SchemaSchemaYAML = `types:
- name: type
type:
namedType: typeRef
- name: default
type:
namedType: __untyped_atomic_
- name: list
map:
fields:
@@ -145,4 +148,14 @@ var SchemaSchemaYAML = `types:
- name: elementRelationship
type:
scalar: string
- name: __untyped_atomic_
scalar: untyped
list:
elementType:
namedType: __untyped_atomic_
elementRelationship: atomic
map:
elementType:
namedType: __untyped_atomic_
elementRelationship: atomic
`

View File

@@ -21,9 +21,9 @@ import (
"fmt"
"strings"
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
"sigs.k8s.io/structured-merge-diff/v3/schema"
"sigs.k8s.io/structured-merge-diff/v3/value"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/schema"
"sigs.k8s.io/structured-merge-diff/v4/value"
)
// ValidationError reports an error about a particular field
@@ -180,11 +180,23 @@ func mapValue(a value.Allocator, val value.Value) (value.Map, error) {
return val.AsMapUsing(a), nil
}
func keyedAssociativeListItemToPathElement(a value.Allocator, list *schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
func getAssociativeKeyDefault(s *schema.Schema, list *schema.List, fieldName string) (interface{}, error) {
atom, ok := s.Resolve(list.ElementType)
if !ok {
return nil, errors.New("invalid elementType for list")
}
if atom.Map == nil {
return nil, errors.New("associative list may not have non-map types")
}
// If the field is not found, we can assume there is no default.
field, _ := atom.Map.FindField(fieldName)
return field.Default, nil
}
func keyedAssociativeListItemToPathElement(a value.Allocator, s *schema.Schema, list *schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
pe := fieldpath.PathElement{}
if child.IsNull() {
// For now, the keys are required which means that null entries
// are illegal.
// null entries are illegal.
return pe, errors.New("associative list with keys may not have a null element")
}
if !child.IsMap() {
@@ -196,8 +208,12 @@ func keyedAssociativeListItemToPathElement(a value.Allocator, list *schema.List,
for _, fieldName := range list.Keys {
if val, ok := m.Get(fieldName); ok {
keyMap = append(keyMap, value.Field{Name: fieldName, Value: val})
} else if def, err := getAssociativeKeyDefault(s, list, fieldName); err != nil {
return pe, fmt.Errorf("couldn't find default value for %v: %v", fieldName, err)
} else if def != nil {
keyMap = append(keyMap, value.Field{Name: fieldName, Value: value.NewValueInterface(def)})
} else {
return pe, fmt.Errorf("associative list with keys has an element that omits key field %q", fieldName)
return pe, fmt.Errorf("associative list with keys has an element that omits key field %q (and doesn't have default value)", fieldName)
}
}
keyMap.Sort()
@@ -225,10 +241,10 @@ func setItemToPathElement(list *schema.List, index int, child value.Value) (fiel
}
}
func listItemToPathElement(a value.Allocator, list *schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
func listItemToPathElement(a value.Allocator, s *schema.Schema, list *schema.List, index int, child value.Value) (fieldpath.PathElement, error) {
if list.ElementRelationship == schema.Associative {
if len(list.Keys) > 0 {
return keyedAssociativeListItemToPathElement(a, list, index, child)
return keyedAssociativeListItemToPathElement(a, s, list, index, child)
}
// If there's no keys, then we must be a set of primitives.

View File

@@ -19,9 +19,9 @@ package typed
import (
"math"
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
"sigs.k8s.io/structured-merge-diff/v3/schema"
"sigs.k8s.io/structured-merge-diff/v3/value"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/schema"
"sigs.k8s.io/structured-merge-diff/v4/value"
)
type mergingWalker struct {
@@ -183,7 +183,7 @@ func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs value.List) (err
if rhs != nil {
for i := 0; i < rhs.Length(); i++ {
child := rhs.At(i)
pe, err := listItemToPathElement(w.allocator, t, i, child)
pe, err := listItemToPathElement(w.allocator, w.schema, t, i, child)
if err != nil {
errs = append(errs, errorf("rhs: element %v: %v", i, err.Error())...)
// If we can't construct the path element, we can't
@@ -204,7 +204,7 @@ func (w *mergingWalker) visitListItems(t *schema.List, lhs, rhs value.List) (err
if lhs != nil {
for i := 0; i < lhs.Length(); i++ {
child := lhs.At(i)
pe, err := listItemToPathElement(w.allocator, t, i, child)
pe, err := listItemToPathElement(w.allocator, w.schema, t, i, child)
if err != nil {
errs = append(errs, errorf("lhs: element %v: %v", i, err.Error())...)
// If we can't construct the path element, we can't

View File

@@ -20,8 +20,8 @@ import (
"fmt"
yaml "gopkg.in/yaml.v2"
"sigs.k8s.io/structured-merge-diff/v3/schema"
"sigs.k8s.io/structured-merge-diff/v3/value"
"sigs.k8s.io/structured-merge-diff/v4/schema"
"sigs.k8s.io/structured-merge-diff/v4/value"
)
// YAMLObject is an object encoded in YAML.

View File

@@ -0,0 +1,295 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package typed
import (
"fmt"
"sync"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/schema"
)
var fmPool = sync.Pool{
New: func() interface{} { return &reconcileWithSchemaWalker{} },
}
func (v *reconcileWithSchemaWalker) finished() {
v.fieldSet = nil
v.schema = nil
v.value = nil
v.typeRef = schema.TypeRef{}
v.path = nil
v.toRemove = nil
v.toAdd = nil
fmPool.Put(v)
}
type reconcileWithSchemaWalker struct {
value *TypedValue // root of the live object
schema *schema.Schema // root of the live schema
// state of node being visited by walker
fieldSet *fieldpath.Set
typeRef schema.TypeRef
path fieldpath.Path
isAtomic bool
// the accumulated diff to perform to apply reconciliation
toRemove *fieldpath.Set // paths to remove recursively
toAdd *fieldpath.Set // paths to add after any removals
// Allocate only as many walkers as needed for the depth by storing them here.
spareWalkers *[]*reconcileWithSchemaWalker
}
func (v *reconcileWithSchemaWalker) prepareDescent(pe fieldpath.PathElement, tr schema.TypeRef) *reconcileWithSchemaWalker {
if v.spareWalkers == nil {
// first descent.
v.spareWalkers = &[]*reconcileWithSchemaWalker{}
}
var v2 *reconcileWithSchemaWalker
if n := len(*v.spareWalkers); n > 0 {
v2, *v.spareWalkers = (*v.spareWalkers)[n-1], (*v.spareWalkers)[:n-1]
} else {
v2 = &reconcileWithSchemaWalker{}
}
*v2 = *v
v2.typeRef = tr
v2.path = append(v.path, pe)
v2.value = v.value
return v2
}
func (v *reconcileWithSchemaWalker) finishDescent(v2 *reconcileWithSchemaWalker) {
v2.fieldSet = nil
v2.schema = nil
v2.value = nil
v2.typeRef = schema.TypeRef{}
if cap(v2.path) < 20 { // recycle slices that do not have unexpectedly high capacity
v2.path = v2.path[:0]
} else {
v2.path = nil
}
// merge any accumulated changes into parent walker
if v2.toRemove != nil {
if v.toRemove == nil {
v.toRemove = v2.toRemove
} else {
v.toRemove = v.toRemove.Union(v2.toRemove)
}
}
if v2.toAdd != nil {
if v.toAdd == nil {
v.toAdd = v2.toAdd
} else {
v.toAdd = v.toAdd.Union(v2.toAdd)
}
}
v2.toRemove = nil
v2.toAdd = nil
// if the descent caused a realloc, ensure that we reuse the buffer
// for the next sibling.
*v.spareWalkers = append(*v.spareWalkers, v2)
}
// ReconcileFieldSetWithSchema reconciles the a field set with any changes to the
//// object's schema since the field set was written. Returns the reconciled field set, or nil of
// no changes were made to the field set.
//
// Supports:
// - changing types from atomic to granular
// - changing types from granular to atomic
func ReconcileFieldSetWithSchema(fieldset *fieldpath.Set, tv *TypedValue) (*fieldpath.Set, error) {
v := fmPool.Get().(*reconcileWithSchemaWalker)
v.fieldSet = fieldset
v.value = tv
v.schema = tv.schema
v.typeRef = tv.typeRef
// We don't reconcile deduced types, which are primarily for use by unstructured CRDs. Deduced
// types do not support atomic or granular tags. Nor does the dynamic schema deduction
// interact well with the reconcile logic.
if v.schema == DeducedParseableType.Schema {
return nil, nil
}
defer v.finished()
errs := v.reconcile()
if len(errs) > 0 {
return nil, fmt.Errorf("errors reconciling field set with schema: %s", errs.Error())
}
// If there are any accumulated changes, apply them
if v.toAdd != nil || v.toRemove != nil {
out := v.fieldSet
if v.toRemove != nil {
out = out.RecursiveDifference(v.toRemove)
}
if v.toAdd != nil {
out = out.Union(v.toAdd)
}
return out, nil
}
return nil, nil
}
func (v *reconcileWithSchemaWalker) reconcile() (errs ValidationErrors) {
a, ok := v.schema.Resolve(v.typeRef)
if !ok {
errs = append(errs, errorf("could not resolve %v", v.typeRef)...)
return
}
return handleAtom(a, v.typeRef, v)
}
func (v *reconcileWithSchemaWalker) doScalar(_ *schema.Scalar) (errs ValidationErrors) {
return errs
}
func (v *reconcileWithSchemaWalker) visitListItems(t *schema.List, element *fieldpath.Set) (errs ValidationErrors) {
handleElement := func(pe fieldpath.PathElement, isMember bool) {
var hasChildren bool
v2 := v.prepareDescent(pe, t.ElementType)
v2.fieldSet, hasChildren = element.Children.Get(pe)
v2.isAtomic = isMember && !hasChildren
errs = append(errs, v2.reconcile()...)
v.finishDescent(v2)
}
element.Children.Iterate(func(pe fieldpath.PathElement) {
if element.Members.Has(pe) {
return
}
handleElement(pe, false)
})
element.Members.Iterate(func(pe fieldpath.PathElement) {
handleElement(pe, true)
})
return errs
}
func (v *reconcileWithSchemaWalker) doList(t *schema.List) (errs ValidationErrors) {
// reconcile lists changed from granular to atomic
if !v.isAtomic && t.ElementRelationship == schema.Atomic {
v.toRemove = fieldpath.NewSet(v.path) // remove all root and all children fields
v.toAdd = fieldpath.NewSet(v.path) // add the root of the atomic
return errs
}
// reconcile lists changed from atomic to granular
if v.isAtomic && t.ElementRelationship == schema.Associative {
v.toAdd, errs = buildGranularFieldSet(v.path, v.value)
if errs != nil {
return errs
}
}
if v.fieldSet != nil {
errs = v.visitListItems(t, v.fieldSet)
}
return errs
}
func (v *reconcileWithSchemaWalker) visitMapItems(t *schema.Map, element *fieldpath.Set) (errs ValidationErrors) {
handleElement := func(pe fieldpath.PathElement, isMember bool) {
var hasChildren bool
if tr, ok := typeRefAtPath(t, pe); ok { // ignore fields not in the schema
v2 := v.prepareDescent(pe, tr)
v2.fieldSet, hasChildren = element.Children.Get(pe)
v2.isAtomic = isMember && !hasChildren
errs = append(errs, v2.reconcile()...)
v.finishDescent(v2)
}
}
element.Children.Iterate(func(pe fieldpath.PathElement) {
if element.Members.Has(pe) {
return
}
handleElement(pe, false)
})
element.Members.Iterate(func(pe fieldpath.PathElement) {
handleElement(pe, true)
})
return errs
}
func (v *reconcileWithSchemaWalker) doMap(t *schema.Map) (errs ValidationErrors) {
// reconcile maps and structs changed from granular to atomic
if !v.isAtomic && t.ElementRelationship == schema.Atomic {
if v.fieldSet != nil && v.fieldSet.Size() > 0 {
v.toRemove = fieldpath.NewSet(v.path) // remove all root and all children fields
v.toAdd = fieldpath.NewSet(v.path) // add the root of the atomic
}
return errs
}
// reconcile maps changed from atomic to granular
if v.isAtomic && (t.ElementRelationship == schema.Separable || t.ElementRelationship == "") {
v.toAdd, errs = buildGranularFieldSet(v.path, v.value)
if errs != nil {
return errs
}
}
if v.fieldSet != nil {
errs = v.visitMapItems(t, v.fieldSet)
}
return errs
}
func buildGranularFieldSet(path fieldpath.Path, value *TypedValue) (*fieldpath.Set, ValidationErrors) {
valueFieldSet, err := value.ToFieldSet()
if err != nil {
return nil, errorf("toFieldSet: %v", err)
}
if valueFieldSetAtPath, ok := fieldSetAtPath(valueFieldSet, path); ok {
result := fieldpath.NewSet(path)
resultAtPath := descendToPath(result, path)
*resultAtPath = *valueFieldSetAtPath
return result, nil
}
return nil, nil
}
func fieldSetAtPath(node *fieldpath.Set, path fieldpath.Path) (*fieldpath.Set, bool) {
ok := true
for _, pe := range path {
if node, ok = node.Children.Get(pe); !ok {
break
}
}
return node, ok
}
func descendToPath(node *fieldpath.Set, path fieldpath.Path) *fieldpath.Set {
for _, pe := range path {
node = node.Children.Descend(pe)
}
return node
}
func typeRefAtPath(t *schema.Map, pe fieldpath.PathElement) (schema.TypeRef, bool) {
tr := t.ElementType
if pe.FieldName != nil {
if sf, ok := t.FindField(*pe.FieldName); ok {
tr = sf.Type
}
}
return tr, tr != schema.TypeRef{}
}

View File

@@ -14,25 +14,32 @@ limitations under the License.
package typed
import (
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
"sigs.k8s.io/structured-merge-diff/v3/schema"
"sigs.k8s.io/structured-merge-diff/v3/value"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/schema"
"sigs.k8s.io/structured-merge-diff/v4/value"
)
type removingWalker struct {
value value.Value
out interface{}
schema *schema.Schema
toRemove *fieldpath.Set
allocator value.Allocator
value value.Value
out interface{}
schema *schema.Schema
toRemove *fieldpath.Set
allocator value.Allocator
shouldExtract bool
}
func removeItemsWithSchema(val value.Value, toRemove *fieldpath.Set, schema *schema.Schema, typeRef schema.TypeRef) value.Value {
// removeItemsWithSchema will walk the given value and look for items from the toRemove set.
// Depending on whether shouldExtract is set true or false, it will return a modified version
// of the input value with either:
// 1. only the items in the toRemove set (when shouldExtract is true) or
// 2. the items from the toRemove set removed from the value (when shouldExtract is false).
func removeItemsWithSchema(val value.Value, toRemove *fieldpath.Set, schema *schema.Schema, typeRef schema.TypeRef, shouldExtract bool) value.Value {
w := &removingWalker{
value: val,
schema: schema,
toRemove: toRemove,
allocator: value.NewFreelistAllocator(),
value: val,
schema: schema,
toRemove: toRemove,
allocator: value.NewFreelistAllocator(),
shouldExtract: shouldExtract,
}
resolveSchema(schema, typeRef, val, w)
return value.NewValueInterface(w.out)
@@ -57,13 +64,24 @@ func (w *removingWalker) doList(t *schema.List) (errs ValidationErrors) {
for iter.Next() {
i, item := iter.Item()
// Ignore error because we have already validated this list
pe, _ := listItemToPathElement(w.allocator, t, i, item)
pe, _ := listItemToPathElement(w.allocator, w.schema, t, i, item)
path, _ := fieldpath.MakePath(pe)
// save items on the path when we shouldExtract
// but ignore them when we are removing (i.e. !w.shouldExtract)
if w.toRemove.Has(path) {
continue
if w.shouldExtract {
newItems = append(newItems, item.Unstructured())
} else {
continue
}
}
if subset := w.toRemove.WithPrefix(pe); !subset.Empty() {
item = removeItemsWithSchema(item, subset, w.schema, t.ElementType)
item = removeItemsWithSchema(item, subset, w.schema, t.ElementType, w.shouldExtract)
} else {
// don't save items not on the path when we shouldExtract.
if w.shouldExtract {
continue
}
}
newItems = append(newItems, item.Unstructured())
}
@@ -95,13 +113,22 @@ func (w *removingWalker) doMap(t *schema.Map) ValidationErrors {
fieldType := t.ElementType
if ft, ok := fieldTypes[k]; ok {
fieldType = ft
} else {
if w.toRemove.Has(path) {
return true
}
// save values on the path when we shouldExtract
// but ignore them when we are removing (i.e. !w.shouldExtract)
if w.toRemove.Has(path) {
if w.shouldExtract {
newMap[k] = val.Unstructured()
}
return true
}
if subset := w.toRemove.WithPrefix(pe); !subset.Empty() {
val = removeItemsWithSchema(val, subset, w.schema, fieldType)
val = removeItemsWithSchema(val, subset, w.schema, fieldType, w.shouldExtract)
} else {
// don't save values not on the path when we shouldExtract.
if w.shouldExtract {
return true
}
}
newMap[k] = val.Unstructured()
return true

View File

@@ -19,9 +19,9 @@ package typed
import (
"sync"
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
"sigs.k8s.io/structured-merge-diff/v3/schema"
"sigs.k8s.io/structured-merge-diff/v3/value"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/schema"
"sigs.k8s.io/structured-merge-diff/v4/value"
)
var tPool = sync.Pool{
@@ -96,7 +96,7 @@ func (v *toFieldSetWalker) doScalar(t *schema.Scalar) ValidationErrors {
func (v *toFieldSetWalker) visitListItems(t *schema.List, list value.List) (errs ValidationErrors) {
for i := 0; i < list.Length(); i++ {
child := list.At(i)
pe, _ := listItemToPathElement(v.allocator, t, i, child)
pe, _ := listItemToPathElement(v.allocator, v.schema, t, i, child)
v2 := v.prepareDescent(pe, t.ElementType)
v2.value = child
errs = append(errs, v2.toFieldSet()...)
@@ -137,7 +137,9 @@ func (v *toFieldSetWalker) visitMapItems(t *schema.Map, m value.Map) (errs Valid
v2 := v.prepareDescent(pe, tr)
v2.value = val
errs = append(errs, v2.toFieldSet()...)
if _, ok := t.FindField(key); !ok {
if val.IsNull() || (val.IsMap() && val.AsMap().Length() == 0) {
v2.set.Insert(v2.path)
} else if _, ok := t.FindField(key); !ok {
v2.set.Insert(v2.path)
}
v.finishDescent(v2)

View File

@@ -21,9 +21,9 @@ import (
"strings"
"sync"
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
"sigs.k8s.io/structured-merge-diff/v3/schema"
"sigs.k8s.io/structured-merge-diff/v3/value"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/schema"
"sigs.k8s.io/structured-merge-diff/v4/value"
)
// AsTyped accepts a value and a type and returns a TypedValue. 'v' must have
@@ -61,11 +61,21 @@ type TypedValue struct {
schema *schema.Schema
}
// TypeRef is the type of the value.
func (tv TypedValue) TypeRef() schema.TypeRef {
return tv.typeRef
}
// AsValue removes the type from the TypedValue and only keeps the value.
func (tv TypedValue) AsValue() value.Value {
return tv.value
}
// Schema gets the schema from the TypedValue.
func (tv TypedValue) Schema() *schema.Schema {
return tv.schema
}
// Validate returns an error with a list of every spec violation.
func (tv TypedValue) Validate() error {
w := tv.walker()
@@ -140,7 +150,13 @@ func (tv TypedValue) Compare(rhs *TypedValue) (c *Comparison, err error) {
// RemoveItems removes each provided list or map item from the value.
func (tv TypedValue) RemoveItems(items *fieldpath.Set) *TypedValue {
tv.value = removeItemsWithSchema(tv.value, items, tv.schema, tv.typeRef)
tv.value = removeItemsWithSchema(tv.value, items, tv.schema, tv.typeRef, false)
return &tv
}
// ExtractItems returns a value with only the provided list or map items extracted from the value.
func (tv TypedValue) ExtractItems(items *fieldpath.Set) *TypedValue {
tv.value = removeItemsWithSchema(tv.value, items, tv.schema, tv.typeRef, true)
return &tv
}
@@ -291,3 +307,15 @@ func (c *Comparison) String() string {
}
return bld.String()
}
// ExcludeFields fields from the compare recursively removes the fields
// from the entire comparison
func (c *Comparison) ExcludeFields(fields *fieldpath.Set) *Comparison {
if fields == nil || fields.Empty() {
return c
}
c.Removed = c.Removed.RecursiveDifference(fields)
c.Modified = c.Modified.RecursiveDifference(fields)
c.Added = c.Added.RecursiveDifference(fields)
return c
}

View File

@@ -20,8 +20,8 @@ import (
"fmt"
"strings"
"sigs.k8s.io/structured-merge-diff/v3/schema"
"sigs.k8s.io/structured-merge-diff/v3/value"
"sigs.k8s.io/structured-merge-diff/v4/schema"
"sigs.k8s.io/structured-merge-diff/v4/value"
)
func normalizeUnions(w *mergingWalker) error {

View File

@@ -19,9 +19,9 @@ package typed
import (
"sync"
"sigs.k8s.io/structured-merge-diff/v3/fieldpath"
"sigs.k8s.io/structured-merge-diff/v3/schema"
"sigs.k8s.io/structured-merge-diff/v3/value"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"sigs.k8s.io/structured-merge-diff/v4/schema"
"sigs.k8s.io/structured-merge-diff/v4/value"
)
var vPool = sync.Pool{
@@ -92,7 +92,7 @@ func validateScalar(t *schema.Scalar, v value.Value, prefix string) (errs Valida
case schema.Numeric:
if !v.IsFloat() && !v.IsInt() {
// TODO: should the schema separate int and float?
return errorf("%vexpected numeric (int or float), got %T", prefix, v)
return errorf("%vexpected numeric (int or float), got %T", prefix, v.Unstructured())
}
case schema.String:
if !v.IsString() {
@@ -123,7 +123,7 @@ func (v *validatingObjectWalker) visitListItems(t *schema.List, list value.List)
pe.Index = &i
} else {
var err error
pe, err = listItemToPathElement(v.allocator, t, i, child)
pe, err = listItemToPathElement(v.allocator, v.schema, t, i, child)
if err != nil {
errs = append(errs, errorf("element %v: %v", i, err.Error())...)
// If we can't construct the path element, we can't

View File

@@ -70,11 +70,11 @@ func (f *FieldCacheEntry) CanOmit(fieldVal reflect.Value) bool {
return f.isOmitEmpty && (safeIsNil(fieldVal) || isZero(fieldVal))
}
// GetUsing returns the field identified by this FieldCacheEntry from the provided struct.
// GetFrom returns the field identified by this FieldCacheEntry from the provided struct.
func (f *FieldCacheEntry) GetFrom(structVal reflect.Value) reflect.Value {
// field might be nested within 'inline' structs
for _, elem := range f.fieldPath {
structVal = structVal.FieldByIndex(elem)
structVal = dereference(structVal).FieldByIndex(elem)
}
return structVal
}
@@ -150,7 +150,11 @@ func buildStructCacheEntry(t reflect.Type, infos map[string]*FieldCacheEntry, fi
continue
}
if isInline {
buildStructCacheEntry(field.Type, infos, append(fieldPath, field.Index))
e := field.Type
if field.Type.Kind() == reflect.Ptr {
e = field.Type.Elem()
}
buildStructCacheEntry(e, infos, append(fieldPath, field.Index))
continue
}
info := &FieldCacheEntry{JsonName: jsonName, isOmitEmpty: isOmitempty, fieldPath: append(fieldPath, field.Index), fieldType: field.Type}