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 }