Commit 1751fc01 authored by Antoine Pelisse's avatar Antoine Pelisse

Update smd repo to handle CRDs

parent 7e4cc384
...@@ -35,6 +35,12 @@ func (fp Path) String() string { ...@@ -35,6 +35,12 @@ func (fp Path) String() string {
return strings.Join(strs, "") 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. // MakePath constructs a Path. The parts may be PathElements, ints, strings.
func MakePath(parts ...interface{}) (Path, error) { func MakePath(parts ...interface{}) (Path, error) {
var fp Path var fp Path
......
...@@ -154,6 +154,16 @@ func (s *Set) iteratePrefix(prefix Path, f func(Path)) { ...@@ -154,6 +154,16 @@ func (s *Set) iteratePrefix(prefix Path, f func(Path)) {
s.Children.iteratePrefix(prefix, f) 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 // setNode is a pair of PathElement / Set, for the purpose of expressing
// nested set membership. // nested set membership.
type setNode struct { type setNode struct {
......
...@@ -85,6 +85,15 @@ func (s *Updater) update(oldObject, newObject typed.TypedValue, version fieldpat ...@@ -85,6 +85,15 @@ func (s *Updater) update(oldObject, newObject typed.TypedValue, version fieldpat
for manager, conflictSet := range conflicts { for manager, conflictSet := range conflicts {
managers[manager].Set = managers[manager].Set.Difference(conflictSet.Set) 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 return managers, nil
...@@ -105,13 +114,11 @@ func (s *Updater) Update(liveObject, newObject typed.TypedValue, version fieldpa ...@@ -105,13 +114,11 @@ func (s *Updater) Update(liveObject, newObject typed.TypedValue, version fieldpa
if err != nil { if err != nil {
return fieldpath.ManagedFields{}, fmt.Errorf("failed to compare live and new objects: %v", err) 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].Set = managers[manager].Set.Union(compare.Modified).Union(compare.Added).Difference(compare.Removed)
managers[manager].APIVersion = version managers[manager].APIVersion = version
if managers[manager].Set.Empty() {
delete(managers, manager)
}
return managers, nil return managers, nil
} }
...@@ -121,22 +128,41 @@ func (s *Updater) Update(liveObject, newObject typed.TypedValue, version fieldpa ...@@ -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) { 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) newObject, err := liveObject.Merge(configObject)
if err != nil { 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) managers, err = s.update(liveObject, newObject, version, managers, manager, force)
if err != nil { 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() set, err := configObject.ToFieldSet()
if err != nil { 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{ managers[manager] = &fieldpath.VersionedSet{
Set: set, Set: set,
APIVersion: version, APIVersion: version,
} }
if managers[manager].Set.Empty() {
delete(managers, manager)
}
return newObject, managers, nil 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
}
...@@ -5,11 +5,13 @@ go_library( ...@@ -5,11 +5,13 @@ go_library(
srcs = [ srcs = [
"doc.go", "doc.go",
"elements.go", "elements.go",
"fromvalue.go",
"schemaschema.go", "schemaschema.go",
], ],
importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/structured-merge-diff/schema", importmap = "k8s.io/kubernetes/vendor/sigs.k8s.io/structured-merge-diff/schema",
importpath = "sigs.k8s.io/structured-merge-diff/schema", importpath = "sigs.k8s.io/structured-merge-diff/schema",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = ["//vendor/sigs.k8s.io/structured-merge-diff/value:go_default_library"],
) )
filegroup( filegroup(
......
...@@ -207,7 +207,7 @@ func (s Schema) FindNamedType(name string) (TypeDef, bool) { ...@@ -207,7 +207,7 @@ func (s Schema) FindNamedType(name string) (TypeDef, bool) {
// //
// This allows callers to not care about the difference between a (possibly // This allows callers to not care about the difference between a (possibly
// inlined) reference and a definition. // 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 { if tr.NamedType != nil {
t, ok := s.FindNamedType(*tr.NamedType) t, ok := s.FindNamedType(*tr.NamedType)
if !ok { if !ok {
......
/*
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{}}
}
...@@ -3,10 +3,12 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") ...@@ -3,10 +3,12 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library( go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"deduced.go",
"doc.go", "doc.go",
"helpers.go", "helpers.go",
"merge.go", "merge.go",
"parser.go", "parser.go",
"remove.go",
"typed.go", "typed.go",
"validate.go", "validate.go",
], ],
......
/*
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
}
...@@ -129,6 +129,9 @@ func (ef errorFormatter) validateScalar(t schema.Scalar, v *value.Value, prefix ...@@ -129,6 +129,9 @@ func (ef errorFormatter) validateScalar(t schema.Scalar, v *value.Value, prefix
if v == nil { if v == nil {
return nil return nil
} }
if v.Null {
return nil
}
switch t { switch t {
case schema.Numeric: case schema.Numeric:
if v.FloatValue == nil && v.IntValue == nil { if v.FloatValue == nil && v.IntValue == nil {
......
...@@ -68,47 +68,80 @@ func (p *Parser) TypeNames() (names []string) { ...@@ -68,47 +68,80 @@ func (p *Parser) TypeNames() (names []string) {
// Type returns a helper which can produce objects of the given type. Any // Type returns a helper which can produce objects of the given type. Any
// errors are deferred until a further function is called. // errors are deferred until a further function is called.
func (p *Parser) Type(name string) *ParseableType { func (p *Parser) Type(name string) ParseableType {
return &ParseableType{ return &parseableType{
parser: p, parser: p,
typename: name, typename: name,
} }
} }
// ParseableType allows for easy production of typed objects. // 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 parser *Parser
typename string typename string
} }
var _ ParseableType = &parseableType{}
// IsValid return true if p's schema and typename are valid. // 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}) _, ok := p.parser.Schema.Resolve(schema.TypeRef{NamedType: &p.typename})
return ok 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 // FromYAML parses a yaml string into an object with the current schema
// and the type "typename" or an error if validation fails. // 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)) v, err := value.FromYAML([]byte(object))
if err != nil { if err != nil {
return TypedValue{}, err return nil, err
} }
return AsTyped(v, &p.parser.Schema, p.typename) return AsTyped(v, &p.parser.Schema, p.typename)
} }
// FromUnstructured converts a go interface to a TypedValue. It will return an // FromUnstructured converts a go interface to a TypedValue. It will return an
// error if the resulting object fails schema validation. // 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) v, err := value.FromUnstructured(in)
if err != nil { if err != nil {
return TypedValue{}, err return nil, err
} }
return AsTyped(v, &p.parser.Schema, p.typename) 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
}
/*
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 }
...@@ -22,7 +22,7 @@ import ( ...@@ -22,7 +22,7 @@ import (
"sigs.k8s.io/structured-merge-diff/value" "sigs.k8s.io/structured-merge-diff/value"
) )
func (tv TypedValue) walker() *validatingObjectWalker { func (tv typedValue) walker() *validatingObjectWalker {
return &validatingObjectWalker{ return &validatingObjectWalker{
value: tv.value, value: tv.value,
schema: tv.schema, schema: tv.schema,
......
...@@ -31,7 +31,7 @@ import ( ...@@ -31,7 +31,7 @@ import (
func FromYAML(input []byte) (Value, error) { func FromYAML(input []byte) (Value, error) {
var decoded interface{} 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 // Special case since the yaml package doesn't accurately
// preserve this. // preserve this.
return Value{Null: true}, nil return Value{Null: true}, nil
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment