diff --git a/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/path.go b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/path.go index 8c2225445d6..c11c7f28a5b 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/path.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/path.go @@ -35,6 +35,12 @@ func (fp Path) String() string { return strings.Join(strs, "") } +func (fp Path) Copy() Path { + new := make(Path, len(fp)) + copy(new, fp) + return new +} + // MakePath constructs a Path. The parts may be PathElements, ints, strings. func MakePath(parts ...interface{}) (Path, error) { var fp Path diff --git a/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/set.go b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/set.go index 91e5dda1060..a4290c4d942 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/set.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/fieldpath/set.go @@ -154,6 +154,16 @@ func (s *Set) iteratePrefix(prefix Path, f func(Path)) { s.Children.iteratePrefix(prefix, f) } +// WithPrefix returns the subset of paths which begin with the given prefix, +// with the prefix not included. +func (s *Set) WithPrefix(pe PathElement) *Set { + subset, ok := s.Children.Get(pe) + if !ok { + return NewSet() + } + return subset +} + // setNode is a pair of PathElement / Set, for the purpose of expressing // nested set membership. type setNode struct { diff --git a/vendor/sigs.k8s.io/structured-merge-diff/merge/update.go b/vendor/sigs.k8s.io/structured-merge-diff/merge/update.go index 3e7ce935728..5572258f238 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/merge/update.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/merge/update.go @@ -85,6 +85,15 @@ func (s *Updater) update(oldObject, newObject typed.TypedValue, version fieldpat for manager, conflictSet := range conflicts { managers[manager].Set = managers[manager].Set.Difference(conflictSet.Set) + if managers[manager].Set.Empty() { + delete(managers, manager) + } + } + + if _, ok := managers[workflow]; !ok { + managers[workflow] = &fieldpath.VersionedSet{ + Set: fieldpath.NewSet(), + } } return managers, nil @@ -105,13 +114,11 @@ func (s *Updater) Update(liveObject, newObject typed.TypedValue, version fieldpa if err != nil { return fieldpath.ManagedFields{}, fmt.Errorf("failed to compare live and new objects: %v", err) } - if _, ok := managers[manager]; !ok { - managers[manager] = &fieldpath.VersionedSet{ - Set: fieldpath.NewSet(), - } - } managers[manager].Set = managers[manager].Set.Union(compare.Modified).Union(compare.Added).Difference(compare.Removed) managers[manager].APIVersion = version + if managers[manager].Set.Empty() { + delete(managers, manager) + } return managers, nil } @@ -121,22 +128,41 @@ func (s *Updater) Update(liveObject, newObject typed.TypedValue, version fieldpa func (s *Updater) Apply(liveObject, configObject typed.TypedValue, version fieldpath.APIVersion, managers fieldpath.ManagedFields, manager string, force bool) (typed.TypedValue, fieldpath.ManagedFields, error) { newObject, err := liveObject.Merge(configObject) if err != nil { - return typed.TypedValue{}, fieldpath.ManagedFields{}, fmt.Errorf("failed to merge config: %v", err) + return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to merge config: %v", err) } managers, err = s.update(liveObject, newObject, version, managers, manager, force) if err != nil { - return typed.TypedValue{}, fieldpath.ManagedFields{}, err + return nil, fieldpath.ManagedFields{}, err + } + newObject, err = s.removeDisownedItems(newObject, configObject, managers[manager]) + if err != nil { + return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to remove fields: %v", err) } - - // TODO: Remove unconflicting removed fields - set, err := configObject.ToFieldSet() if err != nil { - return typed.TypedValue{}, fieldpath.ManagedFields{}, fmt.Errorf("failed to get field set: %v", err) + return nil, fieldpath.ManagedFields{}, fmt.Errorf("failed to get field set: %v", err) } managers[manager] = &fieldpath.VersionedSet{ Set: set, APIVersion: version, } + if managers[manager].Set.Empty() { + delete(managers, manager) + } return newObject, managers, nil } + +func (s *Updater) removeDisownedItems(merged, applied typed.TypedValue, lastSet *fieldpath.VersionedSet) (typed.TypedValue, error) { + if lastSet.Set.Empty() { + return merged, nil + } + convertedApplied, err := s.Converter.Convert(applied, lastSet.APIVersion) + if err != nil { + return nil, fmt.Errorf("failed to convert applied config to last applied version: %v", err) + } + appliedSet, err := convertedApplied.ToFieldSet() + if err != nil { + return nil, fmt.Errorf("failed to create field set from applied config in last applied version: %v", err) + } + return merged.RemoveItems(lastSet.Set.Difference(appliedSet)), nil +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/schema/BUILD b/vendor/sigs.k8s.io/structured-merge-diff/schema/BUILD index 65f9085e16b..2f81776994b 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/schema/BUILD +++ b/vendor/sigs.k8s.io/structured-merge-diff/schema/BUILD @@ -5,11 +5,13 @@ go_library( srcs = [ "doc.go", "elements.go", + "fromvalue.go", "schemaschema.go", ], importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/structured-merge-diff/schema", importpath = "sigs.k8s.io/structured-merge-diff/schema", visibility = ["//visibility:public"], + deps = ["//vendor/sigs.k8s.io/structured-merge-diff/value:go_default_library"], ) filegroup( diff --git a/vendor/sigs.k8s.io/structured-merge-diff/schema/elements.go b/vendor/sigs.k8s.io/structured-merge-diff/schema/elements.go index a30bab883e8..d863bca7c42 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/schema/elements.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/schema/elements.go @@ -207,7 +207,7 @@ func (s Schema) FindNamedType(name string) (TypeDef, bool) { // // This allows callers to not care about the difference between a (possibly // inlined) reference and a definition. -func (s Schema) Resolve(tr TypeRef) (Atom, bool) { +func (s *Schema) Resolve(tr TypeRef) (Atom, bool) { if tr.NamedType != nil { t, ok := s.FindNamedType(*tr.NamedType) if !ok { diff --git a/vendor/sigs.k8s.io/structured-merge-diff/schema/fromvalue.go b/vendor/sigs.k8s.io/structured-merge-diff/schema/fromvalue.go new file mode 100644 index 00000000000..3c41d75796b --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/schema/fromvalue.go @@ -0,0 +1,57 @@ +/* +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 + +import ( + "sigs.k8s.io/structured-merge-diff/value" +) + +// TypeRefFromValue creates an inlined type from a value v +func TypeRefFromValue(v value.Value) TypeRef { + atom := atomFor(v) + return TypeRef{ + Inlined: atom, + } +} + +func atomFor(v value.Value) Atom { + switch { + // Untyped cases (handled at the bottom of this function) + case v.Null: + case v.ListValue != nil: + case v.FloatValue != nil: + case v.IntValue != nil: + case v.StringValue != nil: + case v.BooleanValue != nil: + // Recursive case + case v.MapValue != nil: + s := Struct{} + for i := range v.MapValue.Items { + child := v.MapValue.Items[i] + field := StructField{ + Name: child.Name, + Type: TypeRef{ + Inlined: atomFor(child.Value), + }, + } + s.Fields = append(s.Fields, field) + } + return Atom{Struct: &s} + } + + return Atom{Untyped: &Untyped{}} +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/BUILD b/vendor/sigs.k8s.io/structured-merge-diff/typed/BUILD index 1e6481fb1e4..7b2a669254c 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/typed/BUILD +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/BUILD @@ -3,10 +3,12 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ + "deduced.go", "doc.go", "helpers.go", "merge.go", "parser.go", + "remove.go", "typed.go", "validate.go", ], diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/deduced.go b/vendor/sigs.k8s.io/structured-merge-diff/typed/deduced.go new file mode 100644 index 00000000000..0a8d3080a01 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/deduced.go @@ -0,0 +1,178 @@ +/* +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 ( + "reflect" + + "sigs.k8s.io/structured-merge-diff/fieldpath" + "sigs.k8s.io/structured-merge-diff/value" +) + +// deducedTypedValue holds a value and guesses what it is and what to +// do with it. +type deducedTypedValue struct { + value value.Value +} + +// AsTypedDeduced is going to generate it's own type definition based on +// the content of the object. This is useful for CRDs that don't have a +// validation field. +func AsTypedDeduced(v value.Value) TypedValue { + return deducedTypedValue{value: v} +} + +func (dv deducedTypedValue) AsValue() *value.Value { + return &dv.value +} + +func (deducedTypedValue) Validate() error { + return nil +} + +func (dv deducedTypedValue) ToFieldSet() (*fieldpath.Set, error) { + set := fieldpath.NewSet() + fieldsetDeduced(dv.value, fieldpath.Path{}, set) + return set, nil +} + +func fieldsetDeduced(v value.Value, path fieldpath.Path, set *fieldpath.Set) { + if v.MapValue == nil { + set.Insert(path) + return + } + + // We have a map. + // copy the existing path, append each item, and recursively call. + for i := range v.MapValue.Items { + child := v.MapValue.Items[i] + np := path.Copy() + np = append(np, fieldpath.PathElement{FieldName: &child.Name}) + fieldsetDeduced(child.Value, np, set) + } +} + +func (dv deducedTypedValue) Merge(pso TypedValue) (TypedValue, error) { + tpso, ok := pso.(deducedTypedValue) + if !ok { + return nil, errorFormatter{}. + errorf("can't merge deducedTypedValue with %T", tpso) + } + return AsTypedDeduced(mergeDeduced(dv.value, tpso.value)), nil +} + +func mergeDeduced(lhs, rhs value.Value) value.Value { + // If both sides are maps, merge them, otherwise return right + // side. + if rhs.MapValue == nil || lhs.MapValue == nil { + return rhs + } + + v := value.Value{MapValue: &value.Map{}} + for i := range lhs.MapValue.Items { + child := lhs.MapValue.Items[i] + v.MapValue.Set(child.Name, child.Value) + } + for i := range rhs.MapValue.Items { + child := rhs.MapValue.Items[i] + if sub, ok := v.MapValue.Get(child.Name); ok { + new := mergeDeduced(sub.Value, child.Value) + v.MapValue.Set(child.Name, new) + } else { + v.MapValue.Set(child.Name, child.Value) + } + } + return v +} + +func (dv deducedTypedValue) Compare(rhs TypedValue) (c *Comparison, err error) { + trhs, ok := rhs.(deducedTypedValue) + if !ok { + return nil, errorFormatter{}. + errorf("can't merge deducedTypedValue with %T", rhs) + } + + c = &Comparison{ + Removed: fieldpath.NewSet(), + Modified: fieldpath.NewSet(), + Added: fieldpath.NewSet(), + } + + added(dv.value, trhs.value, fieldpath.Path{}, c.Added) + added(trhs.value, dv.value, fieldpath.Path{}, c.Removed) + modified(dv.value, trhs.value, fieldpath.Path{}, c.Modified) + + merge, err := dv.Merge(rhs) + if err != nil { + return nil, err + } + c.Merged = merge + return c, nil +} + +func added(lhs, rhs value.Value, path fieldpath.Path, set *fieldpath.Set) { + if lhs.MapValue == nil && rhs.MapValue == nil { + // Both non-maps, nothing added, do nothing. + } else if lhs.MapValue == nil && rhs.MapValue != nil { + // From leaf to map, add leaf fields of map. + fieldsetDeduced(rhs, path, set) + } else if lhs.MapValue != nil && rhs.MapValue == nil { + // Went from map to field, add field. + set.Insert(path) + } else { + // Both are maps. + for i := range rhs.MapValue.Items { + child := rhs.MapValue.Items[i] + np := path.Copy() + np = append(np, fieldpath.PathElement{FieldName: &child.Name}) + + if v, ok := lhs.MapValue.Get(child.Name); ok { + added(v.Value, child.Value, np, set) + } else { + fieldsetDeduced(child.Value, np, set) + } + } + } +} + +func modified(lhs, rhs value.Value, path fieldpath.Path, set *fieldpath.Set) { + if lhs.MapValue == nil && rhs.MapValue == nil { + if !reflect.DeepEqual(lhs, rhs) { + set.Insert(path) + } + } else if lhs.MapValue != nil && rhs.MapValue != nil { + // Both are maps. + for i := range rhs.MapValue.Items { + child := rhs.MapValue.Items[i] + + v, ok := lhs.MapValue.Get(child.Name) + if !ok { + continue + } + + np := path.Copy() + np = append(np, fieldpath.PathElement{FieldName: &child.Name}) + modified(v.Value, child.Value, np, set) + } + } +} + +// RemoveItems does nothing because all lists in a deducedTypedValue are considered atomic, +// and there are no maps because it is indistinguishable from a struct. +func (dv deducedTypedValue) RemoveItems(_ *fieldpath.Set) TypedValue { + return dv +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/helpers.go b/vendor/sigs.k8s.io/structured-merge-diff/typed/helpers.go index 95b343014d0..e7fadc0182f 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/typed/helpers.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/helpers.go @@ -129,6 +129,9 @@ func (ef errorFormatter) validateScalar(t schema.Scalar, v *value.Value, prefix if v == nil { return nil } + if v.Null { + return nil + } switch t { case schema.Numeric: if v.FloatValue == nil && v.IntValue == nil { diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/parser.go b/vendor/sigs.k8s.io/structured-merge-diff/typed/parser.go index de1585d6a60..bf21d2f910f 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/typed/parser.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/parser.go @@ -68,47 +68,80 @@ func (p *Parser) TypeNames() (names []string) { // Type returns a helper which can produce objects of the given type. Any // errors are deferred until a further function is called. -func (p *Parser) Type(name string) *ParseableType { - return &ParseableType{ +func (p *Parser) Type(name string) ParseableType { + return &parseableType{ parser: p, typename: name, } } // ParseableType allows for easy production of typed objects. -type ParseableType struct { +type ParseableType interface { + IsValid() bool + FromYAML(YAMLObject) (TypedValue, error) + FromUnstructured(interface{}) (TypedValue, error) +} + +type parseableType struct { parser *Parser typename string } +var _ ParseableType = &parseableType{} + // IsValid return true if p's schema and typename are valid. -func (p *ParseableType) IsValid() bool { +func (p *parseableType) IsValid() bool { _, ok := p.parser.Schema.Resolve(schema.TypeRef{NamedType: &p.typename}) return ok } -// New returns a new empty object with the current schema and the -// type "typename". -func (p *ParseableType) New() (TypedValue, error) { - return p.FromYAML(YAMLObject("{}")) -} - // FromYAML parses a yaml string into an object with the current schema // and the type "typename" or an error if validation fails. -func (p *ParseableType) FromYAML(object YAMLObject) (TypedValue, error) { +func (p *parseableType) FromYAML(object YAMLObject) (TypedValue, error) { v, err := value.FromYAML([]byte(object)) if err != nil { - return TypedValue{}, err + return nil, err } return AsTyped(v, &p.parser.Schema, p.typename) } // FromUnstructured converts a go interface to a TypedValue. It will return an // error if the resulting object fails schema validation. -func (p *ParseableType) FromUnstructured(in interface{}) (TypedValue, error) { +func (p *parseableType) FromUnstructured(in interface{}) (TypedValue, error) { v, err := value.FromUnstructured(in) if err != nil { - return TypedValue{}, err + return nil, err } return AsTyped(v, &p.parser.Schema, p.typename) } + +// DeducedParseableType is a ParseableType that deduces the type from +// the content of the object. +type DeducedParseableType struct{} + +var _ ParseableType = DeducedParseableType{} + +// IsValid always returns true for a DeducedParseableType. +func (p DeducedParseableType) IsValid() bool { + return true +} + +// FromYAML parses a yaml string into an object and deduces the type for +// that object. +func (p DeducedParseableType) FromYAML(object YAMLObject) (TypedValue, error) { + v, err := value.FromYAML([]byte(object)) + if err != nil { + return nil, err + } + return AsTypedDeduced(v), nil +} + +// FromUnstructured converts a go interface to a TypedValue. It will return an +// error if the input object uses un-handled types. +func (p DeducedParseableType) FromUnstructured(in interface{}) (TypedValue, error) { + v, err := value.FromUnstructured(in) + if err != nil { + return nil, err + } + return AsTypedDeduced(v), nil +} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/remove.go b/vendor/sigs.k8s.io/structured-merge-diff/typed/remove.go new file mode 100644 index 00000000000..657fd1467f5 --- /dev/null +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/remove.go @@ -0,0 +1,119 @@ +/* +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 typed + +import ( + "sigs.k8s.io/structured-merge-diff/fieldpath" + "sigs.k8s.io/structured-merge-diff/schema" + "sigs.k8s.io/structured-merge-diff/value" +) + +type removingWalker struct { + value *value.Value + schema *schema.Schema + toRemove *fieldpath.Set +} + +func removeItemsWithSchema(value *value.Value, toRemove *fieldpath.Set, schema *schema.Schema, typeRef schema.TypeRef) { + w := &removingWalker{ + value: value, + schema: schema, + toRemove: toRemove, + } + resolveSchema(schema, typeRef, w) +} + +// doLeaf should be called on leaves before descending into children, if there +// will be a descent. It modifies w.inLeaf. +func (w *removingWalker) doLeaf() ValidationErrors { return nil } + +func (w *removingWalker) doScalar(t schema.Scalar) ValidationErrors { return nil } + +func (w *removingWalker) doStruct(t schema.Struct) ValidationErrors { + s := w.value.MapValue + + // If struct is null, empty, or atomic just return + if s == nil || len(s.Items) == 0 || t.ElementRelationship == schema.Atomic { + return nil + } + + fieldTypes := map[string]schema.TypeRef{} + for _, structField := range t.Fields { + fieldTypes[structField.Name] = structField.Type + } + + for i, _ := range s.Items { + item := s.Items[i] + pe := fieldpath.PathElement{FieldName: &item.Name} + if subset := w.toRemove.WithPrefix(pe); !subset.Empty() { + removeItemsWithSchema(&s.Items[i].Value, subset, w.schema, fieldTypes[item.Name]) + } + } + return nil +} + +func (w *removingWalker) doList(t schema.List) (errs ValidationErrors) { + l := w.value.ListValue + + // If list is null, empty, or atomic just return + if l == nil || len(l.Items) == 0 || t.ElementRelationship == schema.Atomic { + return nil + } + + newItems := []value.Value{} + for i, _ := range l.Items { + item := l.Items[i] + // Ignore error because we have already validated this list + pe, _ := listItemToPathElement(t, i, item) + path, _ := fieldpath.MakePath(pe) + if w.toRemove.Has(path) { + continue + } + if subset := w.toRemove.WithPrefix(pe); !subset.Empty() { + removeItemsWithSchema(&l.Items[i], subset, w.schema, t.ElementType) + } + newItems = append(newItems, l.Items[i]) + } + l.Items = newItems + return nil +} + +func (w *removingWalker) doMap(t schema.Map) ValidationErrors { + m := w.value.MapValue + + // If map is null, empty, or atomic just return + if m == nil || len(m.Items) == 0 || t.ElementRelationship == schema.Atomic { + return nil + } + + newMap := &value.Map{} + for i, _ := range m.Items { + item := m.Items[i] + pe := fieldpath.PathElement{FieldName: &item.Name} + path, _ := fieldpath.MakePath(pe) + if w.toRemove.Has(path) { + continue + } + if subset := w.toRemove.WithPrefix(pe); !subset.Empty() { + removeItemsWithSchema(&m.Items[i].Value, subset, w.schema, t.ElementType) + } + newMap.Set(item.Name, m.Items[i].Value) + } + w.value.MapValue = newMap + return nil +} + +func (*removingWalker) doUntyped(_ schema.Untyped) ValidationErrors { return nil } + +func (*removingWalker) errorf(_ string, _ ...interface{}) ValidationErrors { return nil } diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/typed.go b/vendor/sigs.k8s.io/structured-merge-diff/typed/typed.go index 3a7ee0ceb97..f3134b00d1d 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/typed/typed.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/typed.go @@ -25,44 +25,87 @@ import ( "sigs.k8s.io/structured-merge-diff/value" ) -// TypedValue is a value of some specific type. -type TypedValue struct { - value value.Value - typeRef schema.TypeRef - schema *schema.Schema +// TypedValue is a value with an associated type. +type TypedValue interface { + // AsValue removes the type from the TypedValue and only keeps the value. + AsValue() *value.Value + // Validate returns an error with a list of every spec violation. + Validate() error + // ToFieldSet creates a set containing every leaf field mentioned, or + // validation errors, if any were encountered. + ToFieldSet() (*fieldpath.Set, error) + // Merge returns the result of merging tv and pso ("partially specified + // object") together. Of note: + // * No fields can be removed by this operation. + // * If both tv and pso specify a given leaf field, the result will keep pso's + // value. + // * Container typed elements will have their items ordered: + // * like tv, if pso doesn't change anything in the container + // * like pso, if pso does change something in the container. + // tv and pso must both be of the same type (their Schema and TypeRef must + // match), or an error will be returned. Validation errors will be returned if + // the objects don't conform to the schema. + Merge(pso TypedValue) (TypedValue, error) + // Compare compares the two objects. See the comments on the `Comparison` + // struct for details on the return value. + // + // tv and rhs must both be of the same type (their Schema and TypeRef must + // match), or an error will be returned. Validation errors will be returned if + // the objects don't conform to the schema. + Compare(rhs TypedValue) (c *Comparison, err error) + // RemoveItems removes each provided list or map item from the value. + RemoveItems(items *fieldpath.Set) TypedValue } // AsTyped accepts a value and a type and returns a TypedValue. 'v' must have // type 'typeName' in the schema. An error is returned if the v doesn't conform // to the schema. func AsTyped(v value.Value, s *schema.Schema, typeName string) (TypedValue, error) { - tv := TypedValue{ + tv := typedValue{ value: v, typeRef: schema.TypeRef{NamedType: &typeName}, schema: s, } if err := tv.Validate(); err != nil { - return TypedValue{}, err + return nil, err } return tv, nil } -// AsValue removes the type from the TypedValue and only keeps the value. -func (tv TypedValue) AsValue() *value.Value { +// AsTypeUnvalidated is just like AsTyped, but doesn't validate that the type +// conforms to the schema, for cases where that has already been checked or +// where you're going to call a method that validates as a side-effect (like +// ToFieldSet). +func AsTypedUnvalidated(v value.Value, s *schema.Schema, typeName string) TypedValue { + tv := typedValue{ + value: v, + typeRef: schema.TypeRef{NamedType: &typeName}, + schema: s, + } + return tv +} + +// typedValue is a value of some specific type. +type typedValue struct { + value value.Value + typeRef schema.TypeRef + schema *schema.Schema +} + +var _ TypedValue = typedValue{} + +func (tv typedValue) AsValue() *value.Value { return &tv.value } -// Validate returns an error with a list of every spec violation. -func (tv TypedValue) Validate() error { +func (tv typedValue) Validate() error { if errs := tv.walker().validate(); len(errs) != 0 { return errs } return nil } -// ToFieldSet creates a set containing every leaf field mentioned in tv, or -// validation errors, if any were encountered. -func (tv TypedValue) ToFieldSet() (*fieldpath.Set, error) { +func (tv typedValue) ToFieldSet() (*fieldpath.Set, error) { s := fieldpath.NewSet() w := tv.walker() w.leafFieldCallback = func(p fieldpath.Path) { s.Insert(p) } @@ -72,19 +115,91 @@ func (tv TypedValue) ToFieldSet() (*fieldpath.Set, error) { return s, nil } -// Merge returns the result of merging tv and pso ("partially specified -// object") together. Of note: -// * No fields can be removed by this operation. -// * If both tv and pso specify a given leaf field, the result will keep pso's -// value. -// * Container typed elements will have their items ordered: -// * like tv, if pso doesn't change anything in the container -// * like pso, if pso does change something in the container. -// tv and pso must both be of the same type (their Schema and TypeRef must -// match), or an error will be returned. Validation errors will be returned if -// the objects don't conform to the schema. -func (tv TypedValue) Merge(pso TypedValue) (TypedValue, error) { - return merge(tv, pso, ruleKeepRHS, nil) +func (tv typedValue) Merge(pso TypedValue) (TypedValue, error) { + tpso, ok := pso.(typedValue) + if !ok { + return nil, errorFormatter{}. + errorf("can't merge typedValue with %T", pso) + } + return merge(tv, tpso, ruleKeepRHS, nil) +} + +func (tv typedValue) Compare(rhs TypedValue) (c *Comparison, err error) { + trhs, ok := rhs.(typedValue) + if !ok { + return nil, errorFormatter{}. + errorf("can't compare typedValue with %T", rhs) + } + c = &Comparison{ + Removed: fieldpath.NewSet(), + Modified: fieldpath.NewSet(), + Added: fieldpath.NewSet(), + } + c.Merged, err = merge(tv, trhs, func(w *mergingWalker) { + if w.lhs == nil { + 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. + // Need to implement equality check on the value type. + c.Modified.Insert(w.path) + } + + ruleKeepRHS(w) + }, func(w *mergingWalker) { + if w.lhs == nil { + c.Added.Insert(w.path) + } else if w.rhs == nil { + c.Removed.Insert(w.path) + } + }) + if err != nil { + return nil, err + } + + return c, nil +} + +// RemoveItems removes each provided list or map item from the value. +func (tv typedValue) RemoveItems(items *fieldpath.Set) TypedValue { + removeItemsWithSchema(&tv.value, items, tv.schema, tv.typeRef) + return tv +} + +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) { + 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, + } + errs := mw.merge() + if len(errs) > 0 { + return nil, errs + } + + out := typedValue{ + schema: lhs.schema, + typeRef: lhs.typeRef, + } + if mw.out == nil { + out.value = value.Value{Null: true} + } else { + out.value = *mw.out + } + return out, nil } // Comparison is the return value of a TypedValue.Compare() operation. @@ -125,89 +240,3 @@ func (c *Comparison) String() string { } return str } - -// Compare compares the two objects. See the comments on the `Comparison` -// struct for details on the return value. -// -// tv and rhs must both be of the same type (their Schema and TypeRef must -// match), or an error will be returned. Validation errors will be returned if -// the objects don't conform to the schema. -func (tv TypedValue) Compare(rhs TypedValue) (c *Comparison, err error) { - c = &Comparison{ - Removed: fieldpath.NewSet(), - Modified: fieldpath.NewSet(), - Added: fieldpath.NewSet(), - } - c.Merged, err = merge(tv, rhs, func(w *mergingWalker) { - if w.lhs == nil { - 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. - // Need to implement equality check on the value type. - c.Modified.Insert(w.path) - } - - ruleKeepRHS(w) - }, func(w *mergingWalker) { - if w.lhs == nil { - c.Added.Insert(w.path) - } else if w.rhs == nil { - c.Removed.Insert(w.path) - } - }) - if err != nil { - return nil, err - } - - return c, nil -} - -func merge(lhs, rhs TypedValue, rule, postRule mergeRule) (TypedValue, error) { - if lhs.schema != rhs.schema { - return TypedValue{}, errorFormatter{}. - errorf("expected objects with types from the same schema") - } - if !reflect.DeepEqual(lhs.typeRef, rhs.typeRef) { - return TypedValue{}, 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, - } - errs := mw.merge() - if len(errs) > 0 { - return TypedValue{}, errs - } - - out := TypedValue{ - schema: lhs.schema, - typeRef: lhs.typeRef, - } - if mw.out == nil { - out.value = value.Value{Null: true} - } else { - out.value = *mw.out - } - return out, nil -} - -// AsTypeUnvalidated is just like WithType, but doesn't validate that the type -// conforms to the schema, for cases where that has already been checked or -// where you're going to call a method that validates as a side-effect (like -// ToFieldSet). -func AsTypedUnvalidated(v value.Value, s *schema.Schema, typeName string) TypedValue { - tv := TypedValue{ - value: v, - typeRef: schema.TypeRef{NamedType: &typeName}, - schema: s, - } - return tv -} diff --git a/vendor/sigs.k8s.io/structured-merge-diff/typed/validate.go b/vendor/sigs.k8s.io/structured-merge-diff/typed/validate.go index 38cb66c7174..9edf6fb64d1 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/typed/validate.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/typed/validate.go @@ -22,7 +22,7 @@ import ( "sigs.k8s.io/structured-merge-diff/value" ) -func (tv TypedValue) walker() *validatingObjectWalker { +func (tv typedValue) walker() *validatingObjectWalker { return &validatingObjectWalker{ value: tv.value, schema: tv.schema, diff --git a/vendor/sigs.k8s.io/structured-merge-diff/value/unstructured.go b/vendor/sigs.k8s.io/structured-merge-diff/value/unstructured.go index 62660f2fca4..004bf224f46 100644 --- a/vendor/sigs.k8s.io/structured-merge-diff/value/unstructured.go +++ b/vendor/sigs.k8s.io/structured-merge-diff/value/unstructured.go @@ -31,7 +31,7 @@ import ( func FromYAML(input []byte) (Value, error) { var decoded interface{} - if len(input) == 0 || (len(input) == 4 && string(input) == "null") { + if len(input) == 4 && string(input) == "null" { // Special case since the yaml package doesn't accurately // preserve this. return Value{Null: true}, nil