github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/operators/labeller/labels.go (about) 1 package labeller 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "os" 8 "strings" 9 10 jsonpatch "github.com/evanphx/json-patch" 11 "github.com/sirupsen/logrus" 12 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 "k8s.io/apimachinery/pkg/labels" 15 "k8s.io/apimachinery/pkg/runtime/schema" 16 "k8s.io/apimachinery/pkg/types" 17 18 operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" 19 operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" 20 operatorsv1alpha2 "github.com/operator-framework/api/pkg/operators/v1alpha2" 21 operatorsv2 "github.com/operator-framework/api/pkg/operators/v2" 22 23 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" 24 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/decorators" 25 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil" 26 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer" 27 ) 28 29 type ApplyConfig[T any] interface { 30 WithLabels(map[string]string) T 31 } 32 33 type Client[A ApplyConfig[A], T metav1.Object] interface { 34 Apply(ctx context.Context, cfg ApplyConfig[A], opts metav1.ApplyOptions) (result T, err error) 35 } 36 37 func hasLabel(obj metav1.Object) bool { 38 value, ok := obj.GetLabels()[install.OLMManagedLabelKey] 39 return ok && value == install.OLMManagedLabelValue 40 } 41 42 func ObjectLabeler[T metav1.Object, A ApplyConfig[A]]( 43 ctx context.Context, 44 logger *logrus.Logger, 45 check func(metav1.Object) bool, 46 list func(options labels.Selector) ([]T, error), 47 applyConfigFor func(name, namespace string) A, 48 apply func(namespace string, ctx context.Context, cfg A, opts metav1.ApplyOptions) (T, error), 49 ) func(done func() bool) queueinformer.LegacySyncHandler { 50 return func(done func() bool) queueinformer.LegacySyncHandler { 51 return func(obj interface{}) error { 52 cast, ok := obj.(T) 53 if !ok { 54 err := fmt.Errorf("wrong type %T, expected %T: %#v", obj, new(T), obj) 55 logger.WithError(err).Error("casting failed") 56 return fmt.Errorf("casting failed: %w", err) 57 } 58 59 if !check(cast) || hasLabel(cast) { 60 // if the object we're processing does not need us to label it, it's possible that every object that requires 61 // the label already has it; in which case we should exit the process, so the Pod that succeeds us can filter 62 // the informers used to drive the controller and stop having to track extraneous objects 63 items, err := list(labels.Everything()) 64 if err != nil { 65 logger.WithError(err).Warn("failed to list all objects to check for labelling completion") 66 return nil 67 } 68 gvrFullyLabelled := true 69 for _, item := range items { 70 gvrFullyLabelled = gvrFullyLabelled && (!check(item) || hasLabel(item)) 71 } 72 if gvrFullyLabelled { 73 allObjectsLabelled := done() 74 if allObjectsLabelled { 75 logrus.Info("detected that every object is labelled, exiting to re-start the process...") 76 os.Exit(0) 77 } 78 } 79 return nil 80 } 81 82 logger.WithFields(logrus.Fields{"namespace": cast.GetNamespace(), "name": cast.GetName()}).Info("applying ownership label") 83 cfg := applyConfigFor(cast.GetName(), cast.GetNamespace()) 84 cfg.WithLabels(map[string]string{ 85 install.OLMManagedLabelKey: install.OLMManagedLabelValue, 86 }) 87 88 _, err := apply(cast.GetNamespace(), ctx, cfg, metav1.ApplyOptions{FieldManager: "olm-ownership-labeller"}) 89 return err 90 } 91 } 92 } 93 94 // CRDs did not have applyconfigurations generated for them on accident, we can remove this when 95 // https://github.com/kubernetes/kubernetes/pull/120177 lands 96 func ObjectPatchLabeler( 97 ctx context.Context, 98 logger *logrus.Logger, 99 check func(metav1.Object) bool, 100 list func(selector labels.Selector) (ret []*metav1.PartialObjectMetadata, err error), 101 patch func(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *apiextensionsv1.CustomResourceDefinition, err error), 102 ) func(done func() bool) queueinformer.LegacySyncHandler { 103 return func(done func() bool) queueinformer.LegacySyncHandler { 104 return func(obj interface{}) error { 105 cast, ok := obj.(*metav1.PartialObjectMetadata) 106 if !ok { 107 err := fmt.Errorf("wrong type %T, expected %T: %#v", obj, new(*metav1.PartialObjectMetadata), obj) 108 logger.WithError(err).Error("casting failed") 109 return fmt.Errorf("casting failed: %w", err) 110 } 111 112 if !check(cast) || hasLabel(cast) { 113 // if the object we're processing does not need us to label it, it's possible that every object that requires 114 // the label already has it; in which case we should exit the process, so the Pod that succeeds us can filter 115 // the informers used to drive the controller and stop having to track extraneous objects 116 items, err := list(labels.Everything()) 117 if err != nil { 118 logger.WithError(err).Warn("failed to list all objects to check for labelling completion") 119 return nil 120 } 121 gvrFullyLabelled := true 122 for _, item := range items { 123 gvrFullyLabelled = gvrFullyLabelled && (!check(item) || hasLabel(item)) 124 } 125 if gvrFullyLabelled { 126 allObjectsLabelled := done() 127 if allObjectsLabelled { 128 logrus.Info("detected that every object is labelled, exiting to re-start the process...") 129 os.Exit(0) 130 } 131 } 132 return nil 133 } 134 135 uid := cast.GetUID() 136 rv := cast.GetResourceVersion() 137 138 // to ensure they appear in the patch as preconditions 139 previous := cast.DeepCopy() 140 previous.SetUID("") 141 previous.SetResourceVersion("") 142 143 oldData, err := json.Marshal(previous) 144 if err != nil { 145 return fmt.Errorf("failed to Marshal old data for %s/%s: %w", previous.GetNamespace(), previous.GetName(), err) 146 } 147 148 // to ensure they appear in the patch as preconditions 149 updated := cast.DeepCopy() 150 updated.SetUID(uid) 151 updated.SetResourceVersion(rv) 152 labels := updated.GetLabels() 153 if labels == nil { 154 labels = map[string]string{} 155 } 156 labels[install.OLMManagedLabelKey] = install.OLMManagedLabelValue 157 updated.SetLabels(labels) 158 159 newData, err := json.Marshal(updated) 160 if err != nil { 161 return fmt.Errorf("failed to Marshal old data for %s/%s: %w", updated.GetNamespace(), updated.GetName(), err) 162 } 163 164 logger.WithFields(logrus.Fields{"namespace": cast.GetNamespace(), "name": cast.GetName()}).Info("patching ownership label") 165 patchBytes, err := jsonpatch.CreateMergePatch(oldData, newData) 166 if err != nil { 167 return fmt.Errorf("failed to create patch for %s/%s: %w", cast.GetNamespace(), cast.GetName(), err) 168 } 169 170 _, err = patch(ctx, cast.GetName(), types.MergePatchType, patchBytes, metav1.PatchOptions{FieldManager: "olm-ownership-labeller"}) 171 return err 172 } 173 } 174 } 175 176 // HasOLMOwnerRef determines if an object is owned by another object in the OLM Groups. 177 // This checks both classical OwnerRefs and the "OLM OwnerRef" in labels to handle 178 // cluster-scoped resources. 179 func HasOLMOwnerRef(object metav1.Object) bool { 180 for _, ref := range object.GetOwnerReferences() { 181 for _, gv := range []schema.GroupVersion{ 182 operatorsv1.GroupVersion, 183 operatorsv1alpha1.SchemeGroupVersion, 184 operatorsv1alpha2.GroupVersion, 185 operatorsv2.GroupVersion, 186 } { 187 if ref.APIVersion == gv.String() { 188 return true 189 } 190 } 191 } 192 hasOLMOwnerLabels := true 193 for _, label := range []string{ownerutil.OwnerKey, ownerutil.OwnerNamespaceKey, ownerutil.OwnerKind} { 194 _, exists := object.GetLabels()[label] 195 hasOLMOwnerLabels = hasOLMOwnerLabels && exists 196 } 197 return hasOLMOwnerLabels 198 } 199 200 func HasOLMLabel(object metav1.Object) bool { 201 for key := range object.GetLabels() { 202 if strings.HasPrefix(key, decorators.ComponentLabelKeyPrefix) { 203 return true 204 } 205 } 206 return false 207 }