Skip to content

Commit c6e603f

Browse files
authored
Merge pull request #185 from kevindelgado/extract-items
Expose ExtractItems as the opposite of RemoveItems
2 parents a4e00e9 + 8eee23c commit c6e603f

File tree

7 files changed

+1414
-14
lines changed

7 files changed

+1414
-14
lines changed

fieldpath/set.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,40 @@ func (s *Set) WithPrefix(pe PathElement) *Set {
206206
return subset
207207
}
208208

209+
// Leaves returns a set containing only the leaf paths
210+
// of a set.
211+
func (s *Set) Leaves() *Set {
212+
leaves := PathElementSet{}
213+
im := 0
214+
ic := 0
215+
216+
// any members that are not also children are leaves
217+
outer:
218+
for im < len(s.Members.members) {
219+
member := s.Members.members[im]
220+
221+
for ic < len(s.Children.members) {
222+
d := member.Compare(s.Children.members[ic].pathElement)
223+
if d == 0 {
224+
ic++
225+
im++
226+
continue outer
227+
} else if d < 0 {
228+
break
229+
} else /* if d > 0 */ {
230+
ic++
231+
}
232+
}
233+
leaves.members = append(leaves.members, member)
234+
im++
235+
}
236+
237+
return &Set{
238+
Members: leaves,
239+
Children: *s.Children.Leaves(),
240+
}
241+
}
242+
209243
// setNode is a pair of PathElement / Set, for the purpose of expressing
210244
// nested set membership.
211245
type setNode struct {
@@ -455,3 +489,17 @@ func (s *SetNodeMap) iteratePrefix(prefix Path, f func(Path)) {
455489
n.set.iteratePrefix(append(prefix, pe), f)
456490
}
457491
}
492+
493+
// Leaves returns a SetNodeMap containing
494+
// only setNodes with leaf PathElements.
495+
func (s *SetNodeMap) Leaves() *SetNodeMap {
496+
out := &SetNodeMap{}
497+
out.members = make(sortedSetNode, len(s.members))
498+
for i, n := range s.members {
499+
out.members[i] = setNode{
500+
pathElement: n.pathElement,
501+
set: n.set.Leaves(),
502+
}
503+
}
504+
return out
505+
}

fieldpath/set_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ func BenchmarkFieldSet(b *testing.B) {
140140
randOperand().RecursiveDifference(randOperand())
141141
}
142142
})
143+
b.Run(fmt.Sprintf("leaves-%v", here.size), func(b *testing.B) {
144+
b.ReportAllocs()
145+
for i := 0; i < b.N; i++ {
146+
randOperand().Leaves()
147+
}
148+
})
143149
}
144150
}
145151

@@ -456,6 +462,106 @@ func TestSetIntersectionDifference(t *testing.T) {
456462
})
457463
}
458464

