Skip to content

Commit c0b1fee

Browse files
committed
Extend metautils with various utility functions
Implement the following functions: * `SetAnnotation` * `SetAnnotations` * `SetLabel` * `SetLabels` * `EachListItem` * `NewListForObject` * `NewListForGVK`
1 parent fe278ce commit c0b1fee

File tree

4 files changed

+263
-0
lines changed

4 files changed

+263
-0
lines changed

metautils/metautils.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,3 +298,90 @@ func SetList(list client.ObjectList, objects []client.Object) error {
298298
func MustSetList(list client.ObjectList, objects []client.Object) {
299299
utilruntime.Must(SetList(list, objects))
300300
}
301+
302+
// NewListForGVK creates a new client.ObjectList for the given singular schema.GroupVersionKind.
303+
func NewListForGVK(scheme *runtime.Scheme, gvk schema.GroupVersionKind) (client.ObjectList, error) {
304+
// This is considered to be good-enough (used across controller-runtime).
305+
gvk = gvk.GroupVersion().WithKind(gvk.Kind + "List")
306+
obj, err := scheme.New(gvk)
307+
if err != nil {
308+
return nil, fmt.Errorf("error creating list for %s: %w", gvk, err)
309+
}
310+
311+
list, ok := obj.(client.ObjectList)
312+
if !ok {
313+
return nil, fmt.Errorf("object %T does not implement client.ObjectList", obj)
314+
}
315+
316+
return list, nil
317+
}
318+
319+
// NewListForObject creates a new client.ObjectList for the given singular client.Object.
320+
func NewListForObject(scheme *runtime.Scheme, obj client.Object) (client.ObjectList, error) {
321+
gvk, err := apiutil.GVKForObject(obj, scheme)
322+
if err != nil {
323+
return nil, err
324+
}
325+
326+
return NewListForGVK(scheme, gvk)
327+
}
328+
329+
// EachListItem traverses over all items of the client.ObjectList.
330+
func EachListItem(list client.ObjectList, f func(obj client.Object) error) error {
331+
return meta.EachListItem(list, func(rObj runtime.Object) error {
332+
obj, ok := rObj.(client.Object)
333+
if !ok {
334+
return fmt.Errorf("object %T does not implement client.Object", rObj)
335+
}
336+
337+
return f(obj)
338+
})
339+
}
340+
341+
// SetLabel sets the given label on the object.
342+
func SetLabel(obj metav1.Object, key, value string) {
343+
labels := obj.GetLabels()
344+
if labels == nil {
345+
labels = make(map[string]string)
346+
}
347+
348+
labels[key] = value
349+
obj.SetLabels(labels)
350+
}
351+
352+
// SetLabels sets the given labels on the object.
353+
func SetLabels(obj metav1.Object, set map[string]string) {
354+
labels := obj.GetLabels()
355+
if labels == nil {
356+
labels = make(map[string]string)
357+
}
358+
359+
for k, v := range set {
360+
labels[k] = v
361+
}
362+
obj.SetLabels(labels)
363+
}
364+
365+
// SetAnnotation sets the given annotation on the object.
366+
func SetAnnotation(obj metav1.Object, key, value string) {
367+
annotations := obj.GetAnnotations()
368+
if annotations == nil {
369+
annotations = make(map[string]string)
370+
}
371+
372+
annotations[key] = value
373+
obj.SetAnnotations(annotations)
374+
}
375+
376+
// SetAnnotations sets the given annotations on the object.
377+
func SetAnnotations(obj metav1.Object, set map[string]string) {
378+
annotations := obj.GetAnnotations()
379+
if annotations == nil {
380+
annotations = make(map[string]string)
381+
}
382+
383+
for k, v := range set {
384+
annotations[k] = v
385+
}
386+
obj.SetAnnotations(annotations)
387+
}

metautils/metautils_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,17 @@ package metautils_test
1717
import (
1818
"reflect"
1919

20+
"github.com/golang/mock/gomock"
2021
. "github.com/onmetal/controller-utils/metautils"
22+
mockmetautils "github.com/onmetal/controller-utils/mock/controller-utils/metautils"
2123
. "github.com/onsi/ginkgo/v2"
2224
. "github.com/onsi/gomega"
2325
appsv1 "k8s.io/api/apps/v1"
2426
corev1 "k8s.io/api/core/v1"
2527
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2628
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2729
"k8s.io/apimachinery/pkg/runtime"
30+
"k8s.io/apimachinery/pkg/runtime/schema"
2831
"k8s.io/apimachinery/pkg/types"
2932
"k8s.io/client-go/kubernetes/scheme"
3033
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -44,6 +47,14 @@ func (b *BadList) DeepCopyObject() runtime.Object {
4447
}
4548

4649
var _ = Describe("Metautils", func() {
50+
var (
51+
ctrl *gomock.Controller
52+
)
53+
BeforeEach(func() {
54+
ctrl = gomock.NewController(GinkgoT())
55+
DeferCleanup(ctrl.Finish)
56+
})
57+
4758
Describe("ListElementType", func() {
4859
It("should return the element type of an object list", func() {
4960
t, err := ListElementType(&appsv1.DeploymentList{})
@@ -451,4 +462,97 @@ var _ = Describe("Metautils", func() {
451462
Expect(func() { MustSetObjectSlice(1, nil) }).To(Panic())
452463
})
453464
})
465+
466+
Describe("NewListForGVK", func() {
467+
It("should create a new list for the given gvk", func() {
468+
list, err := NewListForGVK(scheme.Scheme, corev1.SchemeGroupVersion.WithKind("Secret"))
469+
Expect(err).NotTo(HaveOccurred())
470+
471+
Expect(list).To(Equal(&corev1.SecretList{}))
472+
})
473+
474+
It("should error if it cannot instantiate the list", func() {
475+
_, err := NewListForGVK(scheme.Scheme, schema.GroupVersionKind{})
476+
Expect(err).To(HaveOccurred())
477+
})
478+
})
479+
480+
Describe("NewListForObject", func() {
481+
It("should create a new list for the given object", func() {
482+
list, err := NewListForObject(scheme.Scheme, &corev1.Secret{})
483+
Expect(err).NotTo(HaveOccurred())
484+
485+
Expect(list).To(Equal(&corev1.SecretList{}))
486+
})
487+
488+
It("should error if it cannot determine the gvk for the object", func() {
489+
_, err := NewListForObject(scheme.Scheme, &unstructured.Unstructured{})
490+
Expect(err).To(HaveOccurred())
491+
})
492+
})
493+
494+
Describe("EachListItem", func() {
495+
It("should traverse over each list item", func() {
496+
list := &corev1.SecretList{
497+
Items: []corev1.Secret{
498+
{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
499+
{ObjectMeta: metav1.ObjectMeta{Name: "bar"}},
500+
},
501+
}
502+
503+
f := mockmetautils.NewMockEachListItemFunc(ctrl)
504+
gomock.InOrder(
505+
f.EXPECT().Call(&list.Items[0]),
506+
f.EXPECT().Call(&list.Items[1]),
507+
)
508+
509+
Expect(EachListItem(list, f.Call)).To(Succeed())
510+
})
511+
})
512+
513+
DescribeTable("SetLabel",
514+
func(initLabels map[string]string, key, value string, expected map[string]string) {
515+
obj := &metav1.ObjectMeta{Labels: initLabels}
516+
SetLabel(obj, key, value)
517+
Expect(obj.Labels).To(Equal(expected))
518+
},
519+
Entry("nil labels", nil, "foo", "bar", map[string]string{"foo": "bar"}),
520+
Entry("key present w/ different value", map[string]string{"foo": "baz"}, "foo", "bar", map[string]string{"foo": "bar"}),
521+
Entry("other keys present", map[string]string{"bar": "baz"}, "foo", "bar", map[string]string{"bar": "baz", "foo": "bar"}),
522+
)
523+
524+
DescribeTable("SetLabels",
525+
func(initLabels map[string]string, set, expected map[string]string) {
526+
obj := &metav1.ObjectMeta{Labels: initLabels}
527+
SetLabels(obj, set)
528+
Expect(obj.Labels).To(Equal(expected))
529+
},
530+
Entry("nil labels", nil, map[string]string{"foo": "bar"}, map[string]string{"foo": "bar"}),
531+
Entry("key present w/ different value", map[string]string{"foo": "baz"}, map[string]string{"foo": "bar"}, map[string]string{"foo": "bar"}),
532+
Entry("other keys present", map[string]string{"bar": "baz"}, map[string]string{"foo": "bar"}, map[string]string{"bar": "baz", "foo": "bar"}),
533+
Entry("partial other keys, same key", map[string]string{"foo": "baz", "bar": "baz"}, map[string]string{"foo": "bar"}, map[string]string{"foo": "bar", "bar": "baz"}),
534+
)
535+
536+
DescribeTable("SetAnnotation",
537+
func(initAnnotations map[string]string, key, value string, expected map[string]string) {
538+
obj := &metav1.ObjectMeta{Annotations: initAnnotations}
539+
SetAnnotation(obj, key, value)
540+
Expect(obj.Annotations).To(Equal(expected))
541+
},
542+
Entry("nil annotations", nil, "foo", "bar", map[string]string{"foo": "bar"}),
543+
Entry("key present w/ different value", map[string]string{"foo": "baz"}, "foo", "bar", map[string]string{"foo": "bar"}),
544+
Entry("other keys present", map[string]string{"bar": "baz"}, "foo", "bar", map[string]string{"bar": "baz", "foo": "bar"}),
545+
)
546+
547+
DescribeTable("SetAnnotations",
548+
func(initAnnotations map[string]string, set, expected map[string]string) {
549+
obj := &metav1.ObjectMeta{Annotations: initAnnotations}
550+
SetAnnotations(obj, set)
551+
Expect(obj.Annotations).To(Equal(expected))
552+
},
553+
Entry("nil annotations", nil, map[string]string{"foo": "bar"}, map[string]string{"foo": "bar"}),
554+
Entry("key present w/ different value", map[string]string{"foo": "baz"}, map[string]string{"foo": "bar"}, map[string]string{"foo": "bar"}),
555+
Entry("other keys present", map[string]string{"bar": "baz"}, map[string]string{"foo": "bar"}, map[string]string{"bar": "baz", "foo": "bar"}),
556+
Entry("partial other keys, same key", map[string]string{"foo": "baz", "bar": "baz"}, map[string]string{"foo": "bar"}, map[string]string{"foo": "bar", "bar": "baz"}),
557+
)
454558
})
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//go:generate go run github.com/golang/mock/mockgen -copyright_file ../../../hack/boilerplate.go.txt -package metautils -destination=funcs.go github.com/onmetal/controller-utils/mock/controller-utils/metautils EachListItemFunc
2+
package metautils
3+
4+
import "sigs.k8s.io/controller-runtime/pkg/client"
5+
6+
type EachListItemFunc interface {
7+
Call(obj client.Object) error
8+
}

mock/controller-utils/metautils/funcs.go

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

0 commit comments

Comments
 (0)