Skip to content

Commit 723667d

Browse files
authored
feat: support exiting early from IterateHierarchy method (argoproj#388)
* feat: support existing early from IterateHierarchy method Signed-off-by: Alexander Matyushentsev <[email protected]> * reviewer notes: comment action callback return value; add missing return value check Signed-off-by: Alexander Matyushentsev <[email protected]>
1 parent 531c0db commit 723667d

File tree

5 files changed

+92
-20
lines changed

5 files changed

+92
-20
lines changed

pkg/cache/cluster.go

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,9 @@ type ClusterCache interface {
100100
Invalidate(opts ...UpdateSettingsFunc)
101101
// FindResources returns resources that matches given list of predicates from specified namespace or everywhere if specified namespace is empty
102102
FindResources(namespace string, predicates ...func(r *Resource) bool) map[kube.ResourceKey]*Resource
103-
// IterateHierarchy iterates resource tree starting from the specified top level resource and executes callback for each resource in the tree
104-
IterateHierarchy(key kube.ResourceKey, action func(resource *Resource, namespaceResources map[kube.ResourceKey]*Resource))
103+
// IterateHierarchy iterates resource tree starting from the specified top level resource and executes callback for each resource in the tree.
104+
// The action callback returns true if iteration should continue and false otherwise.
105+
IterateHierarchy(key kube.ResourceKey, action func(resource *Resource, namespaceResources map[kube.ResourceKey]*Resource) bool)
105106
// IsNamespaced answers if specified group/kind is a namespaced resource API or not
106107
IsNamespaced(gk schema.GroupKind) (bool, error)
107108
// GetManagedLiveObjs helps finding matching live K8S resources for a given resources list.
@@ -823,12 +824,14 @@ func (c *clusterCache) FindResources(namespace string, predicates ...func(r *Res
823824
}
824825

825826
// IterateHierarchy iterates resource tree starting from the specified top level resource and executes callback for each resource in the tree
826-
func (c *clusterCache) IterateHierarchy(key kube.ResourceKey, action func(resource *Resource, namespaceResources map[kube.ResourceKey]*Resource)) {
827+
func (c *clusterCache) IterateHierarchy(key kube.ResourceKey, action func(resource *Resource, namespaceResources map[kube.ResourceKey]*Resource) bool) {
827828
c.lock.RLock()
828829
defer c.lock.RUnlock()
829830
if res, ok := c.resources[key]; ok {
830831
nsNodes := c.nsIndex[key.Namespace]
831-
action(res, nsNodes)
832+
if !action(res, nsNodes) {
833+
return
834+
}
832835
childrenByUID := make(map[types.UID][]*Resource)
833836
for _, child := range nsNodes {
834837
if res.isParentOf(child) {
@@ -846,14 +849,15 @@ func (c *clusterCache) IterateHierarchy(key kube.ResourceKey, action func(resour
846849
return strings.Compare(key1.String(), key2.String()) < 0
847850
})
848851
child := children[0]
849-
action(child, nsNodes)
850-
child.iterateChildren(nsNodes, map[kube.ResourceKey]bool{res.ResourceKey(): true}, func(err error, child *Resource, namespaceResources map[kube.ResourceKey]*Resource) {
851-
if err != nil {
852-
c.log.V(2).Info(err.Error())
853-
return
854-
}
855-
action(child, namespaceResources)
856-
})
852+
if action(child, nsNodes) {
853+
child.iterateChildren(nsNodes, map[kube.ResourceKey]bool{res.ResourceKey(): true}, func(err error, child *Resource, namespaceResources map[kube.ResourceKey]*Resource) bool {
854+
if err != nil {
855+
c.log.V(2).Info(err.Error())
856+
return false
857+
}
858+
return action(child, namespaceResources)
859+
})
860+
}
857861
}
858862
}
859863
}

pkg/cache/cluster_test.go

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,9 @@ func newCluster(t *testing.T, objs ...runtime.Object) *clusterCache {
109109

110110
func getChildren(cluster *clusterCache, un *unstructured.Unstructured) []*Resource {
111111
hierarchy := make([]*Resource, 0)
112-
cluster.IterateHierarchy(kube.GetResourceKey(un), func(child *Resource, _ map[kube.ResourceKey]*Resource) {
112+
cluster.IterateHierarchy(kube.GetResourceKey(un), func(child *Resource, _ map[kube.ResourceKey]*Resource) bool {
113113
hierarchy = append(hierarchy, child)
114+
return true
114115
})
115116
return hierarchy[1:]
116117
}
@@ -796,3 +797,68 @@ func testDeploy() *appsv1.Deployment {
796797
},
797798
}
798799
}
800+
801+
func TestIterateHierachy(t *testing.T) {
802+
cluster := newCluster(t, testPod(), testRS(), testDeploy())
803+
err := cluster.EnsureSynced()
804+
require.NoError(t, err)
805+
806+
t.Run("IterateAll", func(t *testing.T) {
807+
keys := []kube.ResourceKey{}
808+
cluster.IterateHierarchy(kube.GetResourceKey(mustToUnstructured(testDeploy())), func(child *Resource, _ map[kube.ResourceKey]*Resource) bool {
809+
keys = append(keys, child.ResourceKey())
810+
return true
811+
})
812+
813+
assert.ElementsMatch(t,
814+
[]kube.ResourceKey{
815+
kube.GetResourceKey(mustToUnstructured(testPod())),
816+
kube.GetResourceKey(mustToUnstructured(testRS())),
817+
kube.GetResourceKey(mustToUnstructured(testDeploy()))},
818+
keys)
819+
})
820+
821+
t.Run("ExitAtRoot", func(t *testing.T) {
822+
keys := []kube.ResourceKey{}
823+
cluster.IterateHierarchy(kube.GetResourceKey(mustToUnstructured(testDeploy())), func(child *Resource, _ map[kube.ResourceKey]*Resource) bool {
824+
keys = append(keys, child.ResourceKey())
825+
return false
826+
})
827+
828+
assert.ElementsMatch(t,
829+
[]kube.ResourceKey{
830+
kube.GetResourceKey(mustToUnstructured(testDeploy()))},
831+
keys)
832+
})
833+
834+
t.Run("ExitAtSecondLevelChild", func(t *testing.T) {
835+
keys := []kube.ResourceKey{}
836+
cluster.IterateHierarchy(kube.GetResourceKey(mustToUnstructured(testDeploy())), func(child *Resource, _ map[kube.ResourceKey]*Resource) bool {
837+
keys = append(keys, child.ResourceKey())
838+
return child.ResourceKey().Kind != kube.ReplicaSetKind
839+
})
840+
841+
assert.ElementsMatch(t,
842+
[]kube.ResourceKey{
843+
kube.GetResourceKey(mustToUnstructured(testDeploy())),
844+
kube.GetResourceKey(mustToUnstructured(testRS())),
845+
},
846+
keys)
847+
})
848+
849+
t.Run("ExitAtThirdLevelChild", func(t *testing.T) {
850+
keys := []kube.ResourceKey{}
851+
cluster.IterateHierarchy(kube.GetResourceKey(mustToUnstructured(testDeploy())), func(child *Resource, _ map[kube.ResourceKey]*Resource) bool {
852+
keys = append(keys, child.ResourceKey())
853+
return child.ResourceKey().Kind != kube.PodKind
854+
})
855+
856+
assert.ElementsMatch(t,
857+
[]kube.ResourceKey{
858+
kube.GetResourceKey(mustToUnstructured(testDeploy())),
859+
kube.GetResourceKey(mustToUnstructured(testRS())),
860+
kube.GetResourceKey(mustToUnstructured(testPod())),
861+
},
862+
keys)
863+
})
864+
}

pkg/cache/mocks/ClusterCache.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/cache/predicates_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,9 @@ func ExampleNewClusterCache_inspectNamespaceResources() {
115115
}
116116
// Iterate default namespace resources tree
117117
for _, root := range clusterCache.FindResources("default", TopLevelResource) {
118-
clusterCache.IterateHierarchy(root.ResourceKey(), func(resource *Resource, _ map[kube.ResourceKey]*Resource) {
118+
clusterCache.IterateHierarchy(root.ResourceKey(), func(resource *Resource, _ map[kube.ResourceKey]*Resource) bool {
119119
fmt.Printf("resource: %s, info: %v\n", resource.Ref.String(), resource.Info)
120+
return true
120121
})
121122
}
122123
}

pkg/cache/resource.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,16 @@ func newResourceKeySet(set map[kube.ResourceKey]bool, keys ...kube.ResourceKey)
8585
return newSet
8686
}
8787

88-
func (r *Resource) iterateChildren(ns map[kube.ResourceKey]*Resource, parents map[kube.ResourceKey]bool, action func(err error, child *Resource, namespaceResources map[kube.ResourceKey]*Resource)) {
88+
func (r *Resource) iterateChildren(ns map[kube.ResourceKey]*Resource, parents map[kube.ResourceKey]bool, action func(err error, child *Resource, namespaceResources map[kube.ResourceKey]*Resource) bool) {
8989
for childKey, child := range ns {
9090
if r.isParentOf(ns[childKey]) {
9191
if parents[childKey] {
9292
key := r.ResourceKey()
93-
action(fmt.Errorf("circular dependency detected. %s is child and parent of %s", childKey.String(), key.String()), child, ns)
93+
_ = action(fmt.Errorf("circular dependency detected. %s is child and parent of %s", childKey.String(), key.String()), child, ns)
9494
} else {
95-
action(nil, child, ns)
96-
child.iterateChildren(ns, newResourceKeySet(parents, r.ResourceKey()), action)
95+
if action(nil, child, ns) {
96+
child.iterateChildren(ns, newResourceKeySet(parents, r.ResourceKey()), action)
97+
}
9798
}
9899
}
99100
}

0 commit comments

Comments
 (0)