2 Copyright 2018 The Kubernetes Authors.
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
17 package controllerutil
24 "k8s.io/apimachinery/pkg/api/errors"
25 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/runtime"
27 "k8s.io/apimachinery/pkg/runtime/schema"
28 "sigs.k8s.io/controller-runtime/pkg/client"
29 "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
32 // AlreadyOwnedError is an error returned if the object you are trying to assign
33 // a controller reference is already owned by another controller Object is the
34 // subject and Owner is the reference for the current owner
35 type AlreadyOwnedError struct {
37 Owner v1.OwnerReference
40 func (e *AlreadyOwnedError) Error() string {
41 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)
44 func newAlreadyOwnedError(Object v1.Object, Owner v1.OwnerReference) *AlreadyOwnedError {
45 return &AlreadyOwnedError{
51 // SetControllerReference sets owner as a Controller OwnerReference on owned.
52 // This is used for garbage collection of the owned object and for
53 // reconciling the owner object on changes to owned (with a Watch + EnqueueRequestForOwner).
54 // Since only one OwnerReference can be a controller, it returns an error if
55 // there is another OwnerReference with Controller flag set.
56 func SetControllerReference(owner, object v1.Object, scheme *runtime.Scheme) error {
57 ro, ok := owner.(runtime.Object)
59 return fmt.Errorf("is not a %T a runtime.Object, cannot call SetControllerReference", owner)
62 gvk, err := apiutil.GVKForObject(ro, scheme)
68 ref := *v1.NewControllerRef(owner, schema.GroupVersionKind{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind})
70 existingRefs := object.GetOwnerReferences()
72 for i, r := range existingRefs {
73 if referSameObject(ref, r) {
75 } else if r.Controller != nil && *r.Controller {
76 return newAlreadyOwnedError(object, r)
80 existingRefs = append(existingRefs, ref)
82 existingRefs[fi] = ref
85 // Update owner references
86 object.SetOwnerReferences(existingRefs)
90 // Returns true if a and b point to the same object
91 func referSameObject(a, b v1.OwnerReference) bool {
92 aGV, err := schema.ParseGroupVersion(a.APIVersion)
97 bGV, err := schema.ParseGroupVersion(b.APIVersion)
102 return aGV == bGV && a.Kind == b.Kind && a.Name == b.Name
105 // OperationResult is the action result of a CreateOrUpdate call
106 type OperationResult string
108 const ( // They should complete the sentence "Deployment default/foo has been ..."
109 // OperationResultNone means that the resource has not been changed
110 OperationResultNone OperationResult = "unchanged"
111 // OperationResultCreated means that a new resource is created
112 OperationResultCreated OperationResult = "created"
113 // OperationResultUpdated means that an existing resource is updated
114 OperationResultUpdated OperationResult = "updated"
117 // CreateOrUpdate creates or updates the given object obj in the Kubernetes
118 // cluster. The object's desired state should be reconciled with the existing
119 // state using the passed in ReconcileFn. obj must be a struct pointer so that
120 // obj can be updated with the content returned by the Server.
122 // It returns the executed operation and an error.
123 func CreateOrUpdate(ctx context.Context, c client.Client, obj runtime.Object, f MutateFn) (OperationResult, error) {
124 // op is the operation we are going to attempt
125 op := OperationResultNone
127 // get the existing object meta
128 metaObj, ok := obj.(v1.Object)
130 return OperationResultNone, fmt.Errorf("%T does not implement metav1.Object interface", obj)
133 // retrieve the existing object
134 key := client.ObjectKey{
135 Name: metaObj.GetName(),
136 Namespace: metaObj.GetNamespace(),
138 err := c.Get(ctx, key, obj)
140 // reconcile the existing object
141 existing := obj.DeepCopyObject()
142 existingObjMeta := existing.(v1.Object)
143 existingObjMeta.SetName(metaObj.GetName())
144 existingObjMeta.SetNamespace(metaObj.GetNamespace())
146 if e := f(obj); e != nil {
147 return OperationResultNone, e
150 if metaObj.GetName() != existingObjMeta.GetName() {
151 return OperationResultNone, fmt.Errorf("ReconcileFn cannot mutate objects name")
154 if metaObj.GetNamespace() != existingObjMeta.GetNamespace() {
155 return OperationResultNone, fmt.Errorf("ReconcileFn cannot mutate objects namespace")
158 if errors.IsNotFound(err) {
159 err = c.Create(ctx, obj)
160 op = OperationResultCreated
161 } else if err == nil {
162 if reflect.DeepEqual(existing, obj) {
163 return OperationResultNone, nil
165 err = c.Update(ctx, obj)
166 op = OperationResultUpdated
168 return OperationResultNone, err
172 op = OperationResultNone
177 // MutateFn is a function which mutates the existing object into it's desired state.
178 type MutateFn func(existing runtime.Object) error