|
| 1 | +/* |
| 2 | +Copyright 2020 The Kubernetes Authors. |
| 3 | +
|
| 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 |
| 7 | +
|
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +
|
| 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. |
| 15 | +*/ |
| 16 | + |
| 17 | +package handler |
| 18 | + |
| 19 | +import ( |
| 20 | + "strings" |
| 21 | + |
| 22 | + "k8s.io/apimachinery/pkg/runtime/schema" |
| 23 | + |
| 24 | + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| 25 | + "k8s.io/apimachinery/pkg/types" |
| 26 | + "k8s.io/client-go/util/workqueue" |
| 27 | + "sigs.k8s.io/controller-runtime/pkg/event" |
| 28 | + "sigs.k8s.io/controller-runtime/pkg/reconcile" |
| 29 | +) |
| 30 | + |
| 31 | +var _ EventHandler = &EnqueueRequestForAnnotation{} |
| 32 | + |
| 33 | +const ( |
| 34 | + // NamespacedNameAnnotation defines the annotation that will be used to get the primary resource namespaced name. |
| 35 | + // The handler will use this value to build the types.NamespacedName object used to enqueue a Request when an event |
| 36 | + // to update, to create or to delete the observed object is raised. Note that if only one value be informed without |
| 37 | + // the "/" then it will be set as the name of the primary resource. |
| 38 | + NamespacedNameAnnotation = "watch.kubebuilder.io/owner-namespaced-name" |
| 39 | + |
| 40 | + // TypeAnnotation define the annotation that will be used to verify that the primary resource is the primary resource |
| 41 | + // to use. It should be the type schema.GroupKind. E.g watch.kubebuilder.io/owner-type:core.Pods |
| 42 | + TypeAnnotation = "watch.kubebuilder.io/owner-type" |
| 43 | +) |
| 44 | + |
| 45 | +// EnqueueRequestForAnnotation enqueues Requests based on the presence of annotations that contain the type and |
| 46 | +// namespaced name of the primary resource. The purpose of this handler is to support cross-scope ownership |
| 47 | +// relationships that are not supported by native owner references. |
| 48 | +// |
| 49 | +// This handler should ALWAYS be paired with a finalizer on the primary resource. While the |
| 50 | +// annotation-based watch handler does not have the same scope restrictions that owner references |
| 51 | +// do, they also do not have the garbage collection guarantees that owner references do. Therefore, |
| 52 | +// if the reconciler of a primary resource creates a child resource across scopes not supported by |
| 53 | +// owner references, it is up to the reconciler to clean up that child resource. |
| 54 | +// |
| 55 | +// **NOTE** You might prefer to use the EnqueueRequestsFromMapFunc with predicates instead of it. |
| 56 | +// |
| 57 | +// **Examples:** |
| 58 | +// |
| 59 | +// The following code will enqueue a Request to the `primary-resource-namespace` when some change occurs in a Pod |
| 60 | +// resource: |
| 61 | +// |
| 62 | +// ... |
| 63 | +// podTypeAnnotations := schema.GroupKind{Group: "Pods", Kind: "core"} |
| 64 | +// if err := c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForAnnotation{podTypeAnnotations}); err != nil { |
| 65 | +// entryLog.Error(err, "unable to watch Pods") |
| 66 | +// os.Exit(1) |
| 67 | +// } |
| 68 | +// ... |
| 69 | +// |
| 70 | +// With the annotations: |
| 71 | +// |
| 72 | +// ... |
| 73 | +// annotations: |
| 74 | +// watch.kubebuilder.io/owner-namespaced-name:my-namespace/my-pod |
| 75 | +// watch.kubebuilder.io/owner-type:core.Pods |
| 76 | +// ... |
| 77 | +// |
| 78 | +// The following code will enqueue a Request to the `primary-resource-namespace`, ReplicaSet reconcile, |
| 79 | +// when some change occurs in a ClusterRole resource |
| 80 | +// |
| 81 | +// ... |
| 82 | +// if err := c.Watch(&source.Kind{ |
| 83 | +// // Watch cluster roles |
| 84 | +// Type: &rbacv1.ClusterRole{}}, |
| 85 | +// |
| 86 | +// // Enqueue ReplicaSet reconcile requests using the |
| 87 | +// // namespacedName annotation value in the request. |
| 88 | +// &handler.EnqueueRequestForAnnotation{schema.GroupKind{Group:"ReplicaSet", Kind:"apps"}}); err != nil { |
| 89 | +// entryLog.Error(err, "unable to watch ClusterRole") |
| 90 | +// os.Exit(1) |
| 91 | +// } |
| 92 | +// } |
| 93 | +// ... |
| 94 | +// With the annotations: |
| 95 | +// |
| 96 | +// ... |
| 97 | +// annotations: |
| 98 | +// watch.kubebuilder.io/owner-namespaced-name:my-namespace/my-replicaset |
| 99 | +// watch.kubebuilder.io/owner-type:apps.ReplicaSet |
| 100 | +// ... |
| 101 | +// |
| 102 | +// **NOTE** Cluster-scoped resources will have the NamespacedNameAnnotation such as: |
| 103 | +// `watch.kubebuilder.io/owner-namespaced-name:my-replicaset |
| 104 | +type EnqueueRequestForAnnotation struct { |
| 105 | + // It is used to verify that the primary resource is the primary resource to use. |
| 106 | + // E.g watch.kubebuilder.io/owner-type:core.Pods |
| 107 | + Type schema.GroupKind |
| 108 | +} |
| 109 | + |
| 110 | +// Create implements EventHandler |
| 111 | +func (e *EnqueueRequestForAnnotation) Create(evt event.CreateEvent, q workqueue.RateLimitingInterface) { |
| 112 | + if ok, req := e.getAnnotationRequests(evt.Meta); ok { |
| 113 | + q.Add(req) |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +// Update implements EventHandler |
| 118 | +func (e *EnqueueRequestForAnnotation) Update(evt event.UpdateEvent, q workqueue.RateLimitingInterface) { |
| 119 | + if ok, req := e.getAnnotationRequests(evt.MetaOld); ok { |
| 120 | + q.Add(req) |
| 121 | + } else if ok, req := e.getAnnotationRequests(evt.MetaNew); ok { |
| 122 | + q.Add(req) |
| 123 | + } |
| 124 | +} |
| 125 | + |
| 126 | +// Delete implements EventHandler |
| 127 | +func (e *EnqueueRequestForAnnotation) Delete(evt event.DeleteEvent, q workqueue.RateLimitingInterface) { |
| 128 | + if ok, req := e.getAnnotationRequests(evt.Meta); ok { |
| 129 | + q.Add(req) |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +// Generic implements EventHandler |
| 134 | +func (e *EnqueueRequestForAnnotation) Generic(evt event.GenericEvent, q workqueue.RateLimitingInterface) { |
| 135 | + if ok, req := e.getAnnotationRequests(evt.Meta); ok { |
| 136 | + q.Add(req) |
| 137 | + } |
| 138 | +} |
| 139 | + |
| 140 | +// getAnnotationRequests will check if the object has the annotations for the watch handler and requeue |
| 141 | +func (e *EnqueueRequestForAnnotation) getAnnotationRequests(object metav1.Object) (bool, reconcile.Request) { |
| 142 | + if typeString, ok := object.GetAnnotations()[TypeAnnotation]; ok && typeString == e.Type.String() { |
| 143 | + namespacedNameString, ok := object.GetAnnotations()[NamespacedNameAnnotation] |
| 144 | + if !ok { |
| 145 | + log.Info("Unable to find the annotation for handle watch annotation", |
| 146 | + "resource", object, "annotation", NamespacedNameAnnotation) |
| 147 | + } |
| 148 | + if len(namespacedNameString) < 1 { |
| 149 | + return false, reconcile.Request{} |
| 150 | + } |
| 151 | + return true, reconcile.Request{NamespacedName: parseNamespacedName(namespacedNameString)} |
| 152 | + } |
| 153 | + return false, reconcile.Request{} |
| 154 | +} |
| 155 | + |
| 156 | +// parseNamespacedName will parse the value informed in the NamespacedNameAnnotation and return types.NamespacedName |
| 157 | +// with. Note that if just one value is informed, then it will be set as the name. |
| 158 | +func parseNamespacedName(namespacedNameString string) types.NamespacedName { |
| 159 | + values := strings.SplitN(namespacedNameString, "/", 2) |
| 160 | + if len(values) == 1 { |
| 161 | + return types.NamespacedName{ |
| 162 | + Name: values[0], |
| 163 | + Namespace: "", |
| 164 | + } |
| 165 | + } |
| 166 | + if len(values) >= 2 { |
| 167 | + return types.NamespacedName{ |
| 168 | + Name: values[1], |
| 169 | + Namespace: values[0], |
| 170 | + } |
| 171 | + } |
| 172 | + return types.NamespacedName{} |
| 173 | +} |
0 commit comments