Skip to content

Commit ce38862

Browse files
committed
Fix duplicate controller references
1 parent 3aaf8cd commit ce38862

File tree

2 files changed

+101
-2
lines changed

2 files changed

+101
-2
lines changed

pkg/controller/controllerutil/controllerutil.go

+53-2
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,30 @@ import (
2525
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
2626
)
2727

28+
// AlreadyOwnedError is an error returned if the object you are trying to assign
29+
// a controller reference is already owned by another controller Object is the
30+
// subject and Owner is the reference for the current owner
31+
type AlreadyOwnedError struct {
32+
Object v1.Object
33+
Owner v1.OwnerReference
34+
}
35+
36+
func (e *AlreadyOwnedError) Error() string {
37+
return fmt.Sprintf("Object %s/%s is already owned by another %s controller %s", e.Object.GetNamespace(), e.Object.GetName(), e.Owner.Kind, e.Owner.Name)
38+
}
39+
40+
func newAlreadyOwnedError(Object v1.Object, Owner v1.OwnerReference) *AlreadyOwnedError {
41+
return &AlreadyOwnedError{
42+
Object: Object,
43+
Owner: Owner,
44+
}
45+
}
46+
2847
// SetControllerReference sets owner as a Controller OwnerReference on owned.
2948
// This is used for garbage collection of the owned object and for
3049
// reconciling the owner object on changes to owned (with a Watch + EnqueueRequestForOwner).
50+
// Since only one OwnerReference can be a controller, it returns an error if
51+
// there is another OwnerReference with Controller flag set.
3152
func SetControllerReference(owner, object v1.Object, scheme *runtime.Scheme) error {
3253
ro, ok := owner.(runtime.Object)
3354
if !ok {
@@ -42,7 +63,37 @@ func SetControllerReference(owner, object v1.Object, scheme *runtime.Scheme) err
4263
// Create a new ref
4364
ref := *v1.NewControllerRef(owner, schema.GroupVersionKind{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind})
4465

45-
// Add it to the child
46-
object.SetOwnerReferences(append(object.GetOwnerReferences(), ref))
66+
existingRefs := object.GetOwnerReferences()
67+
fi := -1
68+
for i, r := range existingRefs {
69+
if referSameObject(ref, r) {
70+
fi = i
71+
} else if r.Controller != nil && *r.Controller {
72+
return newAlreadyOwnedError(object, r)
73+
}
74+
}
75+
if fi == -1 {
76+
existingRefs = append(existingRefs, ref)
77+
} else {
78+
existingRefs[fi] = ref
79+
}
80+
81+
// Update owner references
82+
object.SetOwnerReferences(existingRefs)
4783
return nil
4884
}
85+
86+
// Returns true if a and b point to the same object
87+
func referSameObject(a, b v1.OwnerReference) bool {
88+
aGV, err := schema.ParseGroupVersion(a.APIVersion)
89+
if err != nil {
90+
return false
91+
}
92+
93+
bGV, err := schema.ParseGroupVersion(b.APIVersion)
94+
if err != nil {
95+
return false
96+
}
97+
98+
return aGV == bGV && a.Kind == b.Kind && a.Name == b.Name
99+
}

pkg/controller/controllerutil/controllerutil_test.go

+48
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,54 @@ var _ = Describe("Controllerutil", func() {
4343
rs := &appsv1.ReplicaSet{}
4444
Expect(controllerutil.SetControllerReference(&errMetaObj{}, rs, scheme.Scheme)).To(HaveOccurred())
4545
})
46+
47+
It("should return an error if object is already owned by another controller", func() {
48+
t := true
49+
rsOwners := []metav1.OwnerReference{
50+
metav1.OwnerReference{
51+
Name: "bar",
52+
Kind: "Deployment",
53+
APIVersion: "extensions/v1beta1",
54+
UID: "bar-uid",
55+
Controller: &t,
56+
BlockOwnerDeletion: &t,
57+
},
58+
}
59+
rs := &appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", OwnerReferences: rsOwners}}
60+
dep := &extensionsv1beta1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", UID: "foo-uid"}}
61+
62+
err := controllerutil.SetControllerReference(dep, rs, scheme.Scheme)
63+
64+
Expect(err).To(HaveOccurred())
65+
Expect(err).To(BeAssignableToTypeOf(&controllerutil.AlreadyOwnedError{}))
66+
})
67+
68+
It("should not duplicate existing owner reference", func() {
69+
f := false
70+
t := true
71+
rsOwners := []metav1.OwnerReference{
72+
metav1.OwnerReference{
73+
Name: "foo",
74+
Kind: "Deployment",
75+
APIVersion: "extensions/v1beta1",
76+
UID: "foo-uid",
77+
Controller: &f,
78+
BlockOwnerDeletion: &t,
79+
},
80+
}
81+
rs := &appsv1.ReplicaSet{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", OwnerReferences: rsOwners}}
82+
dep := &extensionsv1beta1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default", UID: "foo-uid"}}
83+
84+
Expect(controllerutil.SetControllerReference(dep, rs, scheme.Scheme)).NotTo(HaveOccurred())
85+
Expect(rs.OwnerReferences).To(ConsistOf(metav1.OwnerReference{
86+
Name: "foo",
87+
Kind: "Deployment",
88+
APIVersion: "extensions/v1beta1",
89+
UID: "foo-uid",
90+
Controller: &t,
91+
BlockOwnerDeletion: &t,
92+
}))
93+
})
4694
})
4795
})
4896

0 commit comments

Comments
 (0)