465+
func TestSetLeaves(t *testing.T) {
466+
table := []struct {
467+
name string
468+
input *Set
469+
expected *Set
470+
}{
471+
{
472+
name: "empty set",
473+
input: NewSet(),
474+
expected: NewSet(),
475+
}, {
476+
name: "all leaves",
477+
input: NewSet(
478+
_P("path1"),
479+
_P("path2"),
480+
_P("path3"),
481+
),
482+
expected: NewSet(
483+
_P("path1"),
484+
_P("path2"),
485+
_P("path3"),
486+
),
487+
}, {
488+
name: "only one leaf",
489+
input: NewSet(
490+
_P("root"),
491+
_P("root", "l1"),
492+
_P("root", "l1", "l2"),
493+
_P("root", "l1", "l2", "l3"),
494+
),
495+
expected: NewSet(
496+
_P("root", "l1", "l2", "l3"),
497+
),
498+
}, {
499+
name: "multiple values, check for overwrite",
500+
input: NewSet(
501+
_P("root", KeyByFields("name", "a")),
502+
_P("root", KeyByFields("name", "a"), "name"),
503+
_P("root", KeyByFields("name", "a"), "value", "b"),
504+
_P("root", KeyByFields("name", "a"), "value", "c"),
505+
),
506+
expected: NewSet(
507+
_P("root", KeyByFields("name", "a"), "name"),
508+
_P("root", KeyByFields("name", "a"), "value", "b"),
509+
_P("root", KeyByFields("name", "a"), "value", "c"),
510+
),
511+
}, {
512+
name: "multiple values and nested",
513+
input: NewSet(
514+
_P("root", KeyByFields("name", "a")),
515+
_P("root", KeyByFields("name", "a"), "name"),
516+
_P("root", KeyByFields("name", "a"), "value", "b"),
517+
_P("root", KeyByFields("name", "a"), "value", "b", "d"),
518+
_P("root", KeyByFields("name", "a"), "value", "c"),
519+
),
520+
expected: NewSet(
521+
_P("root", KeyByFields("name", "a"), "name"),
522+
_P("root", KeyByFields("name", "a"), "value", "b", "d"),
523+
_P("root", KeyByFields("name", "a"), "value", "c"),
524+
),
525+
}, {
526+
name: "all-in-one",
527+
input: NewSet(
528+
_P("root"),
529+
_P("root", KeyByFields("name", "a")),
530+
_P("root", KeyByFields("name", "a"), "name"),
531+
_P("root", KeyByFields("name", "a"), "value", "b"),
532+
_P("root", KeyByFields("name", "a"), "value", "b", "c"),
533+
_P("root", KeyByFields("name", "a"), "value", "d"),
534+
_P("root", KeyByFields("name", "a"), "value", "e"),
535+
_P("root", "x"),
536+
_P("root", "x", "y"),
537+
_P("root", "x", "z"),
538+
_P("root", KeyByFields("name", "p")),
539+
_P("root", KeyByFields("name", "p"), "name"),
540+
_P("root", KeyByFields("name", "p"), "value", "q"),
541+
),
542+
expected: NewSet(
543+
_P("root", KeyByFields("name", "a"), "name"),
544+
_P("root", KeyByFields("name", "a"), "value", "b", "c"),
545+
_P("root", KeyByFields("name", "a"), "value", "d"),
546+
_P("root", KeyByFields("name", "a"), "value", "e"),
547+
_P("root", "x", "y"),
548+
_P("root", "x", "z"),
549+
_P("root", KeyByFields("name", "p"), "name"),
550+
_P("root", KeyByFields("name", "p"), "value", "q"),
551+
),
552+
},
553+
}
554+
555+
for _, tt := range table {
556+
t.Run(tt.name, func(t *testing.T) {
557+
if got := tt.input.Leaves(); !tt.expected.Equals(got) {
558+
t.Errorf("expected %v, got %v for input %v", tt.expected, got, tt.input)
559+
}
560+
})
561+
}
562+
563+
}
564+
459565
func TestSetDifference(t *testing.T) {
460566
table := []struct {
461567
name string

internal/fixture/state.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,83 @@ func (f ForceApplyObject) preprocess(parser Parser) (Operation, error) {
341341
return f, nil
342342
}
343343

344+
// ExtractApply is a type of operation. It simulates extracting an object
345+
// the state based on the manager you have applied with, merging the
346+
// apply object with that extracted object and reapplying that.
347+
type ExtractApply struct {
348+
Manager string
349+
APIVersion fieldpath.APIVersion
350+
Object typed.YAMLObject
351+
}
352+
353+
var _ Operation = &ExtractApply{}
354+
355+
func (e ExtractApply) run(state *State) error {
356+
p, err := e.preprocess(state.Parser)
357+
if err != nil {
358+
return err
359+
}
360+
return p.run(state)
361+
}
362+
363+
func (e ExtractApply) preprocess(parser Parser) (Operation, error) {
364+
365+
tv, err := parser.Type(string(e.APIVersion)).FromYAML(FixTabsOrDie(e.Object))
366+
if err != nil {
367+
return nil, err
368+
}
369+
return ExtractApplyObject{
370+
Manager: e.Manager,
371+
APIVersion: e.APIVersion,
372+
Object: tv,
373+
}, nil
374+
}
375+
376+
type ExtractApplyObject struct {
377+
Manager string
378+
APIVersion fieldpath.APIVersion
379+
Object *typed.TypedValue
380+
}
381+
382+
var _ Operation = &ExtractApplyObject{}
383+
384+
func (e ExtractApplyObject) run(state *State) error {
385+
if state.Live == nil {
386+
return state.ApplyObject(e.Object, e.APIVersion, e.Manager, true)
387+
}
388+
// Get object from state and convert it to current APIVersion
389+
current, err := state.Updater.Converter.Convert(state.Live, e.APIVersion)
390+
if err != nil {
391+
return err
392+
}
393+
// Get set based on the manager you've applied with
394+
set := fieldpath.NewSet()
395+
mgr := state.Managers[e.Manager]
396+
if mgr != nil {
397+
// we cannot extract a set that is for a different version
398+
if mgr.APIVersion() != e.APIVersion {
399+
return fmt.Errorf("existing managed fieldpath set APIVersion (%s) differs from desired (%s), unable to extract", mgr.APIVersion(), e.APIVersion)
400+
}
401+
// trying to extract the fieldSet directly will return everything
402+
// under the first path in the set, so we must filter out all
403+
// the non-leaf nodes from the fieldSet
404+
set = mgr.Set().Leaves()
405+
}
406+
// ExtractFields from the state object based on the set
407+
extracted := current.ExtractItems(set)
408+
// Merge ApplyObject on top of the extracted object
409+
obj, err := extracted.Merge(e.Object)
410+
if err != nil {
411+
return err
412+
}
413+
// Reapply that to the state
414+
return state.ApplyObject(obj, e.APIVersion, e.Manager, true)
415+
}
416+
417+
func (e ExtractApplyObject) preprocess(parser Parser) (Operation, error) {
418+
return e, nil
419+
}
420+
344421
// Update is a type of operation. It is a controller type of
345422
// update. Errors are passed along.
346423
type Update struct {

0 commit comments

Comments
 (0)