Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions fieldpath/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,53 @@ 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 recursively removes
// all children from s
// that are either children or members in s2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we align this comment more with the one for Difference? I'm having a hard time understanding the difference (no pun intended). Feel free to adjust both comments, the existing one isn't amazingly clear.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check again?

The difference is, that a Difference only respects fields that are directly specified.
So to remove a.b.c and a.b.d from s, both need to be in s2.
But RecursiveDifference behaves more like deleting a folder that contains folders. It's enough to specify a in s2 and it's members are deleted recursively, or in the case of RecursiveDifference not added to the result.

I tried a few different explanations, but none was really good. I'm open for suggestions :-/

func (s *Set) RecursiveDifference(s2 *Set) *Set {
out := &Set{
Members: *s.Members.Difference(&s2.Members),
}

i, j := 0, 0
for i < len(s.Children.members) && j < len(s2.Children.members) {
if s.Children.members[i].pathElement.Less(s2.Children.members[j].pathElement) {
if !s2.Members.Has(s.Children.members[i].pathElement) {
out.Children.members = append(out.Children.members, setNode{
pathElement: s.Children.members[i].pathElement,
set: s.Children.members[i].set,
})
}
i++
} else {
if !s2.Children.members[j].pathElement.Less(s.Children.members[i].pathElement) {
if !s2.Members.Has(s.Children.members[i].pathElement) {
diff := s.Children.members[i].set.RecursiveDifference(s2.Children.members[j].set)
if !diff.Empty() {
out.Children.members = append(out.Children.members, setNode{pathElement: s.Children.members[i].pathElement, set: diff})
}
}
i++
}
j++
}
}

if i < len(s.Children.members) {
for _, c := range s.Children.members[i:] {
if !s2.Members.Has(c.pathElement) {
out.Children.members = append(out.Children.members, c)
}
}
}

return out
}

// Size returns the number of members of the set.
func (s *Set) Size() int {
return s.Members.Size() + s.Children.Size()
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/json-iterator/go v1.1.6
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/stretchr/testify v1.3.0 // indirect
)

go 1.13
3 changes: 1 addition & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,4 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
sigs.k8s.io/structured-merge-diff v1.0.1 h1:LOs1LZWMsz1xs77Phr/pkB4LFaavH7IVq/3+WTN9XTA=
sigs.k8s.io/structured-merge-diff v1.0.1/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
sigs.k8s.io/structured-merge-diff v1.0.2 h1:WiMoyniAVAYm03w+ImfF9IE2G23GLR/SwDnQyaNZvPk=
166 changes: 123 additions & 43 deletions internal/fixture/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (s *State) checkInit(version fieldpath.APIVersion) error {
return nil
}

