Update structured-merge-diff to latest version

This commit is contained in:
Antoine Pelisse
2019-08-17 21:15:47 -07:00
parent 3f00331452
commit 9edc66ad91
17 changed files with 368 additions and 79 deletions

View File

@@ -31,6 +31,14 @@ type Converter interface {
// merge the object on Apply.
type Updater struct {
Converter Converter
enableUnions bool
}
// EnableUnionFeature turns on union handling. It is disabled by default until the
// feature is complete.
func (s *Updater) EnableUnionFeature() {
s.enableUnions = true
}
func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, workflow string, force bool) (fieldpath.ManagedFields, error) {
@@ -112,9 +120,12 @@ func (s *Updater) update(oldObject, newObject *typed.TypedValue, version fieldpa
// PATCH call), and liveObject must be the original object (empty if
// 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) {
newObject, err := liveObject.NormalizeUnions(newObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
var err error
if s.enableUnions {
newObject, err = liveObject.NormalizeUnions(newObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
}
managers = shallowCopyManagers(managers)
managers, err = s.update(liveObject, newObject, version, managers, manager, true)
@@ -144,17 +155,22 @@ func (s *Updater) Update(liveObject, newObject *typed.TypedValue, version fieldp
// and return it.
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)
configObject, err := configObject.NormalizeUnionsApply(configObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
var err error
if s.enableUnions {
configObject, err = configObject.NormalizeUnionsApply(configObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
}
newObject, err := liveObject.Merge(configObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to merge config: %v", err)
}
newObject, err = configObject.NormalizeUnionsApply(newObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
if s.enableUnions {
newObject, err = configObject.NormalizeUnionsApply(newObject)
if err != nil {
return nil, fieldpath.ManagedFields{}, err
}
}
lastSet := managers[manager]
set, err := configObject.ToFieldSet()

View File

@@ -5,6 +5,7 @@ go_library(
srcs = [
"doc.go",
"elements.go",
"equals.go",
"schemaschema.go",
],
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/structured-merge-diff/schema",

View File

@@ -0,0 +1,166 @@
/*
Copyright 2019 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 schema
// Equals returns true iff the two Schemas are equal.
func (a Schema) Equals(b Schema) bool {
if len(a.Types) != len(b.Types) {
return false
}
for i := range a.Types {
if !a.Types[i].Equals(b.Types[i]) {
return false
}
}
return true
}
// Equals returns true iff the two TypeRefs are equal.
//
// Note that two typerefs that have an equivalent type but where one is
// inlined and the other is named, are not considered equal.
func (a TypeRef) Equals(b TypeRef) bool {
if (a.NamedType == nil) != (b.NamedType == nil) {
return false
}
if a.NamedType != nil {
if *a.NamedType != *b.NamedType {
return false
}
//return true
}
return a.Inlined.Equals(b.Inlined)
}
// Equals returns true iff the two TypeDefs are equal.
func (a TypeDef) Equals(b TypeDef) bool {
if a.Name != b.Name {
return false
}
return a.Atom.Equals(b.Atom)
}
// Equals returns true iff the two Atoms are equal.
func (a Atom) Equals(b Atom) bool {
if (a.Scalar == nil) != (b.Scalar == nil) {
return false
}
if (a.List == nil) != (b.List == nil) {
return false
}
if (a.Map == nil) != (b.Map == nil) {
return false
}
switch {
case a.Scalar != nil:
return *a.Scalar == *b.Scalar
case a.List != nil:
return a.List.Equals(*b.List)
case a.Map != nil:
return a.Map.Equals(*b.Map)
}
return true
}
// Equals returns true iff the two Maps are equal.
func (a Map) Equals(b Map) bool {
if !a.ElementType.Equals(b.ElementType) {
return false
}
if a.ElementRelationship != b.ElementRelationship {
return false
}
if len(a.Fields) != len(b.Fields) {
return false
}
for i := range a.Fields {
if !a.Fields[i].Equals(b.Fields[i]) {
return false
}
}
if len(a.Unions) != len(b.Unions) {
return false
}
for i := range a.Unions {
if !a.Unions[i].Equals(b.Unions[i]) {
return false
}
}
return true
}
// Equals returns true iff the two Unions are equal.
func (a Union) Equals(b Union) bool {
if (a.Discriminator == nil) != (b.Discriminator == nil) {
return false
}
if a.Discriminator != nil {
if *a.Discriminator != *b.Discriminator {
return false
}
}
if a.DeduceInvalidDiscriminator != b.DeduceInvalidDiscriminator {
return false
}
if len(a.Fields) != len(b.Fields) {
return false
}
for i := range a.Fields {
if !a.Fields[i].Equals(b.Fields[i]) {
return false
}
}
return true
}
// Equals returns true iff the two UnionFields are equal.
func (a UnionField) Equals(b UnionField) bool {
if a.FieldName != b.FieldName {
return false
}
if a.DiscriminatorValue != b.DiscriminatorValue {
return false
}
return true
}
// Equals returns true iff the two StructFields are equal.
func (a StructField) Equals(b StructField) bool {
if a.Name != b.Name {
return false
}
return a.Type.Equals(b.Type)
}
// Equals returns true iff the two Lists are equal.
func (a List) Equals(b List) bool {
if !a.ElementType.Equals(b.ElementType) {
return false
}
if a.ElementRelationship != b.ElementRelationship {
return false
}
if len(a.Keys) != len(b.Keys) {
return false
}
for i := range a.Keys {
if a.Keys[i] != b.Keys[i] {
return false
}
}
return true
}

View File

@@ -67,6 +67,14 @@ func (ef *errorFormatter) descend(pe fieldpath.PathElement) {
ef.path = append(ef.path, pe)
}
// parent returns the parent, for the purpose of buffer reuse. It's an error to
// call this if there is no parent.
func (ef *errorFormatter) parent() errorFormatter {
return errorFormatter{
path: ef.path[:len(ef.path)-1],
}
}
func (ef errorFormatter) errorf(format string, args ...interface{}) ValidationErrors {
return ValidationErrors{{
Path: append(fieldpath.Path{}, ef.path...),

View File

@@ -17,8 +17,6 @@ limitations under the License.
package typed
import (
"reflect"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/schema"
"sigs.k8s.io/structured-merge-diff/value"
@@ -43,6 +41,9 @@ type mergingWalker struct {
// internal housekeeping--don't set when constructing.
inLeaf bool // Set to true if we're in a "big leaf"--atomic map/list
// Allocate only as many walkers as needed for the depth by storing them here.
spareWalkers *[]*mergingWalker
}
// merge rules examine w.lhs and w.rhs (up to one of which may be nil) and
@@ -75,7 +76,7 @@ func (w *mergingWalker) merge() (errs ValidationErrors) {
alhs := deduceAtom(a, w.lhs)
arhs := deduceAtom(a, w.rhs)
if reflect.DeepEqual(alhs, arhs) {
if alhs.Equals(arhs) {
errs = append(errs, handleAtom(arhs, w.typeRef, w)...)
} else {
w2 := *w
@@ -117,13 +118,30 @@ func (w *mergingWalker) doScalar(t schema.Scalar) (errs ValidationErrors) {
}
func (w *mergingWalker) prepareDescent(pe fieldpath.PathElement, tr schema.TypeRef) *mergingWalker {
w2 := *w
if w.spareWalkers == nil {
// first descent.
w.spareWalkers = &[]*mergingWalker{}
}
var w2 *mergingWalker
if n := len(*w.spareWalkers); n > 0 {
w2, *w.spareWalkers = (*w.spareWalkers)[n-1], (*w.spareWalkers)[:n-1]
} else {
w2 = &mergingWalker{}
}
*w2 = *w
w2.typeRef = tr
w2.errorFormatter.descend(pe)
w2.lhs = nil
w2.rhs = nil
w2.out = nil
return &w2
return w2
}
func (w *mergingWalker) finishDescent(w2 *mergingWalker) {
// if the descent caused a realloc, ensure that we reuse the buffer
// for the next sibling.
w.errorFormatter = w2.errorFormatter.parent()
*w.spareWalkers = append(*w.spareWalkers, w2)
}
func (w *mergingWalker) derefMap(prefix string, v *value.Value, dest **value.Map) (errs ValidationErrors) {
@@ -198,6 +216,7 @@ func (w *mergingWalker) visitListItems(t schema.List, lhs, rhs *value.List) (err
} else if w2.out != nil {
out.Items = append(out.Items, *w2.out)
}
w.finishDescent(w2)
// Keep track of children that have been handled
delete(observedRHS, keyStr)
}
@@ -212,6 +231,7 @@ func (w *mergingWalker) visitListItems(t schema.List, lhs, rhs *value.List) (err
} else if w2.out != nil {
out.Items = append(out.Items, *w2.out)
}
w.finishDescent(w2)
}
}
@@ -272,13 +292,13 @@ func (w *mergingWalker) visitMapItems(t schema.Map, lhs, rhs *value.Map) (errs V
}
if lhs != nil {
for _, litem := range lhs.Items {
name := litem.Name
for i := range lhs.Items {
litem := &lhs.Items[i]
fieldType := t.ElementType
if ft, ok := fieldTypes[name]; ok {
if ft, ok := fieldTypes[litem.Name]; ok {
fieldType = ft
}
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &name}, fieldType)
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &litem.Name}, fieldType)
w2.lhs = &litem.Value
if rhs != nil {
if ritem, ok := rhs.Get(litem.Name); ok {
@@ -288,31 +308,33 @@ func (w *mergingWalker) visitMapItems(t schema.Map, lhs, rhs *value.Map) (errs V
if newErrs := w2.merge(); len(newErrs) > 0 {
errs = append(errs, newErrs...)
} else if w2.out != nil {
out.Set(name, *w2.out)
out.Items = append(out.Items, value.Field{litem.Name, *w2.out})
}
w.finishDescent(w2)
}
}
if rhs != nil {
for _, ritem := range rhs.Items {
for j := range rhs.Items {
ritem := &rhs.Items[j]
if lhs != nil {
if _, ok := lhs.Get(ritem.Name); ok {
continue
}
}
name := ritem.Name
fieldType := t.ElementType
if ft, ok := fieldTypes[name]; ok {
if ft, ok := fieldTypes[ritem.Name]; ok {
fieldType = ft
}
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &name}, fieldType)
w2 := w.prepareDescent(fieldpath.PathElement{FieldName: &ritem.Name}, fieldType)
w2.rhs = &ritem.Value
if newErrs := w2.merge(); len(newErrs) > 0 {
errs = append(errs, newErrs...)
} else if w2.out != nil {
out.Set(name, *w2.out)
out.Items = append(out.Items, value.Field{ritem.Name, *w2.out})
}
w.finishDescent(w2)
}
}

View File

@@ -18,7 +18,7 @@ package typed
import (
"fmt"
"reflect"
"sync"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/schema"
@@ -67,7 +67,9 @@ func (tv TypedValue) AsValue() *value.Value {
// Validate returns an error with a list of every spec violation.
func (tv TypedValue) Validate() error {
if errs := tv.walker().validate(); len(errs) != 0 {
w := tv.walker()
defer w.finished()
if errs := w.validate(); len(errs) != 0 {
return errs
}
return nil
@@ -78,6 +80,7 @@ func (tv TypedValue) Validate() error {
func (tv TypedValue) ToFieldSet() (*fieldpath.Set, error) {
s := fieldpath.NewSet()
w := tv.walker()
defer w.finished()
w.leafFieldCallback = func(p fieldpath.Path) { s.Insert(p) }
w.nodeFieldCallback = func(p fieldpath.Path) { s.Insert(p) }
if errs := w.validate(); len(errs) != 0 {
@@ -118,8 +121,8 @@ func (tv TypedValue) Compare(rhs *TypedValue) (c *Comparison, err error) {
c.Added.Insert(w.path)
} else if w.rhs == nil {
c.Removed.Insert(w.path)
} else if !reflect.DeepEqual(w.rhs, w.lhs) {
// TODO: reflect.DeepEqual is not sufficient for this.
} else if !w.rhs.Equals(*w.lhs) {
// TODO: Equality is not sufficient for this.
// Need to implement equality check on the value type.
c.Modified.Insert(w.path)
}
@@ -154,6 +157,8 @@ func (tv TypedValue) RemoveItems(items *fieldpath.Set) *TypedValue {
// - If discriminator changed to non-nil, all other fields but the
// discriminated one will be cleared,
// - Otherwise, If only one field is left, update discriminator to that value.
//
// Please note: union behavior isn't finalized yet and this is still experimental.
func (tv TypedValue) NormalizeUnions(new *TypedValue) (*TypedValue, error) {
var errs ValidationErrors
var normalizeFn = func(w *mergingWalker) {
@@ -178,6 +183,8 @@ func (tv TypedValue) NormalizeUnions(new *TypedValue) (*TypedValue, error) {
// NormalizeUnionsApply specifically normalize unions on apply. It
// validates that the applied union is correct (there should be no
// ambiguity there), and clear the fields according to the sent intent.
//
// Please note: union behavior isn't finalized yet and this is still experimental.
func (tv TypedValue) NormalizeUnionsApply(new *TypedValue) (*TypedValue, error) {
var errs ValidationErrors
var normalizeFn = func(w *mergingWalker) {
@@ -204,24 +211,41 @@ func (tv TypedValue) Empty() *TypedValue {
return &tv
}
var mwPool = sync.Pool{
New: func() interface{} { return &mergingWalker{} },
}
func merge(lhs, rhs *TypedValue, rule, postRule mergeRule) (*TypedValue, error) {
if lhs.schema != rhs.schema {
return nil, errorFormatter{}.
errorf("expected objects with types from the same schema")
}
if !reflect.DeepEqual(lhs.typeRef, rhs.typeRef) {
if !lhs.typeRef.Equals(rhs.typeRef) {
return nil, errorFormatter{}.
errorf("expected objects of the same type, but got %v and %v", lhs.typeRef, rhs.typeRef)
}
mw := mergingWalker{
lhs: &lhs.value,
rhs: &rhs.value,
schema: lhs.schema,
typeRef: lhs.typeRef,
rule: rule,
postItemHook: postRule,
}
mw := mwPool.Get().(*mergingWalker)
defer func() {
mw.lhs = nil
mw.rhs = nil
mw.schema = nil
mw.typeRef = schema.TypeRef{}
mw.rule = nil
mw.postItemHook = nil
mw.out = nil
mw.inLeaf = false
mwPool.Put(mw)
}()
mw.lhs = &lhs.value
mw.rhs = &rhs.value
mw.schema = lhs.schema
mw.typeRef = lhs.typeRef
mw.rule = rule
mw.postItemHook = postRule
errs := mw.merge()
if len(errs) > 0 {
return nil, errs

View File

@@ -17,17 +17,33 @@ limitations under the License.
package typed
import (
"sync"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/schema"
"sigs.k8s.io/structured-merge-diff/value"
)
var vPool = sync.Pool{
New: func() interface{} { return &validatingObjectWalker{} },
}
func (tv TypedValue) walker() *validatingObjectWalker {
return &validatingObjectWalker{
value: tv.value,
schema: tv.schema,
typeRef: tv.typeRef,
}
v := vPool.Get().(*validatingObjectWalker)
v.value = tv.value
v.schema = tv.schema
v.typeRef = tv.typeRef
return v
}
func (v *validatingObjectWalker) finished() {
v.value = value.Value{}
v.schema = nil
v.typeRef = schema.TypeRef{}
v.leafFieldCallback = nil
v.nodeFieldCallback = nil
v.inLeaf = false
vPool.Put(v)
}
type validatingObjectWalker struct {
@@ -49,9 +65,36 @@ type validatingObjectWalker struct {
// internal housekeeping--don't set when constructing.
inLeaf bool // Set to true if we're in a "big leaf"--atomic map/list
// Allocate only as many walkers as needed for the depth by storing them here.
spareWalkers *[]*validatingObjectWalker
}
func (v validatingObjectWalker) validate() ValidationErrors {
func (v *validatingObjectWalker) prepareDescent(pe fieldpath.PathElement, tr schema.TypeRef) *validatingObjectWalker {
if v.spareWalkers == nil {
// first descent.
v.spareWalkers = &[]*validatingObjectWalker{}
}
var v2 *validatingObjectWalker
if n := len(*v.spareWalkers); n > 0 {
v2, *v.spareWalkers = (*v.spareWalkers)[n-1], (*v.spareWalkers)[:n-1]
} else {
v2 = &validatingObjectWalker{}
}
*v2 = *v
v2.typeRef = tr
v2.errorFormatter.descend(pe)
return v2
}
func (v *validatingObjectWalker) finishDescent(v2 *validatingObjectWalker) {
// if the descent caused a realloc, ensure that we reuse the buffer
// for the next sibling.
v.errorFormatter = v2.errorFormatter.parent()
*v.spareWalkers = append(*v.spareWalkers, v2)
}
func (v *validatingObjectWalker) validate() ValidationErrors {
return resolveSchema(v.schema, v.typeRef, &v.value, v)
}
@@ -87,7 +130,7 @@ func (v *validatingObjectWalker) doNode() {
}
}
func (v validatingObjectWalker) doScalar(t schema.Scalar) ValidationErrors {
func (v *validatingObjectWalker) doScalar(t schema.Scalar) ValidationErrors {
if errs := v.validateScalar(t, &v.value, ""); len(errs) > 0 {
return errs
}
@@ -98,7 +141,7 @@ func (v validatingObjectWalker) doScalar(t schema.Scalar) ValidationErrors {
return nil
}
func (v validatingObjectWalker) visitListItems(t schema.List, list *value.List) (errs ValidationErrors) {
func (v *validatingObjectWalker) visitListItems(t schema.List, list *value.List) (errs ValidationErrors) {
observedKeys := map[string]struct{}{}
for i, child := range list.Items {
pe, err := listItemToPathElement(t, i, child)
@@ -114,18 +157,17 @@ func (v validatingObjectWalker) visitListItems(t schema.List, list *value.List)
errs = append(errs, v.errorf("duplicate entries for key %v", keyStr)...)
}
observedKeys[keyStr] = struct{}{}
v2 := v
v2.errorFormatter.descend(pe)
v2 := v.prepareDescent(pe, t.ElementType)
v2.value = child
v2.typeRef = t.ElementType
errs = append(errs, v2.validate()...)
v2.doNode()
v.finishDescent(v2)
}
return errs
}
func (v validatingObjectWalker) doList(t schema.List) (errs ValidationErrors) {
func (v *validatingObjectWalker) doList(t schema.List) (errs ValidationErrors) {
list, err := listValue(v.value)
if err != nil {
return v.error(err)
@@ -144,7 +186,7 @@ func (v validatingObjectWalker) doList(t schema.List) (errs ValidationErrors) {
return errs
}
func (v validatingObjectWalker) visitMapItems(t schema.Map, m *value.Map) (errs ValidationErrors) {
func (v *validatingObjectWalker) visitMapItems(t schema.Map, m *value.Map) (errs ValidationErrors) {
fieldTypes := map[string]schema.TypeRef{}
for i := range t.Fields {
// I don't want to use the loop variable since a reference
@@ -153,25 +195,27 @@ func (v validatingObjectWalker) visitMapItems(t schema.Map, m *value.Map) (errs
fieldTypes[f.Name] = f.Type
}
for _, item := range m.Items {
v2 := v
name := item.Name
v2.errorFormatter.descend(fieldpath.PathElement{FieldName: &name})
v2.value = item.Value
for i := range m.Items {
item := &m.Items[i]
pe := fieldpath.PathElement{FieldName: &item.Name}
var ok bool
if v2.typeRef, ok = fieldTypes[name]; ok {
if tr, ok := fieldTypes[item.Name]; ok {
v2 := v.prepareDescent(pe, tr)
v2.value = item.Value
errs = append(errs, v2.validate()...)
v.finishDescent(v2)
} else {
v2.typeRef = t.ElementType
v2 := v.prepareDescent(pe, t.ElementType)
v2.value = item.Value
errs = append(errs, v2.validate()...)
v2.doNode()
v.finishDescent(v2)
}
}
return errs
}
func (v validatingObjectWalker) doMap(t schema.Map) (errs ValidationErrors) {
func (v *validatingObjectWalker) doMap(t schema.Map) (errs ValidationErrors) {
m, err := mapValue(v.value)
if err != nil {
return v.error(err)

View File

@@ -34,6 +34,11 @@ type Value struct {
Null bool // represents an explicit `"foo" = null`
}
// Equals returns true iff the two values are equal.
func (v Value) Equals(rhs Value) bool {
return !v.Less(rhs) && !rhs.Less(v)
}
// Less provides a total ordering for Value (so that they can be sorted, even
// if they are of different types).
func (v Value) Less(rhs Value) bool {
@@ -167,7 +172,7 @@ type Map struct {
// may be nil; lazily constructed.
// TODO: Direct modifications to Items above will cause serious problems.
index map[string]*Field
index map[string]int
// may be empty; lazily constructed.
// TODO: Direct modifications to Items above will cause serious problems.
order []int
@@ -264,14 +269,16 @@ func (m *Map) Less(rhs *Map) bool {
// Get returns the (Field, true) or (nil, false) if it is not present
func (m *Map) Get(key string) (*Field, bool) {
if m.index == nil {
m.index = map[string]*Field{}
m.index = map[string]int{}
for i := range m.Items {
f := &m.Items[i]
m.index[f.Name] = f
m.index[m.Items[i].Name] = i
}
}
f, ok := m.index[key]
return f, ok
if !ok {
return nil, false
}
return &m.Items[f], true
}
// Set inserts or updates the given item.
@@ -281,7 +288,8 @@ func (m *Map) Set(key string, value Value) {
return
}
m.Items = append(m.Items, Field{Name: key, Value: value})
m.index = nil // Since the append might have reallocated
i := len(m.Items) - 1
m.index[key] = i
m.order = nil
}