Skip to content

Commit 8fc450e

Browse files
committed
✨ Add controllerutil.EnsureOwnerReference
Signed-off-by: Vince Prignano <[email protected]>
1 parent 525a922 commit 8fc450e

File tree

2 files changed

+152
-27
lines changed

2 files changed

+152
-27
lines changed

pkg/controller/controllerutil/controllerutil.go

Lines changed: 73 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -55,47 +55,95 @@ func newAlreadyOwnedError(Object metav1.Object, Owner metav1.OwnerReference) *Al
5555
// Since only one OwnerReference can be a controller, it returns an error if
5656
// there is another OwnerReference with Controller flag set.
5757
func SetControllerReference(owner, controlled metav1.Object, scheme *runtime.Scheme) error {
58+
// Return early with an error if the object is already controlled.
59+
if existing := metav1.GetControllerOf(controlled); existing != nil {
60+
return newAlreadyOwnedError(controlled, *existing)
61+
}
62+
63+
// Validate the owner.
5864
ro, ok := owner.(runtime.Object)
5965
if !ok {
6066
return fmt.Errorf("%T is not a runtime.Object, cannot call SetControllerReference", owner)
6167
}
62-
63-
ownerNs := owner.GetNamespace()
64-
if ownerNs != "" {
65-
objNs := controlled.GetNamespace()
66-
if objNs == "" {
67-
return fmt.Errorf("cluster-scoped resource must not have a namespace-scoped owner, owner's namespace %s", ownerNs)
68-
}
69-
if ownerNs != objNs {
70-
return fmt.Errorf("cross-namespace owner references are disallowed, owner's namespace %s, obj's namespace %s", owner.GetNamespace(), controlled.GetNamespace())
71-
}
68+
if err := validateOwner(owner, controlled); err != nil {
69+
return err
7270
}
7371

72+
// Create a new controller ref.
7473
gvk, err := apiutil.GVKForObject(ro, scheme)
7574
if err != nil {
7675
return err
7776
}
78-
79-
// Create a new ref
8077
ref := *metav1.NewControllerRef(owner, schema.GroupVersionKind{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind})
8178

82-
existingRefs := controlled.GetOwnerReferences()
83-
fi := -1
84-
for i, r := range existingRefs {
85-
if referSameObject(ref, r) {
86-
fi = i
87-
} else if r.Controller != nil && *r.Controller {
88-
return newAlreadyOwnedError(controlled, r)
89-
}
79+
// Update owner references and return.
80+
upsertOwnerRef(ref, controlled)
81+
return nil
82+
}
83+
84+
// EnsureOwnerReference is a helper method to make sure the given object contains
85+
// an object reference to the object provided.
86+
// If a reference already exists, it'll be overwritten with the newly provided version.
87+
func EnsureOwnerReference(owner, object metav1.Object, scheme *runtime.Scheme) error {
88+
// Validate the owner.
89+
ro, ok := owner.(runtime.Object)
90+
if !ok {
91+
return fmt.Errorf("%T is not a runtime.Object, cannot call SetControllerReference", owner)
92+
}
93+
if err := validateOwner(owner, object); err != nil {
94+
return err
9095
}
91-
if fi == -1 {
92-
existingRefs = append(existingRefs, ref)
96+
97+
// Create a new owner ref.
98+
gvk, err := apiutil.GVKForObject(ro, scheme)
99+
if err != nil {
100+
return err
101+
}
102+
ref := metav1.OwnerReference{
103+
APIVersion: gvk.GroupVersion().String(),
104+
Kind: gvk.Kind,
105+
UID: owner.GetUID(),
106+
Name: owner.GetName(),
107+
}
108+
109+
// Update owner references and return.
110+
upsertOwnerRef(ref, object)
111+
return nil
112+
113+
}
114+
115+
func upsertOwnerRef(ref metav1.OwnerReference, object metav1.Object) {
116+
owners := object.GetOwnerReferences()
117+
idx := indexOwnerRef(owners, ref)
118+
if idx == -1 {
119+
owners = append(owners, ref)
93120
} else {
94-
existingRefs[fi] = ref
121+
owners[idx] = ref
122+
}
123+
object.SetOwnerReferences(owners)
124+
}
125+
126+
// indexOwnerRef returns the index of the owner reference in the slice if found, or -1.
127+
func indexOwnerRef(ownerReferences []metav1.OwnerReference, ref metav1.OwnerReference) int {
128+
for index, r := range ownerReferences {
129+
if referSameObject(r, ref) {
130+
return index
131+
}
95132
}
133+
return -1
134+
}
96135

97-
// Update owner references
98-
controlled.SetOwnerReferences(existingRefs)
136+
func validateOwner(owner, object metav1.Object) error {
137+
ownerNs := owner.GetNamespace()
138+
if ownerNs != "" {
139+
objNs := object.GetNamespace()
140+
if objNs == "" {
141+
return fmt.Errorf("cluster-scoped resource must not have a namespace-scoped owner, owner's namespace %s", ownerNs)
142+
}
143+
if ownerNs != objNs {
144+
return fmt.Errorf("cross-namespace owner references are disallowed, owner's namespace %s, obj's namespace %s", owner.GetNamespace(), object.GetNamespace())
145+
}
146+
}
99147
return nil
100148
}
101149

pkg/controller/controllerutil/controllerutil_test.go

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ import (
2121
"fmt"
2222
"math/rand"
2323

24-
"sigs.k8s.io/controller-runtime/pkg/client"
25-
2624
. "github.com/onsi/ginkgo"
2725
. "github.com/onsi/gomega"
2826
appsv1 "k8s.io/api/apps/v1"
@@ -32,10 +30,89 @@ import (
3230
"k8s.io/apimachinery/pkg/runtime"
3331
"k8s.io/apimachinery/pkg/types"
3432
"k8s.io/client-go/kubernetes/scheme"
33+
"sigs.k8s.io/controller-runtime/pkg/client"
3534
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3635
)
3736

3837
var _ = Describe("Controllerutil", func() {
38+
Describe("EnsureOwnerReference", func() {
39+
It("should set ownerRef on an empty list", func() {
40+
rs := &appsv1.ReplicaSet{}
41+
dep := &extensionsv1beta1.Deployment{
42+
ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"},
43+
}
44+
Expect(controllerutil.EnsureOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
45+
t := false
46+
Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
47+
Name: "foo",
48+
Kind: "Deployment",
49+
APIVersion: "extensions/v1beta1",
50+
UID: "foo-uid",
51+
Controller: &t,
52+
BlockOwnerDeletion: &t,
53+
}))
54+
})
55+
56+
It("should not duplicate owner references", func() {
57+
rs := &appsv1.ReplicaSet{
58+
ObjectMeta: metav1.ObjectMeta{
59+
OwnerReferences: []metav1.OwnerReference{
60+
{
61+
Name: "foo",
62+
Kind: "Deployment",
63+
APIVersion: "extensions/v1beta1",
64+
UID: "foo-uid",
65+
},
66+
},
67+
},
68+
}
69+
dep := &extensionsv1beta1.Deployment{
70+
ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"},
71+
}
72+
73+
Expect(controllerutil.EnsureOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
74+
t := false
75+
Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
76+
Name: "foo",
77+
Kind: "Deployment",
78+
APIVersion: "extensions/v1beta1",
79+
UID: "foo-uid",
80+
Controller: &t,
81+
BlockOwnerDeletion: &t,
82+
}))
83+
})
84+
85+
It("should update the APIVersion if duplicate", func() {
86+
rs := &appsv1.ReplicaSet{
87+
ObjectMeta: metav1.ObjectMeta{
88+
OwnerReferences: []metav1.OwnerReference{
89+
{
90+
Name: "foo",
91+
Kind: "Deployment",
92+
APIVersion: "extensions/v1alpha1",
93+
UID: "foo-uid",
94+
},
95+
},
96+
},
97+
}
98+
dep := &extensionsv1beta1.Deployment{
99+
ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"},
100+
}
101+
102+
Expect(controllerutil.EnsureOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
103+
t := false
104+
Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
105+
Name: "foo",
106+
Kind: "Deployment",
107+
APIVersion: "extensions/v1beta1",
108+
UID: "foo-uid",
109+
Controller: &t,
110+
BlockOwnerDeletion: &t,
111+
}))
112+
113+
})
114+
})
115+
39116
Describe("SetControllerReference", func() {
40117
It("should set the OwnerReference if it can find the group version kind", func() {
41118
rs := &appsv1.ReplicaSet{}

0 commit comments

Comments
 (0)