func (s *State) UpdateObject(tv *typed.TypedValue, version fieldpath.APIVersion, manager string) error {
func (s *State) UpdateObject(tv *typed.TypedValue, version fieldpath.APIVersion, ignored *fieldpath.Set, manager string) error {
err := s.checkInit(version)
if err != nil {
return err
Expand All @@ -118,7 +118,7 @@ func (s *State) UpdateObject(tv *typed.TypedValue, version fieldpath.APIVersion,
if err != nil {
return err
}
newObj, managers, err := s.Updater.Update(s.Live, tv, version, s.Managers, manager)
newObj, managers, err := s.Updater.Update(s.Live, tv, version, s.Managers, ignored, manager)
if err != nil {
return err
}
Expand All @@ -129,15 +129,15 @@ func (s *State) UpdateObject(tv *typed.TypedValue, version fieldpath.APIVersion,
}

// Update the current state with the passed in object
func (s *State) Update(obj typed.YAMLObject, version fieldpath.APIVersion, manager string) error {
func (s *State) Update(obj typed.YAMLObject, version fieldpath.APIVersion, ignored *fieldpath.Set, manager string) error {
tv, err := s.Parser.Type(string(version)).FromYAML(FixTabsOrDie(obj))
if err != nil {
return err
}
return s.UpdateObject(tv, version, manager)
return s.UpdateObject(tv, version, ignored, manager)
}

func (s *State) ApplyObject(tv *typed.TypedValue, version fieldpath.APIVersion, manager string, force bool) error {
func (s *State) ApplyObject(tv *typed.TypedValue, version fieldpath.APIVersion, ignored *fieldpath.Set, manager string, force bool) error {
err := s.checkInit(version)
if err != nil {
return err
Expand All @@ -146,7 +146,7 @@ func (s *State) ApplyObject(tv *typed.TypedValue, version fieldpath.APIVersion,
if err != nil {
return err
}
new, managers, err := s.Updater.Apply(s.Live, tv, version, s.Managers, manager, force)
new, managers, err := s.Updater.Apply(s.Live, tv, version, s.Managers, ignored, manager, force)
if err != nil {
return err
}
Expand All @@ -158,12 +158,12 @@ func (s *State) ApplyObject(tv *typed.TypedValue, version fieldpath.APIVersion,
}

// Apply the passed in object to the current state
func (s *State) Apply(obj typed.YAMLObject, version fieldpath.APIVersion, manager string, force bool) error {
func (s *State) Apply(obj typed.YAMLObject, version fieldpath.APIVersion, ignored *fieldpath.Set, manager string, force bool) error {
tv, err := s.Parser.Type(string(version)).FromYAML(FixTabsOrDie(obj))
if err != nil {
return err
}
return s.ApplyObject(tv, version, manager, force)
return s.ApplyObject(tv, version, ignored, manager, force)
}

// CompareLive takes a YAML string and returns the comparison with the
Expand Down Expand Up @@ -231,10 +231,11 @@ func addedConflicts(one, other merge.Conflicts) merge.Conflicts {
// conflict, the user can specify the expected conflicts. If conflicts
// don't match, an error will occur.
type Apply struct {
Manager string
APIVersion fieldpath.APIVersion
Object typed.YAMLObject
Conflicts merge.Conflicts
Manager string
APIVersion fieldpath.APIVersion
Object typed.YAMLObject
Conflicts merge.Conflicts
IgnoredFields *fieldpath.Set
}

var _ Operation = &Apply{}
Expand All @@ -253,24 +254,26 @@ func (a Apply) preprocess(parser Parser) (Operation, error) {
return nil, err
}
return ApplyObject{
Manager: a.Manager,
APIVersion: a.APIVersion,
Object: tv,
Conflicts: a.Conflicts,
Manager: a.Manager,
APIVersion: a.APIVersion,
Object: tv,
Conflicts: a.Conflicts,
IgnoredFields: a.IgnoredFields,
}, nil
}

type ApplyObject struct {
Manager string
APIVersion fieldpath.APIVersion
Object *typed.TypedValue
Conflicts merge.Conflicts
Manager string
APIVersion fieldpath.APIVersion
Object *typed.TypedValue
Conflicts merge.Conflicts
IgnoredFields *fieldpath.Set
}

var _ Operation = &ApplyObject{}

func (a ApplyObject) run(state *State) error {
err := state.ApplyObject(a.Object, a.APIVersion, a.Manager, false)
err := state.ApplyObject(a.Object, a.APIVersion, a.IgnoredFields, a.Manager, false)
if err != nil {
if _, ok := err.(merge.Conflicts); !ok || a.Conflicts == nil {
return err
Expand Down Expand Up @@ -300,15 +303,16 @@ func (a ApplyObject) preprocess(parser Parser) (Operation, error) {
// ForceApply is a type of operation. It is a forced-apply run by a
// manager with a given object. Any error will be returned.
type ForceApply struct {
Manager string
APIVersion fieldpath.APIVersion
Object typed.YAMLObject
Manager string
APIVersion fieldpath.APIVersion
Object typed.YAMLObject
IgnoredFields *fieldpath.Set
}

var _ Operation = &ForceApply{}

func (f ForceApply) run(state *State) error {
return state.Apply(f.Object, f.APIVersion, f.Manager, true)
return state.Apply(f.Object, f.APIVersion, f.IgnoredFields, f.Manager, true)
}

func (f ForceApply) preprocess(parser Parser) (Operation, error) {
Expand All @@ -317,24 +321,26 @@ func (f ForceApply) preprocess(parser Parser) (Operation, error) {
return nil, err
}
return ForceApplyObject{
Manager: f.Manager,
APIVersion: f.APIVersion,
Object: tv,
Manager: f.Manager,
APIVersion: f.APIVersion,
Object: tv,
IgnoredFields: f.IgnoredFields,
}, nil
}

// ForceApplyObject is a type of operation. It is a forced-apply run by
// a manager with a given object. Any error will be returned.
type ForceApplyObject struct {
Manager string
APIVersion fieldpath.APIVersion
Object *typed.TypedValue
Manager string
APIVersion fieldpath.APIVersion
Object *typed.TypedValue
IgnoredFields *fieldpath.Set
}

var _ Operation = &ForceApplyObject{}

func (f ForceApplyObject) run(state *State) error {
return state.ApplyObject(f.Object, f.APIVersion, f.Manager, true)
return state.ApplyObject(f.Object, f.APIVersion, f.IgnoredFields, f.Manager, true)
}

func (f ForceApplyObject) preprocess(parser Parser) (Operation, error) {
Expand All @@ -344,15 +350,16 @@ func (f ForceApplyObject) preprocess(parser Parser) (Operation, error) {
// Update is a type of operation. It is a controller type of
// update. Errors are passed along.
type Update struct {
Manager string
APIVersion fieldpath.APIVersion
Object typed.YAMLObject
Manager string
APIVersion fieldpath.APIVersion
Object typed.YAMLObject
IgnoredFields *fieldpath.Set
}

var _ Operation = &Update{}

func (u Update) run(state *State) error {
return state.Update(u.Object, u.APIVersion, u.Manager)
return state.Update(u.Object, u.APIVersion, u.IgnoredFields, u.Manager)
}

func (u Update) preprocess(parser Parser) (Operation, error) {
Expand All @@ -361,24 +368,26 @@ func (u Update) preprocess(parser Parser) (Operation, error) {
return nil, err
}
return UpdateObject{
Manager: u.Manager,
APIVersion: u.APIVersion,
Object: tv,
Manager: u.Manager,
APIVersion: u.APIVersion,
Object: tv,
IgnoredFields: u.IgnoredFields,
}, nil
}

// UpdateObject is a type of operation. It is a controller type of
// update. Errors are passed along.
type UpdateObject struct {
Manager string
APIVersion fieldpath.APIVersion
Object *typed.TypedValue
Manager string
APIVersion fieldpath.APIVersion
Object *typed.TypedValue
IgnoredFields *fieldpath.Set
}

var _ Operation = &Update{}

func (u UpdateObject) run(state *State) error {
return state.UpdateObject(u.Object, u.APIVersion, u.Manager)
return state.UpdateObject(u.Object, u.APIVersion, u.IgnoredFields, u.Manager)
}

func (f UpdateObject) preprocess(parser Parser) (Operation, error) {
Expand Down Expand Up @@ -505,3 +514,74 @@ func (tc TestCase) TestWithConverter(parser Parser, converter merge.Converter) e

return nil
}

// PrintState is an Operation printing the current state to help with debugging tests
type PrintState struct{}

var _ Operation = PrintState{}

func (op PrintState) run(s *State) error {
fmt.Println(value.ToString(s.Live.AsValue()))
return nil
}

func (op PrintState) preprocess(_ Parser) (Operation, error) {
return op, nil
}

// ExpectState is an Operation comparing the current state to the defined config to help with debugging tests
type ExpectState struct {
APIVersion fieldpath.APIVersion
Object typed.YAMLObject
}

var _ Operation = ExpectState{}

func (op ExpectState) run(state *State) error {
comparison, err := state.CompareLive(op.Object, op.APIVersion)
if err != nil {
return fmt.Errorf("failed to compare live with config: %v", err)
}
if !comparison.IsSame() {
config, err := state.Parser.Type(string(op.APIVersion)).FromYAML(FixTabsOrDie(op.Object))
if err != nil {
return fmt.Errorf("failed to parse config: %w", err)
}
return fmt.Errorf("expected live and config to be the same:\n%v\nConfig: %v\nLive: %v\n", comparison, value.ToString(config.AsValue()), value.ToString(state.Live.AsValue()))
}
return nil
}

func (op ExpectState) preprocess(parser Parser) (Operation, error) {
return op, nil
}

// ExpectManagedFields is an Operation checking if the manager owns the defined fields in the current state
// If the Fields are nil, it won't be an error if the manager is missing
type ExpectManagedFields struct {
Manager string
Fields *fieldpath.Set
}

var _ Operation = ExpectManagedFields{}

func (op ExpectManagedFields) run(state *State) error {
manager, ok := state.Managers[op.Manager]
if !ok {
if op.Fields == nil {
return nil
}
return fmt.Errorf("manager not found: %s", op.Manager)
}
if op.Fields == nil {
op.Fields = fieldpath.NewSet()
}
if diff := manager.Set().Difference(op.Fields); !diff.Empty() {
return fmt.Errorf("unexpected managedFields for %s: \n%v", op.Manager, diff)
}
return nil
}

func (op ExpectManagedFields) preprocess(parser Parser) (Operation, error) {
return op, nil
}
Loading