github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/resolver/step_resolver.go (about)

     1  //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o ../../../fakes/fake_resolver.go . StepResolver
     2  package resolver
     3  
     4  import (
     5  	"context"
     6  	"fmt"
     7  
     8  	"github.com/operator-framework/api/pkg/operators/v1alpha1"
     9  	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
    10  	v1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1"
    11  	v1alpha1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
    12  	controllerbundle "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/bundle"
    13  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
    14  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection"
    15  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
    16  	"github.com/sirupsen/logrus"
    17  	corev1 "k8s.io/api/core/v1"
    18  	"k8s.io/apimachinery/pkg/api/errors"
    19  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    20  	"k8s.io/apimachinery/pkg/labels"
    21  )
    22  
    23  const (
    24  	BundleLookupConditionPacked v1alpha1.BundleLookupConditionType = "BundleLookupNotPersisted"
    25  	exclusionAnnotation         string                             = "olm.operatorframework.io/exclude-global-namespace-resolution"
    26  )
    27  
    28  // init hooks provides the downstream a way to modify the upstream behavior
    29  var initHooks []stepResolverInitHook
    30  
    31  type StepResolver interface {
    32  	ResolveSteps(namespace string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error)
    33  }
    34  
    35  type OperatorStepResolver struct {
    36  	subLister              v1alpha1listers.SubscriptionLister
    37  	csvLister              v1alpha1listers.ClusterServiceVersionLister
    38  	ogLister               v1listers.OperatorGroupLister
    39  	client                 versioned.Interface
    40  	globalCatalogNamespace string
    41  	resolver               *Resolver
    42  	log                    logrus.FieldLogger
    43  }
    44  
    45  var _ StepResolver = &OperatorStepResolver{}
    46  
    47  type catsrcPriorityProvider struct {
    48  	lister v1alpha1listers.CatalogSourceLister
    49  }
    50  
    51  func (pp catsrcPriorityProvider) Priority(key cache.SourceKey) int {
    52  	catsrc, err := pp.lister.CatalogSources(key.Namespace).Get(key.Name)
    53  	if err != nil {
    54  		return 0
    55  	}
    56  	return catsrc.Spec.Priority
    57  }
    58  
    59  func NewOperatorStepResolver(lister operatorlister.OperatorLister, client versioned.Interface, globalCatalogNamespace string, sourceProvider cache.SourceProvider, log logrus.FieldLogger) *OperatorStepResolver {
    60  	cacheSourceProvider := &mergedSourceProvider{
    61  		sps: []cache.SourceProvider{
    62  			sourceProvider,
    63  			//SourceProviderFromRegistryClientProvider(provider, log),
    64  			&csvSourceProvider{
    65  				csvLister: lister.OperatorsV1alpha1().ClusterServiceVersionLister(),
    66  				subLister: lister.OperatorsV1alpha1().SubscriptionLister(),
    67  				ogLister:  lister.OperatorsV1().OperatorGroupLister(),
    68  				logger:    log,
    69  				client:    client,
    70  			},
    71  		},
    72  	}
    73  	stepResolver := &OperatorStepResolver{
    74  		subLister:              lister.OperatorsV1alpha1().SubscriptionLister(),
    75  		csvLister:              lister.OperatorsV1alpha1().ClusterServiceVersionLister(),
    76  		ogLister:               lister.OperatorsV1().OperatorGroupLister(),
    77  		client:                 client,
    78  		globalCatalogNamespace: globalCatalogNamespace,
    79  		resolver:               NewDefaultResolver(cacheSourceProvider, catsrcPriorityProvider{lister: lister.OperatorsV1alpha1().CatalogSourceLister()}, log),
    80  		log:                    log,
    81  	}
    82  
    83  	// init hooks can be added to the downstream to
    84  	// modify resolver behaviour
    85  	for _, initHook := range initHooks {
    86  		if err := initHook(stepResolver); err != nil {
    87  			panic(err)
    88  		}
    89  	}
    90  	return stepResolver
    91  }
    92  
    93  func (r *OperatorStepResolver) ResolveSteps(namespace string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) {
    94  	subs, err := r.listSubscriptions(namespace)
    95  	if err != nil {
    96  		return nil, nil, nil, err
    97  	}
    98  
    99  	namespaces := []string{namespace}
   100  	ogs, err := r.ogLister.OperatorGroups(namespace).List(labels.Everything())
   101  	if err != nil {
   102  		return nil, nil, nil, fmt.Errorf("listing operatorgroups in namespace %s: %s", namespace, err)
   103  	}
   104  	if len(ogs) != 1 {
   105  		return nil, nil, nil, fmt.Errorf("expected 1 OperatorGroup in the namespace, found %d", len(ogs))
   106  	}
   107  	og := ogs[0]
   108  	if val, ok := og.Annotations[exclusionAnnotation]; ok && val == "true" {
   109  		// Exclusion specified
   110  		// Ignore the globalNamespace for the purposes of resolution in this namespace
   111  		r.log.Printf("excluding global catalogs from resolution in namespace %s", namespace)
   112  	} else {
   113  		namespaces = append(namespaces, r.globalCatalogNamespace)
   114  	}
   115  	operators, err := r.resolver.Resolve(namespaces, subs)
   116  	if err != nil {
   117  		return nil, nil, nil, err
   118  	}
   119  
   120  	// if there's no error, we were able to satisfy all constraints in the subscription set, so we calculate what
   121  	// changes to persist to the cluster and write them out as `steps`
   122  	steps := []*v1alpha1.Step{}
   123  	updatedSubs := []*v1alpha1.Subscription{}
   124  	bundleLookups := []v1alpha1.BundleLookup{}
   125  	for _, op := range operators {
   126  		// Find any existing subscriptions that resolve to this operator.
   127  		existingSubscriptions := make(map[*v1alpha1.Subscription]bool)
   128  		sourceInfo := *op.SourceInfo
   129  		for _, sub := range subs {
   130  			if sub.Spec.Package != sourceInfo.Package {
   131  				continue
   132  			}
   133  			if sub.Spec.Channel != "" && sub.Spec.Channel != sourceInfo.Channel {
   134  				continue
   135  			}
   136  			subCatalogKey := cache.SourceKey{
   137  				Name:      sub.Spec.CatalogSource,
   138  				Namespace: sub.Spec.CatalogSourceNamespace,
   139  			}
   140  			if !subCatalogKey.Empty() && !subCatalogKey.Equal(sourceInfo.Catalog) {
   141  				continue
   142  			}
   143  			alreadyExists, err := r.hasExistingCurrentCSV(sub)
   144  			if err != nil {
   145  				return nil, nil, nil, fmt.Errorf("unable to determine whether subscription %s has a preexisting CSV", sub.GetName())
   146  			}
   147  			existingSubscriptions[sub] = alreadyExists
   148  		}
   149  
   150  		if len(existingSubscriptions) > 0 {
   151  			upToDate := true
   152  			for sub, exists := range existingSubscriptions {
   153  				if !exists || sub.Status.CurrentCSV != op.Name {
   154  					upToDate = false
   155  				}
   156  			}
   157  			// all matching subscriptions are up to date
   158  			if upToDate {
   159  				continue
   160  			}
   161  		}
   162  
   163  		// add steps for any new bundle
   164  		if op.Bundle != nil {
   165  			bundleSteps, err := NewStepsFromBundle(op.Bundle, namespace, op.Replaces, op.SourceInfo.Catalog.Name, op.SourceInfo.Catalog.Namespace)
   166  			if err != nil {
   167  				return nil, nil, nil, fmt.Errorf("failed to turn bundle into steps: %s", err.Error())
   168  			}
   169  			steps = append(steps, bundleSteps...)
   170  		} else {
   171  			lookup := v1alpha1.BundleLookup{
   172  				Path:       op.BundlePath,
   173  				Identifier: op.Name,
   174  				Replaces:   op.Replaces,
   175  				CatalogSourceRef: &corev1.ObjectReference{
   176  					Namespace: op.SourceInfo.Catalog.Namespace,
   177  					Name:      op.SourceInfo.Catalog.Name,
   178  				},
   179  				Conditions: []v1alpha1.BundleLookupCondition{
   180  					{
   181  						Type:    BundleLookupConditionPacked,
   182  						Status:  corev1.ConditionTrue,
   183  						Reason:  controllerbundle.NotUnpackedReason,
   184  						Message: controllerbundle.NotUnpackedMessage,
   185  					},
   186  					{
   187  						Type:    v1alpha1.BundleLookupPending,
   188  						Status:  corev1.ConditionTrue,
   189  						Reason:  controllerbundle.JobNotStartedReason,
   190  						Message: controllerbundle.JobNotStartedMessage,
   191  					},
   192  				},
   193  			}
   194  			anno, err := projection.PropertiesAnnotationFromPropertyList(op.Properties)
   195  			if err != nil {
   196  				return nil, nil, nil, fmt.Errorf("failed to serialize operator properties for %q: %w", op.Name, err)
   197  			}
   198  			lookup.Properties = anno
   199  			bundleLookups = append(bundleLookups, lookup)
   200  		}
   201  
   202  		if len(existingSubscriptions) == 0 {
   203  			// explicitly track the resolved CSV as the starting CSV on the resolved subscriptions
   204  			op.SourceInfo.StartingCSV = op.Name
   205  			subStep, err := NewSubscriptionStepResource(namespace, *op.SourceInfo)
   206  			if err != nil {
   207  				return nil, nil, nil, err
   208  			}
   209  			steps = append(steps, &v1alpha1.Step{
   210  				Resolving: op.Name,
   211  				Resource:  subStep,
   212  				Status:    v1alpha1.StepStatusUnknown,
   213  			})
   214  		}
   215  
   216  		// add steps for subscriptions for bundles that were added through resolution
   217  		for sub := range existingSubscriptions {
   218  			if sub.Status.CurrentCSV == op.Name {
   219  				continue
   220  			}
   221  			// update existing subscription status
   222  			sub.Status.CurrentCSV = op.Name
   223  			updatedSubs = append(updatedSubs, sub)
   224  		}
   225  	}
   226  
   227  	// Order Steps
   228  	steps = v1alpha1.OrderSteps(steps)
   229  	return steps, bundleLookups, updatedSubs, nil
   230  }
   231  
   232  func (r *OperatorStepResolver) hasExistingCurrentCSV(sub *v1alpha1.Subscription) (bool, error) {
   233  	if sub.Status.CurrentCSV == "" {
   234  		return false, nil
   235  	}
   236  	_, err := r.csvLister.ClusterServiceVersions(sub.GetNamespace()).Get(sub.Status.CurrentCSV)
   237  	if err == nil {
   238  		return true, nil
   239  	}
   240  	if errors.IsNotFound(err) {
   241  		return false, nil
   242  	}
   243  	return false, err // Can't answer this question right now.
   244  }
   245  
   246  func (r *OperatorStepResolver) listSubscriptions(namespace string) ([]*v1alpha1.Subscription, error) {
   247  	list, err := r.client.OperatorsV1alpha1().Subscriptions(namespace).List(context.TODO(), metav1.ListOptions{})
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  
   252  	var subs []*v1alpha1.Subscription
   253  	for i := range list.Items {
   254  		subs = append(subs, &list.Items[i])
   255  	}
   256  
   257  	return subs, nil
   258  }
   259  
   260  type mergedSourceProvider struct {
   261  	sps    []cache.SourceProvider
   262  	logger logrus.StdLogger
   263  }
   264  
   265  func (msp *mergedSourceProvider) Sources(namespaces ...string) map[cache.SourceKey]cache.Source {
   266  	result := make(map[cache.SourceKey]cache.Source)
   267  	for _, sp := range msp.sps {
   268  		for key, source := range sp.Sources(namespaces...) {
   269  			if _, ok := result[key]; ok {
   270  				msp.logger.Printf("warning: duplicate sourcekey: %q\n", key)
   271  			}
   272  			result[key] = source
   273  		}
   274  	}
   275  	return result
   276  }