Skip to content

Commit 01c5f95

Browse files
committed
✨ Add controllerutil.EnsureOwnerReference
Signed-off-by: Vince Prignano <[email protected]>
1 parent 8507812 commit 01c5f95

File tree

2 files changed

+169
-27
lines changed

2 files changed

+169
-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+
// Validate the owner.
5859
ro, ok := owner.(runtime.Object)
5960
if !ok {
6061
return fmt.Errorf("%T is not a runtime.Object, cannot call SetControllerReference", owner)
6162
}
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-
}
63+
if err := validateOwner(owner, controlled); err != nil {
64+
return err
7265
}
7366

67+
// Create a new controller ref.
7468
gvk, err := apiutil.GVKForObject(ro, scheme)
7569
if err != nil {
7670
return err
7771
}
78-
79-
// Create a new ref
8072
ref := *metav1.NewControllerRef(owner, schema.GroupVersionKind{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind})
8173

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-
}
74+
// Return early with an error if the object is already controlled.
75+
if existing := metav1.GetControllerOf(controlled); existing != nil && !referSameObject(*existing, ref) {
76+
return newAlreadyOwnedError(controlled, *existing)
9077
}
91-
if fi == -1 {
92-
existingRefs = append(existingRefs, ref)
78+
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
95+
}
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: 96 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,80 @@ 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+
Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
46+
Name: "foo",
47+
Kind: "Deployment",
48+
APIVersion: "extensions/v1beta1",
49+
UID: "foo-uid",
50+
}))
51+
})
52+
53+
It("should not duplicate owner references", func() {
54+
rs := &appsv1.ReplicaSet{
55+
ObjectMeta: metav1.ObjectMeta{
56+
OwnerReferences: []metav1.OwnerReference{
57+
{
58+
Name: "foo",
59+
Kind: "Deployment",
60+
APIVersion: "extensions/v1beta1",
61+
UID: "foo-uid",
62+
},
63+
},
64+
},
65+
}
66+
dep := &extensionsv1beta1.Deployment{
67+
ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"},
68+
}
69+
70+
Expect(controllerutil.EnsureOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
71+
Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
72+
Name: "foo",
73+
Kind: "Deployment",
74+
APIVersion: "extensions/v1beta1",
75+
UID: "foo-uid",
76+
}))
77+
})
78+
79+
It("should update the APIVersion if duplicate", func() {
80+
rs := &appsv1.ReplicaSet{
81+
ObjectMeta: metav1.ObjectMeta{
82+
OwnerReferences: []metav1.OwnerReference{
83+
{
84+
Name: "foo",
85+
Kind: "Deployment",
86+
APIVersion: "extensions/v1alpha1",
87+
UID: "foo-uid",
88+
},
89+
},
90+
},
91+
}
92+
dep := &extensionsv1beta1.Deployment{
93+
ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: "foo-uid"},
94+
}
95+
96+
Expect(controllerutil.EnsureOwnerReference(dep, rs, scheme.Scheme)).ToNot(HaveOccurred())
97+
Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
98+
Name: "foo",
99+
Kind: "Deployment",
100+
APIVersion: "extensions/v1beta1",
101+
UID: "foo-uid",
102+
}))
103+
104+
})
105+
})
106+
39107
Describe("SetControllerReference", func() {
40108
It("should set the OwnerReference if it can find the group version kind", func() {
41109
rs := &appsv1.ReplicaSet{}
@@ -116,6 +184,32 @@ var _ = Describe("Controllerutil", func() {
116184
}))
117185
})
118186

187+
It("should replace the owner reference if it's already present", func() {
188+
t := true
189+
rsOwners := []metav1.OwnerReference{
190+
{
191+
Name: "foo",
192+
Kind: "Deployment",
193+
APIVersion: "extensions/v1alpha1",
194+
UID: "foo-uid",
195+
Controller: &t,
196+
BlockOwnerDeletion: &t,
197+
},
198+
}
199+
rs := &appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", OwnerReferences: rsOwners}}
200+
dep := &extensionsv1beta1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", UID: "foo-uid"}}
201+
202+
Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred())
203+
Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
204+
Name: "foo",
205+
Kind: "Deployment",
206+
APIVersion: "extensions/v1beta1",
207+
UID: "foo-uid",
208+
Controller: &t,
209+
BlockOwnerDeletion: &t,
210+
}))
211+
})
212+
119213
It("should return an error if it's setting a cross-namespace owner reference", func() {
120214
rs := &appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "namespace1"}}
121215
dep := &extensionsv1beta1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "namespace2", UID: "foo-uid"}}

0 commit comments

Comments
 (0)