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  }