Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
46 changes: 41 additions & 5 deletions pkg/template/filter.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package template

import "k8s.io/apimachinery/pkg/runtime"
import (
"fmt"

"k8s.io/apimachinery/pkg/runtime"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
)

var (
// RetainNamespaces a func to retain only namespaces
Expand All @@ -20,17 +25,48 @@ var (
type FilterFunc func(runtime.RawExtension) bool

// Filter filters the given objs to return only those matching the given filters (if any)
func Filter(objs []runtime.RawExtension, filters ...FilterFunc) []runtime.RawExtension {
result := make([]runtime.RawExtension, 0, len(objs))
// Accepts []runtime.RawExtension, []runtime.Object, or []runtimeclient.Object
func Filter(objs interface{}, filters ...FilterFunc) []runtime.RawExtension {
// Convert input to []runtime.RawExtension
var rawExtensions []runtime.RawExtension

switch v := objs.(type) {
case []runtime.RawExtension:
rawExtensions = v
case []runtime.Object, []runtimeclient.Object:
// Handle both runtime.Object and runtimeclient.Object the same way
// since runtimeclient.Object embeds runtime.Object
var objects []runtime.Object
if runtimeObjs, ok := v.([]runtime.Object); ok {
objects = runtimeObjs
Comment on lines +28 to +41
Copy link
Contributor

Choose a reason for hiding this comment

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

Using interface{} looks like an overkill and similar to a python code - let's stay with Golang code and the standard approach to keep the code sane.
Also, based on the latest changes changes in the other PR, do we actually need to support the Object types?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yea i was actually thinking the same, this PR is now not needed , closing it

} else {
// Convert []runtimeclient.Object to []runtime.Object
clientObjs := v.([]runtimeclient.Object)
objects = make([]runtime.Object, len(clientObjs))
for i, obj := range clientObjs {
objects[i] = obj
}
}

rawExtensions = make([]runtime.RawExtension, len(objects))
for i, obj := range objects {
rawExtensions[i] = runtime.RawExtension{Object: obj}
}
default:
panic(fmt.Sprintf("unsupported type %T for Filter function. Supported types: []runtime.RawExtension, []runtime.Object, []runtimeclient.Object", objs))
}

// Apply filters - single implementation for all types
result := make([]runtime.RawExtension, 0, len(rawExtensions))
loop:
for _, obj := range objs {
for _, obj := range rawExtensions {
for _, filter := range filters {
if !filter(obj) {
continue loop
}

}
result = append(result, obj)
}

return result
}
165 changes: 165 additions & 0 deletions pkg/template/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
)

func TestFilter(t *testing.T) {
Expand Down Expand Up @@ -216,3 +217,167 @@ func TestFilter(t *testing.T) {
})
})
}

func TestFilterWithRuntimeObjects(t *testing.T) {
ns1 := &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "Namespace",
"metadata": map[string]interface{}{
"name": "ns1",
},
},
}
ns2 := &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "Namespace",
"metadata": map[string]interface{}{
"name": "ns2",
},
},
}
rb1 := &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "RoleBinding",
"metadata": map[string]interface{}{
"name": "rb1",
},
},
}
rb2 := &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "RoleBinding",
"metadata": map[string]interface{}{
"name": "rb2",
},
},
}

t.Run("filter runtime.Object slice with no filter", func(t *testing.T) {
// given
objs := []runtime.Object{ns1, rb1, ns2, rb2}

// when
result := template.Filter(objs)

// then
require.Len(t, result, 4)
assert.Equal(t, ns1, result[0].Object)
assert.Equal(t, rb1, result[1].Object)
assert.Equal(t, ns2, result[2].Object)
assert.Equal(t, rb2, result[3].Object)
})

t.Run("filter runtime.Object slice retaining namespaces", func(t *testing.T) {
// given
objs := []runtime.Object{ns1, rb1, ns2, rb2}

// when
result := template.Filter(objs, template.RetainNamespaces)

// then
require.Len(t, result, 2)
assert.Equal(t, ns1, result[0].Object)
assert.Equal(t, ns2, result[1].Object)
})

t.Run("filter runtime.Object slice retaining all but namespaces", func(t *testing.T) {
// given
objs := []runtime.Object{ns1, rb1, ns2, rb2}

// when
result := template.Filter(objs, template.RetainAllButNamespaces)

// then
require.Len(t, result, 2)
assert.Equal(t, rb1, result[0].Object)
assert.Equal(t, rb2, result[1].Object)
})
}

func TestFilterWithClientObjects(t *testing.T) {
ns1 := &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "Namespace",
"metadata": map[string]interface{}{
"name": "ns1",
},
},
}
ns2 := &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "Namespace",
"metadata": map[string]interface{}{
"name": "ns2",
},
},
}
rb1 := &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "RoleBinding",
"metadata": map[string]interface{}{
"name": "rb1",
},
},
}
rb2 := &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "RoleBinding",
"metadata": map[string]interface{}{
"name": "rb2",
},
},
}

t.Run("filter runtimeclient.Object slice with no filter", func(t *testing.T) {
// given
objs := []runtimeclient.Object{ns1, rb1, ns2, rb2}

// when
result := template.Filter(objs)

// then
require.Len(t, result, 4)
assert.Equal(t, ns1, result[0].Object)
assert.Equal(t, rb1, result[1].Object)
assert.Equal(t, ns2, result[2].Object)
assert.Equal(t, rb2, result[3].Object)
})

t.Run("filter runtimeclient.Object slice retaining namespaces", func(t *testing.T) {
// given
objs := []runtimeclient.Object{ns1, rb1, ns2, rb2}

// when
result := template.Filter(objs, template.RetainNamespaces)

// then
require.Len(t, result, 2)
assert.Equal(t, ns1, result[0].Object)
assert.Equal(t, ns2, result[1].Object)
})

t.Run("filter runtimeclient.Object slice retaining all but namespaces", func(t *testing.T) {
// given
objs := []runtimeclient.Object{ns1, rb1, ns2, rb2}

// when
result := template.Filter(objs, template.RetainAllButNamespaces)

// then
require.Len(t, result, 2)
assert.Equal(t, rb1, result[0].Object)
assert.Equal(t, rb2, result[1].Object)
})
}

func TestFilterUnsupportedType(t *testing.T) {
t.Run("panic on unsupported type", func(t *testing.T) {
// given
unsupportedSlice := []string{"not", "supported"}

// when/then
assert.Panics(t, func() {
template.Filter(unsupportedSlice)
})
})
}
Loading