@@ -26,6 +26,7 @@ import (
26
26
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27
27
"k8s.io/apimachinery/pkg/runtime"
28
28
"k8s.io/apimachinery/pkg/runtime/schema"
29
+ "k8s.io/utils/pointer"
29
30
"sigs.k8s.io/controller-runtime/pkg/client"
30
31
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
31
32
)
@@ -55,47 +56,102 @@ func newAlreadyOwnedError(Object metav1.Object, Owner metav1.OwnerReference) *Al
55
56
// Since only one OwnerReference can be a controller, it returns an error if
56
57
// there is another OwnerReference with Controller flag set.
57
58
func SetControllerReference (owner , controlled metav1.Object , scheme * runtime.Scheme ) error {
59
+ // Validate the owner.
58
60
ro , ok := owner .(runtime.Object )
59
61
if ! ok {
60
62
return fmt .Errorf ("%T is not a runtime.Object, cannot call SetControllerReference" , owner )
61
63
}
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
- }
64
+ if err := validateOwner (owner , controlled ); err != nil {
65
+ return err
72
66
}
73
67
68
+ // Create a new controller ref.
74
69
gvk , err := apiutil .GVKForObject (ro , scheme )
75
70
if err != nil {
76
71
return err
77
72
}
73
+ ref := metav1.OwnerReference {
74
+ APIVersion : gvk .GroupVersion ().String (),
75
+ Kind : gvk .Kind ,
76
+ Name : owner .GetName (),
77
+ UID : owner .GetUID (),
78
+ BlockOwnerDeletion : pointer .BoolPtr (true ),
79
+ Controller : pointer .BoolPtr (true ),
80
+ }
81
+
82
+ // Return early with an error if the object is already controlled.
83
+ if existing := metav1 .GetControllerOf (controlled ); existing != nil && ! referSameObject (* existing , ref ) {
84
+ return newAlreadyOwnedError (controlled , * existing )
85
+ }
78
86
79
- // Create a new ref
80
- ref := * metav1 .NewControllerRef (owner , schema.GroupVersionKind {Group : gvk .Group , Version : gvk .Version , Kind : gvk .Kind })
87
+ // Update owner references and return.
88
+ upsertOwnerRef (ref , controlled )
89
+ return nil
90
+ }
81
91
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
- }
92
+ // EnsureOwnerReference is a helper method to make sure the given object contains
93
+ // an object reference to the object provided.
94
+ // If a reference already exists, it'll be overwritten with the newly provided version.
95
+ func EnsureOwnerReference (owner , object metav1.Object , scheme * runtime.Scheme ) error {
96
+ // Validate the owner.
97
+ ro , ok := owner .(runtime.Object )
98
+ if ! ok {
99
+ return fmt .Errorf ("%T is not a runtime.Object, cannot call SetControllerReference" , owner )
100
+ }
101
+ if err := validateOwner (owner , object ); err != nil {
102
+ return err
103
+ }
104
+
105
+ // Create a new owner ref.
106
+ gvk , err := apiutil .GVKForObject (ro , scheme )
107
+ if err != nil {
108
+ return err
90
109
}
91
- if fi == - 1 {
92
- existingRefs = append (existingRefs , ref )
110
+ ref := metav1.OwnerReference {
111
+ APIVersion : gvk .GroupVersion ().String (),
112
+ Kind : gvk .Kind ,
113
+ UID : owner .GetUID (),
114
+ Name : owner .GetName (),
115
+ }
116
+
117
+ // Update owner references and return.
118
+ upsertOwnerRef (ref , object )
119
+ return nil
120
+
121
+ }
122
+
123
+ func upsertOwnerRef (ref metav1.OwnerReference , object metav1.Object ) {
124
+ owners := object .GetOwnerReferences ()
125
+ idx := indexOwnerRef (owners , ref )
126
+ if idx == - 1 {
127
+ owners = append (owners , ref )
93
128
} else {
94
- existingRefs [fi ] = ref
129
+ owners [idx ] = ref
130
+ }
131
+ object .SetOwnerReferences (owners )
132
+ }
133
+
134
+ // indexOwnerRef returns the index of the owner reference in the slice if found, or -1.
135
+ func indexOwnerRef (ownerReferences []metav1.OwnerReference , ref metav1.OwnerReference ) int {
136
+ for index , r := range ownerReferences {
137
+ if referSameObject (r , ref ) {
138
+ return index
139
+ }
95
140
}
141
+ return - 1
142
+ }
96
143
97
- // Update owner references
98
- controlled .SetOwnerReferences (existingRefs )
144
+ func validateOwner (owner , object metav1.Object ) error {
145
+ ownerNs := owner .GetNamespace ()
146
+ if ownerNs != "" {
147
+ objNs := object .GetNamespace ()
148
+ if objNs == "" {
149
+ return fmt .Errorf ("cluster-scoped resource must not have a namespace-scoped owner, owner's namespace %s" , ownerNs )
150
+ }
151
+ if ownerNs != objNs {
152
+ return fmt .Errorf ("cross-namespace owner references are disallowed, owner's namespace %s, obj's namespace %s" , owner .GetNamespace (), object .GetNamespace ())
153
+ }
154
+ }
99
155
return nil
100
156
}
101
157
0 commit comments