github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/operators/adoption_controller.go (about)

     1  package operators
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  
     8  	"github.com/go-logr/logr"
     9  	appsv1 "k8s.io/api/apps/v1"
    10  	corev1 "k8s.io/api/core/v1"
    11  	rbacv1 "k8s.io/api/rbac/v1"
    12  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    13  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    14  	"k8s.io/apimachinery/pkg/api/meta"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	"k8s.io/apimachinery/pkg/labels"
    17  	"k8s.io/apimachinery/pkg/runtime"
    18  	"k8s.io/apimachinery/pkg/types"
    19  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    20  	apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
    21  	ctrl "sigs.k8s.io/controller-runtime"
    22  	"sigs.k8s.io/controller-runtime/pkg/builder"
    23  	"sigs.k8s.io/controller-runtime/pkg/client"
    24  	"sigs.k8s.io/controller-runtime/pkg/handler"
    25  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    26  
    27  	operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
    28  	operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
    29  	operatorsv2 "github.com/operator-framework/api/pkg/operators/v2"
    30  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/decorators"
    31  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
    32  	"github.com/operator-framework/operator-lifecycle-manager/pkg/metrics"
    33  )
    34  
    35  // AdoptionReconciler automagically associates Operator components with their respective operator resource.
    36  type AdoptionReconciler struct {
    37  	client.Client
    38  
    39  	log     logr.Logger
    40  	factory decorators.OperatorFactory
    41  }
    42  
    43  // +kubebuilder:rbac:groups=operators.coreos.com,resources=operators,verbs=create;update;patch;delete
    44  // +kubebuilder:rbac:groups=operators.coreos.com,resources=operators/status,verbs=update;patch
    45  // +kubebuilder:rbac:groups=*,resources=*,verbs=get;list;watch
    46  
    47  // SetupWithManager adds the operator reconciler to the given controller manager.
    48  func (r *AdoptionReconciler) SetupWithManager(mgr ctrl.Manager) error {
    49  	// Trigger operator events from the events of their compoenents.
    50  	enqueueSub := handler.EnqueueRequestsFromMapFunc(r.mapToSubscriptions)
    51  
    52  	// Create multiple controllers for resource types that require automatic adoption
    53  	err := ctrl.NewControllerManagedBy(mgr).
    54  		For(&operatorsv1alpha1.Subscription{}).
    55  		Watches(&operatorsv1alpha1.ClusterServiceVersion{}, enqueueSub).
    56  		Watches(&operatorsv1alpha1.InstallPlan{}, enqueueSub).
    57  		Complete(reconcile.Func(r.ReconcileSubscription))
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	var (
    63  		enqueueCSV       = handler.EnqueueRequestsFromMapFunc(r.mapToClusterServiceVersions)
    64  		enqueueProviders = handler.EnqueueRequestsFromMapFunc(r.mapToProviders)
    65  	)
    66  	err = ctrl.NewControllerManagedBy(mgr).
    67  		For(&operatorsv1alpha1.ClusterServiceVersion{}).
    68  		Watches(&appsv1.Deployment{}, enqueueCSV).
    69  		Watches(&corev1.Namespace{}, enqueueCSV).
    70  		Watches(&corev1.Service{}, enqueueCSV).
    71  		Watches(&apiextensionsv1.CustomResourceDefinition{}, enqueueProviders).
    72  		Watches(&apiregistrationv1.APIService{}, enqueueCSV).
    73  		Watches(&operatorsv1alpha1.Subscription{}, enqueueCSV).
    74  		Watches(&operatorsv2.OperatorCondition{}, enqueueCSV).
    75  		Watches(&corev1.Secret{}, enqueueCSV, builder.OnlyMetadata).
    76  		Watches(&corev1.ConfigMap{}, enqueueCSV, builder.OnlyMetadata).
    77  		Watches(&corev1.ServiceAccount{}, enqueueCSV, builder.OnlyMetadata).
    78  		Watches(&rbacv1.Role{}, enqueueCSV, builder.OnlyMetadata).
    79  		Watches(&rbacv1.RoleBinding{}, enqueueCSV, builder.OnlyMetadata).
    80  		Watches(&rbacv1.ClusterRole{}, enqueueCSV, builder.OnlyMetadata).
    81  		Watches(&rbacv1.ClusterRoleBinding{}, enqueueCSV, builder.OnlyMetadata).
    82  		Complete(reconcile.Func(r.ReconcileClusterServiceVersion))
    83  	if err != nil {
    84  		return err
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  // NewAdoptionReconciler constructs and returns an AdoptionReconciler.
    91  // As a side effect, the given scheme has operator discovery types added to it.
    92  func NewAdoptionReconciler(cli client.Client, log logr.Logger, scheme *runtime.Scheme) (*AdoptionReconciler, error) {
    93  	// Add watched types to scheme.
    94  	if err := AddToScheme(scheme); err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	factory, err := decorators.NewSchemedOperatorFactory(scheme)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	return &AdoptionReconciler{
   104  		Client: cli,
   105  
   106  		log:     log,
   107  		factory: factory,
   108  	}, nil
   109  }
   110  
   111  // ReconcileSubscription labels the CSVs installed by a Subscription as components of an operator named after the subscribed package and install namespace.
   112  func (r *AdoptionReconciler) ReconcileSubscription(ctx context.Context, req ctrl.Request) (reconcile.Result, error) {
   113  	// Set up a convenient log object so we don't have to type request over and over again
   114  	log := r.log.WithValues("request", req)
   115  	log.V(1).Info("reconciling subscription")
   116  	metrics.EmitAdoptionSubscriptionReconcile(req.Namespace, req.Name)
   117  
   118  	// Fetch the Subscription from the cache
   119  	in := &operatorsv1alpha1.Subscription{}
   120  	if err := r.Get(ctx, req.NamespacedName, in); err != nil {
   121  		if apierrors.IsNotFound(err) {
   122  			log.Info("Could not find Subscription")
   123  		} else {
   124  			log.Error(err, "Error finding Subscription")
   125  		}
   126  
   127  		return reconcile.Result{}, nil
   128  	}
   129  
   130  	// OLM generated Operators are named after their packages and further qualified by the install namespace
   131  	if in.Spec == nil || in.Spec.Package == "" {
   132  		log.Info("subscription spec missing package, ignoring")
   133  		return reconcile.Result{}, nil
   134  	}
   135  
   136  	// Wrap with convenience decorator
   137  	operator, err := r.factory.NewPackageOperator(in.Spec.Package, in.GetNamespace())
   138  	if err != nil {
   139  		log.Error(err, "Could not wrap Operator with convenience decorator")
   140  		return reconcile.Result{}, nil
   141  	}
   142  
   143  	// Adopt the Subscription
   144  	var errs []error
   145  	if err := r.adopt(ctx, operator, in); err != nil {
   146  		log.Error(err, "Error adopting Subscription")
   147  		errs = append(errs, err)
   148  	}
   149  
   150  	// Adopt the Subscription's installed CSV
   151  	if name := in.Status.InstalledCSV; name != "" {
   152  		csv := &operatorsv1alpha1.ClusterServiceVersion{}
   153  		csv.SetNamespace(in.GetNamespace())
   154  		csv.SetName(name)
   155  		if err := r.adopt(ctx, operator, csv); err != nil {
   156  			log.Error(err, "Error adopting installed CSV")
   157  			errs = append(errs, err)
   158  		}
   159  	}
   160  
   161  	// Adopt the Subscription's latest InstallPlan and Disown all others in the same namespace
   162  	if ref := in.Status.InstallPlanRef; ref != nil {
   163  		ip := &operatorsv1alpha1.InstallPlan{}
   164  		ip.SetNamespace(ref.Namespace)
   165  		ip.SetName(ref.Name)
   166  		if err := r.adoptInstallPlan(ctx, operator, ip); err != nil {
   167  			errs = append(errs, err)
   168  		}
   169  	}
   170  
   171  	return reconcile.Result{}, utilerrors.NewAggregate(errs)
   172  }
   173  
   174  // ReconcileClusterServiceVersion projects the component labels of a given CSV onto all resources owned by it.
   175  func (r *AdoptionReconciler) ReconcileClusterServiceVersion(ctx context.Context, req ctrl.Request) (reconcile.Result, error) {
   176  	// Set up a convenient log object so we don't have to type request over and over again
   177  	log := r.log.WithValues("request", req)
   178  	log.V(1).Info("reconciling csv")
   179  	metrics.EmitAdoptionCSVReconcile(req.Namespace, req.Name)
   180  
   181  	// Fetch the CSV from the cache
   182  	in := &operatorsv1alpha1.ClusterServiceVersion{}
   183  	if err := r.Get(ctx, req.NamespacedName, in); err != nil {
   184  		if apierrors.IsNotFound(err) {
   185  			err = nil
   186  		} else {
   187  			log.Error(err, "Error finding ClusterServiceVersion")
   188  		}
   189  
   190  		return reconcile.Result{}, err
   191  	}
   192  
   193  	// disown copied csvs - a previous release inadvertently adopted them, this cleans any up if they are adopted
   194  	if in.IsCopied() {
   195  		err := r.disownFromAll(ctx, in)
   196  		return reconcile.Result{}, err
   197  	}
   198  
   199  	// Adopt all resources owned by the CSV if necessary
   200  	return reconcile.Result{}, r.adoptComponents(ctx, in)
   201  }
   202  
   203  func (r *AdoptionReconciler) adoptComponents(ctx context.Context, csv *operatorsv1alpha1.ClusterServiceVersion) error {
   204  	if csv.IsCopied() {
   205  		// For now, skip copied CSVs
   206  		return nil
   207  	}
   208  
   209  	var operators []decorators.Operator
   210  	for _, name := range decorators.OperatorNames(csv.GetLabels()) {
   211  		o := &operatorsv1.Operator{}
   212  		o.SetName(name.Name)
   213  		operator, err := r.factory.NewOperator(o)
   214  		if err != nil {
   215  			return err
   216  		}
   217  		operators = append(operators, *operator)
   218  	}
   219  
   220  	if len(operators) < 1 {
   221  		// No operators to adopt for
   222  		return nil
   223  	}
   224  
   225  	// Label (adopt) prospective components
   226  	var (
   227  		errs []error
   228  		mu   sync.Mutex
   229  		wg   sync.WaitGroup
   230  	)
   231  	for _, operator := range operators {
   232  		components, err := r.adoptees(ctx, operator, csv)
   233  		if err != nil {
   234  			func() {
   235  				mu.Lock()
   236  				defer mu.Unlock()
   237  				errs = append(errs, err)
   238  			}()
   239  		}
   240  
   241  		for _, component := range components {
   242  			var (
   243  				// Copy variables into iteration scope
   244  				operator  = operator
   245  				component = component
   246  			)
   247  			wg.Add(1)
   248  
   249  			go func() {
   250  				defer wg.Done()
   251  				if err := r.adopt(ctx, &operator, component); err != nil {
   252  					mu.Lock()
   253  					defer mu.Unlock()
   254  					errs = append(errs, err)
   255  				}
   256  			}()
   257  		}
   258  	}
   259  	wg.Wait()
   260  
   261  	return utilerrors.NewAggregate(errs)
   262  }
   263  
   264  func (r *AdoptionReconciler) adopt(ctx context.Context, operator *decorators.Operator, component runtime.Object) error {
   265  	m, err := meta.Accessor(component)
   266  	if err != nil {
   267  		return nil
   268  	}
   269  
   270  	cObj, ok := component.(client.Object)
   271  	if !ok {
   272  		return fmt.Errorf("unable to typecast runtime.Object to client.Object")
   273  	}
   274  
   275  	if err := r.Get(ctx, types.NamespacedName{Namespace: m.GetNamespace(), Name: m.GetName()}, cObj); err != nil {
   276  		if apierrors.IsNotFound(err) {
   277  			r.log.V(1).Info("not found", "component", cObj)
   278  			err = nil
   279  		}
   280  
   281  		return err
   282  	}
   283  	candidate := cObj.DeepCopyObject()
   284  
   285  	adopted, err := operator.AdoptComponent(candidate)
   286  	if err != nil {
   287  		return err
   288  	}
   289  
   290  	if adopted {
   291  		// Only update if freshly adopted
   292  		pCObj, ok := candidate.(client.Object)
   293  		if !ok {
   294  			return fmt.Errorf("unable to typecast runtime.Object to client.Object")
   295  		}
   296  		return r.Patch(ctx, pCObj, client.MergeFrom(cObj))
   297  	}
   298  
   299  	return nil
   300  }
   301  
   302  func (r *AdoptionReconciler) disown(ctx context.Context, operator *decorators.Operator, component runtime.Object) error {
   303  	cObj, ok := component.(client.Object)
   304  	if !ok {
   305  		return fmt.Errorf("unable to typecast runtime.Object to client.Object")
   306  	}
   307  	candidate := component.DeepCopyObject()
   308  	disowned, err := operator.DisownComponent(candidate)
   309  	if err != nil {
   310  		return err
   311  	}
   312  
   313  	if !disowned {
   314  		// Wasn't a component
   315  		return nil
   316  	}
   317  
   318  	// Only update if freshly disowned
   319  	r.log.V(1).Info("component disowned", "component", candidate)
   320  	uCObj, ok := candidate.(client.Object)
   321  	if !ok {
   322  		return fmt.Errorf("unable to typecast runtime.Object to client.Object")
   323  	}
   324  	return r.Patch(ctx, uCObj, client.MergeFrom(cObj))
   325  }
   326  
   327  func (r *AdoptionReconciler) disownFromAll(ctx context.Context, component runtime.Object) error {
   328  	cObj, ok := component.(client.Object)
   329  	if !ok {
   330  		return fmt.Errorf("unable to typecast runtime.Object to client.Object")
   331  	}
   332  	var operators []decorators.Operator
   333  	for _, name := range decorators.OperatorNames(cObj.GetLabels()) {
   334  		o := &operatorsv1.Operator{}
   335  		o.SetName(name.Name)
   336  		operator, err := r.factory.NewOperator(o)
   337  		if err != nil {
   338  			return err
   339  		}
   340  		operators = append(operators, *operator)
   341  	}
   342  	errs := make([]error, 0)
   343  	for _, operator := range operators {
   344  		if err := r.disown(ctx, &operator, component); err != nil {
   345  			errs = append(errs, err)
   346  		}
   347  	}
   348  
   349  	return utilerrors.NewAggregate(errs)
   350  }
   351  
   352  func (r *AdoptionReconciler) adoptees(ctx context.Context, operator decorators.Operator, csv *operatorsv1alpha1.ClusterServiceVersion) ([]runtime.Object, error) {
   353  	// Note: We need to figure out how to dynamically add new list types here (or some equivalent) in
   354  	// order to support operators composed of custom resources.
   355  	componentLists := componentLists()
   356  
   357  	// Only resources that aren't already labelled are adoption candidates
   358  	selector, err := operator.NonComponentSelector()
   359  	if err != nil {
   360  		return nil, err
   361  	}
   362  	opt := client.MatchingLabelsSelector{Selector: selector}
   363  	for _, list := range componentLists {
   364  		cList, ok := list.(client.ObjectList)
   365  		if !ok {
   366  			return nil, fmt.Errorf("unable to typecast runtime.Object to client.ObjectList")
   367  		}
   368  		if err := r.List(ctx, cList, opt); err != nil {
   369  			return nil, err
   370  		}
   371  	}
   372  
   373  	var (
   374  		components []runtime.Object
   375  		errs       []error
   376  	)
   377  	for _, candidate := range flatten(componentLists) {
   378  		m, err := meta.Accessor(candidate)
   379  		if err != nil {
   380  			errs = append(errs, err)
   381  			continue
   382  		}
   383  
   384  		if ownerutil.IsOwnedBy(m, csv) || ownerutil.IsOwnedByLabel(m, csv) {
   385  			components = append(components, candidate)
   386  		}
   387  	}
   388  
   389  	// Pick up owned CRDs
   390  	for _, provided := range csv.Spec.CustomResourceDefinitions.Owned {
   391  		crd := &metav1.PartialObjectMetadata{}
   392  		crd.SetGroupVersionKind(apiextensionsv1.SchemeGroupVersion.WithKind("CustomResourceDefinition"))
   393  		if err := r.Get(ctx, types.NamespacedName{Name: provided.Name}, crd); err != nil {
   394  			if !apierrors.IsNotFound(err) {
   395  				// Inform requeue on transient error
   396  				errs = append(errs, err)
   397  			}
   398  
   399  			// Skip on transient error or missing CRD
   400  			continue
   401  		}
   402  
   403  		if crd == nil || !selector.Matches(labels.Set(crd.GetLabels())) {
   404  			// Skip empty and labelled CRDs
   405  			continue
   406  		}
   407  
   408  		components = append(components, crd)
   409  	}
   410  
   411  	return components, utilerrors.NewAggregate(errs)
   412  }
   413  
   414  func (r *AdoptionReconciler) adoptInstallPlan(ctx context.Context, operator *decorators.Operator, latest *operatorsv1alpha1.InstallPlan) error {
   415  	// Adopt the latest InstallPlan
   416  	if err := r.adopt(ctx, operator, latest); err != nil {
   417  		return err
   418  	}
   419  
   420  	// Disown older InstallPlans
   421  	selector, err := operator.ComponentSelector()
   422  	if err != nil {
   423  		return err
   424  	}
   425  
   426  	var (
   427  		ips = &operatorsv1alpha1.InstallPlanList{}
   428  		opt = client.MatchingLabelsSelector{Selector: selector}
   429  	)
   430  	if err := r.List(ctx, ips, opt, client.InNamespace(latest.GetNamespace())); err != nil {
   431  		return err
   432  	}
   433  
   434  	var errs []error
   435  	for _, ip := range ips.Items {
   436  		if ip.GetName() == latest.GetName() {
   437  			// Don't disown latest
   438  			continue
   439  		}
   440  
   441  		if err := r.disown(ctx, operator, &ip); err != nil {
   442  			errs = append(errs, err)
   443  		}
   444  	}
   445  
   446  	return utilerrors.NewAggregate(errs)
   447  }
   448  
   449  func (r *AdoptionReconciler) mapToSubscriptions(ctx context.Context, obj client.Object) (requests []reconcile.Request) {
   450  	if obj == nil {
   451  		return
   452  	}
   453  
   454  	// Requeue all Subscriptions in the resource namespace
   455  	// The Subscription reconciler will sort out the important changes
   456  	subs := &operatorsv1alpha1.SubscriptionList{}
   457  	if err := r.List(ctx, subs, client.InNamespace(obj.GetNamespace())); err != nil {
   458  		r.log.Error(err, "error listing subscriptions")
   459  	}
   460  
   461  	visited := map[types.NamespacedName]struct{}{}
   462  	for _, sub := range subs.Items {
   463  		nsn := types.NamespacedName{Namespace: sub.GetNamespace(), Name: sub.GetName()}
   464  
   465  		if _, ok := visited[nsn]; ok {
   466  			// Already requested
   467  			continue
   468  		}
   469  
   470  		requests = append(requests, reconcile.Request{NamespacedName: nsn})
   471  		visited[nsn] = struct{}{}
   472  	}
   473  
   474  	return
   475  }
   476  
   477  func (r *AdoptionReconciler) mapToClusterServiceVersions(_ context.Context, obj client.Object) (requests []reconcile.Request) {
   478  	if obj == nil {
   479  		return
   480  	}
   481  
   482  	// Get all owner CSV from owner labels if cluster scoped
   483  	namespace := obj.GetNamespace()
   484  	if namespace == metav1.NamespaceAll {
   485  		name, ns, ok := ownerutil.GetOwnerByKindLabel(obj, operatorsv1alpha1.ClusterServiceVersionKind)
   486  		if ok {
   487  			nsn := types.NamespacedName{Namespace: ns, Name: name}
   488  			requests = append(requests, reconcile.Request{NamespacedName: nsn})
   489  		}
   490  		return
   491  	}
   492  
   493  	// Get all owner CSVs from OwnerReferences
   494  	owners := ownerutil.GetOwnersByKind(obj, operatorsv1alpha1.ClusterServiceVersionKind)
   495  	for _, owner := range owners {
   496  		nsn := types.NamespacedName{Namespace: namespace, Name: owner.Name}
   497  		requests = append(requests, reconcile.Request{NamespacedName: nsn})
   498  	}
   499  
   500  	return
   501  }
   502  
   503  func (r *AdoptionReconciler) mapToProviders(ctx context.Context, obj client.Object) (requests []reconcile.Request) {
   504  	if obj == nil {
   505  		return nil
   506  	}
   507  
   508  	var (
   509  		csvs = &operatorsv1alpha1.ClusterServiceVersionList{}
   510  	)
   511  	if err := r.List(ctx, csvs); err != nil {
   512  		r.log.Error(err, "error listing csvs")
   513  		return
   514  	}
   515  
   516  	for _, csv := range csvs.Items {
   517  		request := reconcile.Request{
   518  			NamespacedName: types.NamespacedName{Namespace: csv.GetNamespace(), Name: csv.GetName()},
   519  		}
   520  		for _, provided := range csv.Spec.CustomResourceDefinitions.Owned {
   521  			if provided.Name == obj.GetName() {
   522  				requests = append(requests, request)
   523  				break
   524  			}
   525  		}
   526  	}
   527  
   528  	return
   529  }
   530  
   531  func flatten(objs []runtime.Object) (flattened []runtime.Object) {
   532  	for _, obj := range objs {
   533  		if nested, err := meta.ExtractList(obj); err == nil {
   534  			flattened = append(flattened, flatten(nested)...)
   535  			continue
   536  		}
   537  
   538  		flattened = append(flattened, obj)
   539  	}
   540  
   541  	return
   542  }