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

     1  package resolver
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/blang/semver/v4"
    11  	"github.com/sirupsen/logrus"
    12  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    13  
    14  	"github.com/operator-framework/api/pkg/constraints"
    15  	"github.com/operator-framework/api/pkg/operators/v1alpha1"
    16  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
    17  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver"
    18  	"github.com/operator-framework/operator-registry/pkg/api"
    19  	opregistry "github.com/operator-framework/operator-registry/pkg/registry"
    20  )
    21  
    22  // constraintProvider knows how to provide solver constraints for a given cache entry.
    23  // For instance, it could be used to surface additional constraints against an entry given some
    24  // properties it may expose. E.g. olm.maxOpenShiftVersion could be checked against the cluster version
    25  // and prohibit any entry that doesn't meet the requirement
    26  type constraintProvider interface {
    27  	// Constraints returns a set of solver constraints for a cache entry.
    28  	Constraints(e *cache.Entry) ([]solver.Constraint, error)
    29  }
    30  
    31  type Resolver struct {
    32  	cache                     *cache.Cache
    33  	log                       logrus.FieldLogger
    34  	pc                        *predicateConverter
    35  	systemConstraintsProvider constraintProvider
    36  }
    37  
    38  func NewDefaultResolver(rcp cache.SourceProvider, sourcePriorityProvider cache.SourcePriorityProvider, logger logrus.FieldLogger) *Resolver {
    39  	return &Resolver{
    40  		cache: cache.New(rcp, cache.WithLogger(logger), cache.WithSourcePriorityProvider(sourcePriorityProvider)),
    41  		log:   logger,
    42  		pc: &predicateConverter{
    43  			celEnv: constraints.NewCelEnvironment(),
    44  		},
    45  	}
    46  }
    47  
    48  type debugWriter struct {
    49  	logrus.FieldLogger
    50  }
    51  
    52  func (w *debugWriter) Write(b []byte) (int, error) {
    53  	n := len(b)
    54  	w.Debug(string(b))
    55  	return n, nil
    56  }
    57  
    58  func (r *Resolver) Resolve(namespaces []string, subs []*v1alpha1.Subscription) ([]*cache.Entry, error) {
    59  	var errs []error
    60  
    61  	variables := make(map[solver.Identifier]solver.Variable)
    62  	visited := make(map[*cache.Entry]*BundleVariable)
    63  
    64  	// TODO: better abstraction
    65  	startingCSVs := make(map[string]struct{})
    66  
    67  	namespacedCache := r.cache.Namespaced(namespaces...)
    68  
    69  	if len(namespaces) < 1 {
    70  		// the first namespace is treated as the preferred namespace today
    71  		return nil, fmt.Errorf("at least one namespace must be provided to resolution")
    72  	}
    73  
    74  	preferredNamespace := namespaces[0]
    75  	_, existingVariables, err := r.getBundleVariables(preferredNamespace, namespacedCache.Catalog(cache.NewVirtualSourceKey(preferredNamespace)).Find(cache.True()), namespacedCache, visited)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	for _, i := range existingVariables {
    80  		variables[i.Identifier()] = i
    81  	}
    82  
    83  	// build constraints for each Subscription
    84  	for _, sub := range subs {
    85  		// find the currently installed operator (if it exists)
    86  		var current *cache.Entry
    87  
    88  		matches := namespacedCache.Catalog(cache.NewVirtualSourceKey(sub.Namespace)).Find(cache.CSVNamePredicate(sub.Status.InstalledCSV))
    89  		if len(matches) > 1 {
    90  			var names []string
    91  			for _, each := range matches {
    92  				names = append(names, each.Name)
    93  			}
    94  			return nil, fmt.Errorf("multiple name matches for status.installedCSV of subscription %s/%s: %s", sub.Namespace, sub.Name, strings.Join(names, ", "))
    95  		} else if len(matches) == 1 {
    96  			current = matches[0]
    97  		}
    98  
    99  		if current == nil && sub.Spec.StartingCSV != "" {
   100  			startingCSVs[sub.Spec.StartingCSV] = struct{}{}
   101  		}
   102  
   103  		// find operators, in channel order, that can skip from the current version or list the current in "replaces"
   104  		subVariables, err := r.getSubscriptionVariables(sub, current, namespacedCache, visited)
   105  		if err != nil {
   106  			errs = append(errs, err)
   107  			continue
   108  		}
   109  
   110  		for _, i := range subVariables {
   111  			variables[i.Identifier()] = i
   112  		}
   113  	}
   114  
   115  	r.addInvariants(namespacedCache, variables)
   116  
   117  	if err := namespacedCache.Error(); err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	input := make([]solver.Variable, 0)
   122  	for _, i := range variables {
   123  		input = append(input, i)
   124  	}
   125  
   126  	if len(errs) > 0 {
   127  		return nil, utilerrors.NewAggregate(errs)
   128  	}
   129  	s, err := solver.New(solver.WithInput(input), solver.WithTracer(solver.LoggingTracer{Writer: &debugWriter{r.log}}))
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	solvedVariables, err := s.Solve(context.TODO())
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	// get the set of bundle variables from the result solved variables
   139  	operatorVariables := make([]BundleVariable, 0)
   140  	for _, variable := range solvedVariables {
   141  		if bundleVariable, ok := variable.(*BundleVariable); ok {
   142  			_, _, catalog, err := bundleVariable.BundleSourceInfo()
   143  			if err != nil {
   144  				return nil, fmt.Errorf("error determining origin of operator: %w", err)
   145  			}
   146  			if catalog.Virtual() {
   147  				// Result is expected to contain only new things.
   148  				continue
   149  			}
   150  			operatorVariables = append(operatorVariables, *bundleVariable)
   151  		}
   152  	}
   153  
   154  	var operators []*cache.Entry
   155  	for _, variableOperator := range operatorVariables {
   156  		csvName, channel, catalog, err := variableOperator.BundleSourceInfo()
   157  		if err != nil {
   158  			errs = append(errs, err)
   159  			continue
   160  		}
   161  
   162  		op, err := cache.ExactlyOne(namespacedCache.Catalog(catalog).Find(cache.CSVNamePredicate(csvName), cache.ChannelPredicate(channel)))
   163  		if err != nil {
   164  			errs = append(errs, err)
   165  			continue
   166  		}
   167  
   168  		// copy consumed fields to avoid directly mutating cache
   169  		op = &cache.Entry{
   170  			Name:         op.Name,
   171  			Replaces:     op.Replaces,
   172  			Skips:        op.Skips,
   173  			SkipRange:    op.SkipRange,
   174  			ProvidedAPIs: op.ProvidedAPIs,
   175  			RequiredAPIs: op.RequiredAPIs,
   176  			Version:      op.Version,
   177  			SourceInfo: &cache.OperatorSourceInfo{
   178  				Package:        op.SourceInfo.Package,
   179  				Channel:        op.SourceInfo.Channel,
   180  				StartingCSV:    op.SourceInfo.StartingCSV,
   181  				Catalog:        op.SourceInfo.Catalog,
   182  				DefaultChannel: op.SourceInfo.DefaultChannel,
   183  				Subscription:   op.SourceInfo.Subscription,
   184  			},
   185  			Properties: op.Properties,
   186  			BundlePath: op.BundlePath,
   187  			Bundle:     op.Bundle,
   188  		}
   189  		if len(variableOperator.Replaces) > 0 {
   190  			op.Replaces = variableOperator.Replaces
   191  		}
   192  
   193  		// lookup if this variable came from a starting CSV
   194  		if _, ok := startingCSVs[csvName]; ok {
   195  			op.SourceInfo.StartingCSV = csvName
   196  		}
   197  
   198  		operators = append(operators, op)
   199  	}
   200  
   201  	if len(errs) > 0 {
   202  		return nil, utilerrors.NewAggregate(errs)
   203  	}
   204  
   205  	return operators, nil
   206  }
   207  
   208  // newBundleVariableFromEntry converts an entry into a bundle variable with
   209  // system constraints applied, if they are defined for the entry
   210  func (r *Resolver) newBundleVariableFromEntry(entry *cache.Entry) (*BundleVariable, error) {
   211  	bundleInstalleble, err := NewBundleVariableFromOperator(entry)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  
   216  	// apply system constraints if necessary
   217  	if r.systemConstraintsProvider != nil && !(entry.SourceInfo.Catalog.Virtual()) {
   218  		systemConstraints, err := r.systemConstraintsProvider.Constraints(entry)
   219  		if err != nil {
   220  			return nil, err
   221  		}
   222  		bundleInstalleble.constraints = append(bundleInstalleble.constraints, systemConstraints...)
   223  	}
   224  	return &bundleInstalleble, nil
   225  }
   226  
   227  func (r *Resolver) getSubscriptionVariables(sub *v1alpha1.Subscription, current *cache.Entry, namespacedCache cache.MultiCatalogOperatorFinder, visited map[*cache.Entry]*BundleVariable) (map[solver.Identifier]solver.Variable, error) {
   228  	var cachePredicates, channelPredicates []cache.Predicate
   229  	variables := make(map[solver.Identifier]solver.Variable)
   230  
   231  	catalog := cache.SourceKey{
   232  		Name:      sub.Spec.CatalogSource,
   233  		Namespace: sub.Spec.CatalogSourceNamespace,
   234  	}
   235  
   236  	var entries []*cache.Entry
   237  	{
   238  		var nall, npkg, nch, ncsv int
   239  
   240  		csvPredicate := cache.True()
   241  		if current != nil {
   242  			// if we found an existing installed operator, we should filter the channel by operators that can replace it
   243  			channelPredicates = append(channelPredicates, cache.Or(cache.SkipRangeIncludesPredicate(*current.Version), cache.ReplacesPredicate(current.Name)))
   244  		} else if sub.Spec.StartingCSV != "" {
   245  			// if no operator is installed and we have a startingCSV, filter for it
   246  			csvPredicate = cache.CSVNamePredicate(sub.Spec.StartingCSV)
   247  		}
   248  
   249  		cachePredicates = append(cachePredicates, cache.And(
   250  			cache.CountingPredicate(cache.True(), &nall),
   251  			cache.CountingPredicate(cache.PkgPredicate(sub.Spec.Package), &npkg),
   252  			cache.CountingPredicate(cache.ChannelPredicate(sub.Spec.Channel), &nch),
   253  			cache.CountingPredicate(csvPredicate, &ncsv),
   254  		))
   255  		entries = namespacedCache.Catalog(catalog).Find(cachePredicates...)
   256  
   257  		var si solver.Variable
   258  		switch {
   259  		case nall == 0:
   260  			si = NewInvalidSubscriptionVariable(sub.GetName(), fmt.Sprintf("no operators found from catalog %s in namespace %s referenced by subscription %s", sub.Spec.CatalogSource, sub.Spec.CatalogSourceNamespace, sub.GetName()))
   261  		case npkg == 0:
   262  			si = NewInvalidSubscriptionVariable(sub.GetName(), fmt.Sprintf("no operators found in package %s in the catalog referenced by subscription %s", sub.Spec.Package, sub.GetName()))
   263  		case nch == 0:
   264  			si = NewInvalidSubscriptionVariable(sub.GetName(), fmt.Sprintf("no operators found in channel %s of package %s in the catalog referenced by subscription %s", sub.Spec.Channel, sub.Spec.Package, sub.GetName()))
   265  		case ncsv == 0:
   266  			si = NewInvalidSubscriptionVariable(sub.GetName(), fmt.Sprintf("no operators found with name %s in channel %s of package %s in the catalog referenced by subscription %s", sub.Spec.StartingCSV, sub.Spec.Channel, sub.Spec.Package, sub.GetName()))
   267  		}
   268  
   269  		if si != nil {
   270  			variables[si.Identifier()] = si
   271  			return variables, nil
   272  		}
   273  	}
   274  
   275  	// entries in the default channel appear first, then lexicographically order by channel name
   276  	sort.SliceStable(entries, func(i, j int) bool {
   277  		var idef bool
   278  		var ichan string
   279  		if isrc := entries[i].SourceInfo; isrc != nil {
   280  			idef = isrc.DefaultChannel
   281  			ichan = isrc.Channel
   282  		}
   283  		var jdef bool
   284  		var jchan string
   285  		if jsrc := entries[j].SourceInfo; jsrc != nil {
   286  			jdef = jsrc.DefaultChannel
   287  			jchan = jsrc.Channel
   288  		}
   289  		if idef == jdef {
   290  			return ichan < jchan
   291  		}
   292  		return idef
   293  	})
   294  
   295  	var sortedBundles []*cache.Entry
   296  	lastChannel, lastIndex := "", 0
   297  	for i := 0; i <= len(entries); i++ {
   298  		if i != len(entries) && entries[i].Channel() == lastChannel {
   299  			continue
   300  		}
   301  		channel, err := sortChannel(entries[lastIndex:i])
   302  		if err != nil {
   303  			return nil, err
   304  		}
   305  		sortedBundles = append(sortedBundles, channel...)
   306  
   307  		if i != len(entries) {
   308  			lastChannel = entries[i].Channel()
   309  			lastIndex = i
   310  		}
   311  	}
   312  
   313  	candidates := make([]*BundleVariable, 0)
   314  	for _, o := range cache.Filter(sortedBundles, channelPredicates...) {
   315  		predicates := append(cachePredicates, cache.CSVNamePredicate(o.Name))
   316  		stack := namespacedCache.Catalog(catalog).Find(predicates...)
   317  		id, variable, err := r.getBundleVariables(sub.Namespace, stack, namespacedCache, visited)
   318  		if err != nil {
   319  			return nil, err
   320  		}
   321  		if len(id) < 1 {
   322  			return nil, fmt.Errorf("could not find any potential bundles for subscription: %s", sub.Spec.Package)
   323  		}
   324  
   325  		for _, i := range variable {
   326  			if _, ok := id[i.Identifier()]; ok {
   327  				candidates = append(candidates, i)
   328  			}
   329  			variables[i.Identifier()] = i
   330  		}
   331  	}
   332  
   333  	depIds := make([]solver.Identifier, 0)
   334  	for _, c := range candidates {
   335  		// track which operator this is replacing, so that it can be realized when creating the resources on cluster
   336  		if current != nil {
   337  			c.Replaces = current.Name
   338  			// Package name can't be reliably inferred
   339  			// from a CSV without a projected package
   340  			// property, so for the replacement case, a
   341  			// one-to-one conflict is created between the
   342  			// replacer and the replacee. It should be
   343  			// safe to remove this conflict if properties
   344  			// annotations are made mandatory for
   345  			// resolution.
   346  			c.AddConflict(bundleID(current.Name, current.Channel(), cache.NewVirtualSourceKey(sub.GetNamespace())))
   347  		}
   348  		depIds = append(depIds, c.Identifier())
   349  	}
   350  	if current != nil {
   351  		depIds = append(depIds, bundleID(current.Name, current.Channel(), cache.NewVirtualSourceKey(sub.GetNamespace())))
   352  	}
   353  
   354  	// all candidates added as options for this constraint
   355  	subVariable := NewSubscriptionVariable(sub.GetName(), depIds)
   356  	variables[subVariable.Identifier()] = subVariable
   357  
   358  	return variables, nil
   359  }
   360  
   361  func (r *Resolver) getBundleVariables(preferredNamespace string, bundleStack []*cache.Entry, namespacedCache cache.MultiCatalogOperatorFinder, visited map[*cache.Entry]*BundleVariable) (map[solver.Identifier]struct{}, map[solver.Identifier]*BundleVariable, error) {
   362  	errs := make([]error, 0)
   363  	variables := make(map[solver.Identifier]*BundleVariable) // all variables, including dependencies
   364  
   365  	// track the first layer of variable ids
   366  	var initial = make(map[*cache.Entry]struct{})
   367  	for _, o := range bundleStack {
   368  		initial[o] = struct{}{}
   369  	}
   370  
   371  	for {
   372  		if len(bundleStack) == 0 {
   373  			break
   374  		}
   375  		// pop from the stack
   376  		bundle := bundleStack[len(bundleStack)-1]
   377  		bundleStack = bundleStack[:len(bundleStack)-1]
   378  
   379  		if b, ok := visited[bundle]; ok {
   380  			variables[b.identifier] = b
   381  			continue
   382  		}
   383  
   384  		bundleVariable, err := r.newBundleVariableFromEntry(bundle)
   385  		if err != nil {
   386  			errs = append(errs, err)
   387  			continue
   388  		}
   389  
   390  		visited[bundle] = bundleVariable
   391  
   392  		dependencyPredicates, err := r.pc.convertDependencyProperties(bundle.Properties)
   393  		if err != nil {
   394  			errs = append(errs, err)
   395  			continue
   396  		}
   397  
   398  		for _, d := range dependencyPredicates {
   399  			sourcePredicate := cache.False()
   400  			// Build a filter matching all (catalog,
   401  			// package, channel) combinations that contain
   402  			// at least one candidate bundle, even if only
   403  			// a subset of those bundles actually satisfy
   404  			// the dependency.
   405  			sources := map[cache.OperatorSourceInfo]struct{}{}
   406  			for _, b := range namespacedCache.Find(d) {
   407  				si := b.SourceInfo
   408  
   409  				if _, ok := sources[*si]; ok {
   410  					// Predicate already covers this source.
   411  					continue
   412  				}
   413  				sources[*si] = struct{}{}
   414  
   415  				if si.Catalog.Virtual() {
   416  					sourcePredicate = cache.Or(sourcePredicate, cache.And(
   417  						cache.CSVNamePredicate(b.Name),
   418  						cache.CatalogPredicate(si.Catalog),
   419  					))
   420  				} else {
   421  					sourcePredicate = cache.Or(sourcePredicate, cache.And(
   422  						cache.PkgPredicate(si.Package),
   423  						cache.ChannelPredicate(si.Channel),
   424  						cache.CatalogPredicate(si.Catalog),
   425  					))
   426  				}
   427  			}
   428  
   429  			sortedBundles, err := r.sortBundles(namespacedCache.FindPreferred(&bundle.SourceInfo.Catalog, preferredNamespace, sourcePredicate))
   430  			if err != nil {
   431  				errs = append(errs, err)
   432  				continue
   433  			}
   434  			bundleDependencies := make([]solver.Identifier, 0)
   435  			// The dependency predicate is applied here
   436  			// (after sorting) to remove all bundles that
   437  			// don't satisfy the dependency.
   438  			for _, b := range cache.Filter(sortedBundles, d) {
   439  				i, err := r.newBundleVariableFromEntry(b)
   440  				if err != nil {
   441  					errs = append(errs, err)
   442  					continue
   443  				}
   444  				variables[i.Identifier()] = i
   445  				bundleDependencies = append(bundleDependencies, i.Identifier())
   446  				bundleStack = append(bundleStack, b)
   447  			}
   448  			bundleVariable.AddConstraint(PrettyConstraint(
   449  				solver.Dependency(bundleDependencies...),
   450  				fmt.Sprintf("bundle %s requires an operator %s", bundle.Name, d.String()),
   451  			))
   452  		}
   453  
   454  		variables[bundleVariable.Identifier()] = bundleVariable
   455  	}
   456  
   457  	if len(errs) > 0 {
   458  		return nil, nil, utilerrors.NewAggregate(errs)
   459  	}
   460  
   461  	ids := make(map[solver.Identifier]struct{}) // immediate variables found via predicates
   462  	for o := range initial {
   463  		ids[visited[o].Identifier()] = struct{}{}
   464  	}
   465  
   466  	return ids, variables, nil
   467  }
   468  
   469  func (r *Resolver) addInvariants(namespacedCache cache.MultiCatalogOperatorFinder, variables map[solver.Identifier]solver.Variable) {
   470  	// no two operators may provide the same GVK or Package in a namespace
   471  	gvkConflictToVariable := make(map[opregistry.GVKProperty][]solver.Identifier)
   472  	packageConflictToVariable := make(map[string][]solver.Identifier)
   473  	for _, variable := range variables {
   474  		bundleVariable, ok := variable.(*BundleVariable)
   475  		if !ok {
   476  			continue
   477  		}
   478  		csvName, channel, catalog, err := bundleVariable.BundleSourceInfo()
   479  		if err != nil {
   480  			continue
   481  		}
   482  
   483  		op, err := cache.ExactlyOne(namespacedCache.Catalog(catalog).Find(cache.CSVNamePredicate(csvName), cache.ChannelPredicate(channel)))
   484  		if err != nil {
   485  			continue
   486  		}
   487  
   488  		// cannot provide the same GVK
   489  		for _, p := range op.Properties {
   490  			if p.Type != opregistry.GVKType {
   491  				continue
   492  			}
   493  			var prop opregistry.GVKProperty
   494  			err := json.Unmarshal([]byte(p.Value), &prop)
   495  			if err != nil {
   496  				continue
   497  			}
   498  			gvkConflictToVariable[prop] = append(gvkConflictToVariable[prop], variable.Identifier())
   499  		}
   500  
   501  		// cannot have the same package
   502  		for _, p := range op.Properties {
   503  			if p.Type != opregistry.PackageType {
   504  				continue
   505  			}
   506  			var prop opregistry.PackageProperty
   507  			err := json.Unmarshal([]byte(p.Value), &prop)
   508  			if err != nil {
   509  				continue
   510  			}
   511  			packageConflictToVariable[prop.PackageName] = append(packageConflictToVariable[prop.PackageName], variable.Identifier())
   512  		}
   513  	}
   514  
   515  	for gvk, is := range gvkConflictToVariable {
   516  		s := NewSingleAPIProviderVariable(gvk.Group, gvk.Version, gvk.Kind, is)
   517  		variables[s.Identifier()] = s
   518  	}
   519  
   520  	for pkg, is := range packageConflictToVariable {
   521  		s := NewSinglePackageInstanceVariable(pkg, is)
   522  		variables[s.Identifier()] = s
   523  	}
   524  }
   525  
   526  func (r *Resolver) sortBundles(bundles []*cache.Entry) ([]*cache.Entry, error) {
   527  	// assume bundles have been passed in sorted by catalog already
   528  	catalogOrder := make([]cache.SourceKey, 0)
   529  
   530  	type PackageChannel struct {
   531  		Package, Channel string
   532  		DefaultChannel   bool
   533  	}
   534  	// TODO: for now channels will be sorted lexicographically
   535  	channelOrder := make(map[cache.SourceKey][]PackageChannel)
   536  
   537  	// partition by catalog -> channel -> bundle
   538  	partitionedBundles := map[cache.SourceKey]map[PackageChannel][]*cache.Entry{}
   539  	for _, b := range bundles {
   540  		pc := PackageChannel{
   541  			Package:        b.Package(),
   542  			Channel:        b.Channel(),
   543  			DefaultChannel: b.SourceInfo.DefaultChannel,
   544  		}
   545  		if _, ok := partitionedBundles[b.SourceInfo.Catalog]; !ok {
   546  			catalogOrder = append(catalogOrder, b.SourceInfo.Catalog)
   547  			partitionedBundles[b.SourceInfo.Catalog] = make(map[PackageChannel][]*cache.Entry)
   548  		}
   549  		if _, ok := partitionedBundles[b.SourceInfo.Catalog][pc]; !ok {
   550  			channelOrder[b.SourceInfo.Catalog] = append(channelOrder[b.SourceInfo.Catalog], pc)
   551  			partitionedBundles[b.SourceInfo.Catalog][pc] = make([]*cache.Entry, 0)
   552  		}
   553  		partitionedBundles[b.SourceInfo.Catalog][pc] = append(partitionedBundles[b.SourceInfo.Catalog][pc], b)
   554  	}
   555  
   556  	for catalog := range partitionedBundles {
   557  		sort.SliceStable(channelOrder[catalog], func(i, j int) bool {
   558  			pi, pj := channelOrder[catalog][i], channelOrder[catalog][j]
   559  			if pi.DefaultChannel != pj.DefaultChannel {
   560  				return pi.DefaultChannel
   561  			}
   562  			if pi.Package != pj.Package {
   563  				return pi.Package < pj.Package
   564  			}
   565  			return pi.Channel < pj.Channel
   566  		})
   567  		for channel := range partitionedBundles[catalog] {
   568  			sorted, err := sortChannel(partitionedBundles[catalog][channel])
   569  			if err != nil {
   570  				return nil, err
   571  			}
   572  			partitionedBundles[catalog][channel] = sorted
   573  		}
   574  	}
   575  	all := make([]*cache.Entry, 0)
   576  	for _, catalog := range catalogOrder {
   577  		for _, channel := range channelOrder[catalog] {
   578  			all = append(all, partitionedBundles[catalog][channel]...)
   579  		}
   580  	}
   581  	return all, nil
   582  }
   583  
   584  // Sorts bundle in a channel by replaces. All entries in the argument
   585  // are assumed to have the same Package and Channel.
   586  func sortChannel(bundles []*cache.Entry) ([]*cache.Entry, error) {
   587  	if len(bundles) < 1 {
   588  		return bundles, nil
   589  	}
   590  
   591  	packageName := bundles[0].Package()
   592  	channelName := bundles[0].Channel()
   593  
   594  	bundleLookup := map[string]*cache.Entry{}
   595  
   596  	// if a replaces b, then replacedBy[b] = a
   597  	replacedBy := map[*cache.Entry]*cache.Entry{}
   598  	replaces := map[*cache.Entry]*cache.Entry{}
   599  	skipped := map[string]*cache.Entry{}
   600  
   601  	for _, b := range bundles {
   602  		bundleLookup[b.Name] = b
   603  	}
   604  
   605  	for _, b := range bundles {
   606  		if b.Replaces != "" {
   607  			if r, ok := bundleLookup[b.Replaces]; ok {
   608  				replacedBy[r] = b
   609  				replaces[b] = r
   610  			}
   611  		}
   612  		for _, skip := range b.Skips {
   613  			if r, ok := bundleLookup[skip]; ok {
   614  				replacedBy[r] = b
   615  				skipped[skip] = r
   616  			}
   617  		}
   618  	}
   619  
   620  	// a bundle without a replacement is a channel head, but if we
   621  	// find more than one of those something is weird
   622  	headCandidates := []*cache.Entry{}
   623  	for _, b := range bundles {
   624  		if _, ok := replacedBy[b]; !ok {
   625  			headCandidates = append(headCandidates, b)
   626  		}
   627  	}
   628  	if len(headCandidates) == 0 {
   629  		return nil, fmt.Errorf("no channel heads (entries not replaced by another entry) found in channel %q of package %q", channelName, packageName)
   630  	}
   631  
   632  	var chains [][]*cache.Entry
   633  	for _, head := range headCandidates {
   634  		var chain []*cache.Entry
   635  		visited := make(map[*cache.Entry]struct{})
   636  		current := head
   637  		for {
   638  			visited[current] = struct{}{}
   639  			if _, ok := skipped[current.Name]; !ok {
   640  				chain = append(chain, current)
   641  			}
   642  			next, ok := replaces[current]
   643  			if !ok {
   644  				break
   645  			}
   646  			if _, ok := visited[next]; ok {
   647  				return nil, fmt.Errorf("a cycle exists in the chain of replacement beginning with %q in channel %q of package %q", head.Name, channelName, packageName)
   648  			}
   649  			current = next
   650  		}
   651  		chains = append(chains, chain)
   652  	}
   653  
   654  	if len(chains) > 1 {
   655  		schains := make([]string, len(chains))
   656  		for i, chain := range chains {
   657  			switch len(chain) {
   658  			case 0:
   659  				schains[i] = "[]" // Bug?
   660  			case 1:
   661  				schains[i] = chain[0].Name
   662  			default:
   663  				schains[i] = fmt.Sprintf("%s...%s", chain[0].Name, chain[len(chain)-1].Name)
   664  			}
   665  		}
   666  		return nil, fmt.Errorf("a unique replacement chain within a channel is required to determine the relative order between channel entries, but %d replacement chains were found in channel %q of package %q: %s", len(schains), channelName, packageName, strings.Join(schains, ", "))
   667  	}
   668  
   669  	if len(chains) == 0 {
   670  		// Bug?
   671  		return nil, fmt.Errorf("found no replacement chains in channel %q of package %q", channelName, packageName)
   672  	}
   673  
   674  	// TODO: do we care if the channel doesn't include every bundle in the input?
   675  	return chains[0], nil
   676  }
   677  
   678  // predicateConverter configures olm.constraint value -> predicate conversion for the resolver.
   679  type predicateConverter struct {
   680  	celEnv *constraints.CelEnvironment
   681  }
   682  
   683  // convertDependencyProperties converts all known constraint properties to predicates.
   684  func (pc *predicateConverter) convertDependencyProperties(properties []*api.Property) ([]cache.Predicate, error) {
   685  	var predicates []cache.Predicate
   686  	for _, property := range properties {
   687  		predicate, err := pc.predicateForProperty(property)
   688  		if err != nil {
   689  			return nil, err
   690  		}
   691  		if predicate == nil {
   692  			continue
   693  		}
   694  		predicates = append(predicates, predicate)
   695  	}
   696  	return predicates, nil
   697  }
   698  
   699  func (pc *predicateConverter) predicateForProperty(property *api.Property) (cache.Predicate, error) {
   700  	if property == nil {
   701  		return nil, nil
   702  	}
   703  
   704  	// olm.constraint holds all constraint types except legacy types,
   705  	// so defer error handling to its parser.
   706  	if property.Type == constraints.OLMConstraintType {
   707  		return pc.predicateForConstraintProperty(property.Value)
   708  	}
   709  
   710  	// Legacy behavior dictates that unknown properties are ignored. See enhancement for details:
   711  	// https://github.com/operator-framework/enhancements/blob/master/enhancements/compound-bundle-constraints.md
   712  	p, ok := legacyPredicateParsers[property.Type]
   713  	if !ok {
   714  		return nil, nil
   715  	}
   716  	return p(property.Value)
   717  }
   718  
   719  func (pc *predicateConverter) predicateForConstraintProperty(value string) (cache.Predicate, error) {
   720  	constraint, err := constraints.Parse(json.RawMessage([]byte(value)))
   721  	if err != nil {
   722  		return nil, fmt.Errorf("parse olm.constraint: %v", err)
   723  	}
   724  
   725  	preds, err := pc.convertConstraints(constraint)
   726  	if err != nil {
   727  		return nil, fmt.Errorf("convert olm.constraint to resolver predicate: %v", err)
   728  	}
   729  	return preds[0], nil
   730  }
   731  
   732  // convertConstraints creates predicates from each element of constraints, recursing on compound constraints.
   733  // New constraint types added to the constraints package must be handled here.
   734  func (pc *predicateConverter) convertConstraints(constraints ...constraints.Constraint) ([]cache.Predicate, error) {
   735  	preds := make([]cache.Predicate, len(constraints))
   736  	for i, constraint := range constraints {
   737  		var err error
   738  		switch {
   739  		case constraint.GVK != nil:
   740  			preds[i] = cache.ProvidingAPIPredicate(opregistry.APIKey{
   741  				Group:   constraint.GVK.Group,
   742  				Version: constraint.GVK.Version,
   743  				Kind:    constraint.GVK.Kind,
   744  			})
   745  		case constraint.Package != nil:
   746  			preds[i], err = newPackageRequiredPredicate(constraint.Package.PackageName, constraint.Package.VersionRange)
   747  		case constraint.All != nil:
   748  			subs, perr := pc.convertConstraints(constraint.All.Constraints...)
   749  			preds[i], err = cache.And(subs...), perr
   750  		case constraint.Any != nil:
   751  			subs, perr := pc.convertConstraints(constraint.Any.Constraints...)
   752  			preds[i], err = cache.Or(subs...), perr
   753  		case constraint.Not != nil:
   754  			subs, perr := pc.convertConstraints(constraint.Not.Constraints...)
   755  			preds[i], err = cache.Not(subs...), perr
   756  		case constraint.Cel != nil:
   757  			preds[i], err = cache.CreateCelPredicate(pc.celEnv, constraint.Cel.Rule, constraint.FailureMessage)
   758  		default:
   759  			// Unknown constraint types are handled by constraints.Parse(),
   760  			// but parsed constraints may be empty.
   761  			return nil, fmt.Errorf("constraint is empty")
   762  		}
   763  		if err != nil {
   764  			return nil, err
   765  		}
   766  	}
   767  
   768  	return preds, nil
   769  }
   770  
   771  var legacyPredicateParsers = map[string]func(string) (cache.Predicate, error){
   772  	"olm.gvk.required":     predicateForRequiredGVKProperty,
   773  	"olm.package.required": predicateForRequiredPackageProperty,
   774  	"olm.label.required":   predicateForRequiredLabelProperty,
   775  }
   776  
   777  func predicateForRequiredGVKProperty(value string) (cache.Predicate, error) {
   778  	var gvk struct {
   779  		Group   string `json:"group"`
   780  		Version string `json:"version"`
   781  		Kind    string `json:"kind"`
   782  	}
   783  	if err := json.Unmarshal([]byte(value), &gvk); err != nil {
   784  		return nil, err
   785  	}
   786  	return cache.ProvidingAPIPredicate(opregistry.APIKey{
   787  		Group:   gvk.Group,
   788  		Version: gvk.Version,
   789  		Kind:    gvk.Kind,
   790  	}), nil
   791  }
   792  
   793  func predicateForRequiredPackageProperty(value string) (cache.Predicate, error) {
   794  	var pkg struct {
   795  		PackageName  string `json:"packageName"`
   796  		VersionRange string `json:"versionRange"`
   797  	}
   798  	if err := json.Unmarshal([]byte(value), &pkg); err != nil {
   799  		return nil, err
   800  	}
   801  	return newPackageRequiredPredicate(pkg.PackageName, pkg.VersionRange)
   802  }
   803  
   804  func newPackageRequiredPredicate(name, verRange string) (cache.Predicate, error) {
   805  	ver, err := semver.ParseRange(verRange)
   806  	if err != nil {
   807  		return nil, err
   808  	}
   809  	return cache.And(cache.PkgPredicate(name), cache.VersionInRangePredicate(ver, verRange)), nil
   810  }
   811  
   812  func predicateForRequiredLabelProperty(value string) (cache.Predicate, error) {
   813  	var label struct {
   814  		Label string `json:"label"`
   815  	}
   816  	if err := json.Unmarshal([]byte(value), &label); err != nil {
   817  		return nil, err
   818  	}
   819  	return cache.LabelPredicate(label.Label), nil
   820  }
   821  
   822  func providedAPIsToProperties(apis cache.APISet) ([]*api.Property, error) {
   823  	var out []*api.Property
   824  	for a := range apis {
   825  		val, err := json.Marshal(opregistry.GVKProperty{
   826  			Group:   a.Group,
   827  			Version: a.Version,
   828  			Kind:    a.Kind,
   829  		})
   830  		if err != nil {
   831  			panic(err)
   832  		}
   833  		out = append(out, &api.Property{
   834  			Type:  opregistry.GVKType,
   835  			Value: string(val),
   836  		})
   837  	}
   838  	sort.Slice(out, func(i, j int) bool {
   839  		return out[i].Value < out[j].Value
   840  	})
   841  	return out, nil
   842  }
   843  
   844  func requiredAPIsToProperties(apis cache.APISet) ([]*api.Property, error) {
   845  	var out []*api.Property
   846  	for a := range apis {
   847  		val, err := json.Marshal(struct {
   848  			Group   string `json:"group"`
   849  			Version string `json:"version"`
   850  			Kind    string `json:"kind"`
   851  		}{
   852  			Group:   a.Group,
   853  			Version: a.Version,
   854  			Kind:    a.Kind,
   855  		})
   856  		if err != nil {
   857  			return nil, err
   858  		}
   859  		out = append(out, &api.Property{
   860  			Type:  "olm.gvk.required",
   861  			Value: string(val),
   862  		})
   863  	}
   864  	sort.Slice(out, func(i, j int) bool {
   865  		return out[i].Value < out[j].Value
   866  	})
   867  	return out, nil
   868  }