Skip to content

feat(resolver): olm.constraint property and compound constraints #2418

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPp
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e h1:GCzyKMDDjSGnlpl3clrdAK7I1AaVoaiKDOYkUzChZzg=
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
Expand Down Expand Up @@ -553,6 +554,7 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/cel-go v0.9.0 h1:u1hg7lcZ/XWw2d3aV1jFS30ijQQ6q0/h1C2ZBeBD1gY=
github.com/google/cel-go v0.9.0/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w=
github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
Expand Down Expand Up @@ -1075,6 +1077,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
Expand Down
27 changes: 26 additions & 1 deletion pkg/controller/registry/resolver/cache/predicates.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"fmt"

"github.com/blang/semver/v4"

opregistry "github.com/operator-framework/operator-registry/pkg/registry"
)

Expand Down Expand Up @@ -282,6 +281,32 @@ func (p orPredicate) String() string {
return b.String()
}

func Not(p ...Predicate) Predicate {
return notPredicate{
predicates: p,
}
}

type notPredicate struct {
predicates []Predicate
}

func (p notPredicate) Test(o *Entry) bool {
// !pred && !pred is equivalent to !(pred || pred).
return !orPredicate{p.predicates}.Test(o)
}

func (p notPredicate) String() string {
var b bytes.Buffer
for i, predicate := range p.predicates {
b.WriteString(predicate.String())
if i != len(p.predicates)-1 {
b.WriteString(" and not ")
}
}
return b.String()
}

type booleanPredicate struct {
result bool
}
Expand Down
89 changes: 81 additions & 8 deletions pkg/controller/registry/resolver/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/sirupsen/logrus"
utilerrors "k8s.io/apimachinery/pkg/util/errors"

"github.com/operator-framework/api/pkg/constraints"
"github.com/operator-framework/api/pkg/operators/v1alpha1"
v1alpha1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
Expand All @@ -27,12 +28,14 @@ type OperatorResolver interface {
type SatResolver struct {
cache cache.OperatorCacheProvider
log logrus.FieldLogger
pc *predicateConverter
}

func NewDefaultSatResolver(rcp cache.SourceProvider, catsrcLister v1alpha1listers.CatalogSourceLister, logger logrus.FieldLogger) *SatResolver {
return &SatResolver{
cache: cache.New(rcp, cache.WithLogger(logger), cache.WithCatalogSourceLister(catsrcLister)),
log: logger,
pc: &predicateConverter{},
}
}

Expand Down Expand Up @@ -337,7 +340,7 @@ func (r *SatResolver) getBundleInstallables(preferredNamespace string, bundleSta

visited[bundle] = &bundleInstallable

dependencyPredicates, err := DependencyPredicates(bundle.Properties)
dependencyPredicates, err := r.pc.convertDependencyProperties(bundle.Properties)
if err != nil {
errs = append(errs, err)
continue
Expand Down Expand Up @@ -733,10 +736,14 @@ func sortChannel(bundles []*cache.Entry) ([]*cache.Entry, error) {
return chains[0], nil
}

func DependencyPredicates(properties []*api.Property) ([]cache.Predicate, error) {
// predicateConverter configures olm.constraint value -> predicate conversion for the resolver.
type predicateConverter struct{}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will contain fields when CEL is added.


// convertDependencyProperties converts all known constraint properties to predicates.
func (pc *predicateConverter) convertDependencyProperties(properties []*api.Property) ([]cache.Predicate, error) {
var predicates []cache.Predicate
for _, property := range properties {
predicate, err := predicateForProperty(property)
predicate, err := pc.predicateForProperty(property)
if err != nil {
return nil, err
}
Expand All @@ -748,18 +755,80 @@ func DependencyPredicates(properties []*api.Property) ([]cache.Predicate, error)
return predicates, nil
}

func predicateForProperty(property *api.Property) (cache.Predicate, error) {
func (pc *predicateConverter) predicateForProperty(property *api.Property) (cache.Predicate, error) {
if property == nil {
return nil, nil
}
p, ok := predicates[property.Type]

// olm.constraint holds all constraint types except legacy types,
// so defer error handling to its parser.
if property.Type == constraints.OLMConstraintType {
return pc.predicateForConstraintProperty(property.Value)
}

// Legacy behavior dictates that unknown properties are ignored. See enhancement for details:
// https://github.com/operator-framework/enhancements/blob/master/enhancements/compound-bundle-constraints.md
p, ok := legacyPredicateParsers[property.Type]
if !ok {
return nil, nil
}
return p(property.Value)
}

var predicates = map[string]func(string) (cache.Predicate, error){
func (pc *predicateConverter) predicateForConstraintProperty(value string) (cache.Predicate, error) {
constraint, err := constraints.Parse(json.RawMessage([]byte(value)))
if err != nil {
return nil, fmt.Errorf("parse olm.constraint: %v", err)
}

preds, err := pc.convertConstraints(constraint)
if err != nil {
return nil, fmt.Errorf("convert olm.constraint to resolver predicate: %v", err)
}
return preds[0], nil
}

// convertConstraints creates predicates from each element of constraints, recursing on compound constraints.
// New constraint types added to the constraints package must be handled here.
func (pc *predicateConverter) convertConstraints(constraints ...constraints.Constraint) ([]cache.Predicate, error) {

preds := make([]cache.Predicate, len(constraints))
for i, constraint := range constraints {

var err error
switch {
case constraint.GVK != nil:
preds[i] = cache.ProvidingAPIPredicate(opregistry.APIKey{
Group: constraint.GVK.Group,
Version: constraint.GVK.Version,
Kind: constraint.GVK.Kind,
})
case constraint.Package != nil:
preds[i], err = newPackageRequiredPredicate(constraint.Package.PackageName, constraint.Package.VersionRange)
case constraint.All != nil:
subs, perr := pc.convertConstraints(constraint.All.Constraints...)
preds[i], err = cache.And(subs...), perr
case constraint.Any != nil:
subs, perr := pc.convertConstraints(constraint.Any.Constraints...)
preds[i], err = cache.Or(subs...), perr
case constraint.None != nil:
subs, perr := pc.convertConstraints(constraint.None.Constraints...)
preds[i], err = cache.Not(subs...), perr
default:
// Unknown constraint types are handled by constraints.Parse(),
// but parsed constraints may be empty.
return nil, fmt.Errorf("constraint is empty")
}
if err != nil {
return nil, err
}

}

return preds, nil
}

var legacyPredicateParsers = map[string]func(string) (cache.Predicate, error){
"olm.gvk.required": predicateForRequiredGVKProperty,
"olm.package.required": predicateForRequiredPackageProperty,
"olm.label.required": predicateForRequiredLabelProperty,
Expand Down Expand Up @@ -789,11 +858,15 @@ func predicateForRequiredPackageProperty(value string) (cache.Predicate, error)
if err := json.Unmarshal([]byte(value), &pkg); err != nil {
return nil, err
}
ver, err := semver.ParseRange(pkg.VersionRange)
return newPackageRequiredPredicate(pkg.PackageName, pkg.VersionRange)
}

func newPackageRequiredPredicate(name, verRange string) (cache.Predicate, error) {
ver, err := semver.ParseRange(verRange)
if err != nil {
return nil, err
}
return cache.And(cache.PkgPredicate(pkg.PackageName), cache.VersionInRangePredicate(ver, pkg.VersionRange)), nil
return cache.And(cache.PkgPredicate(name), cache.VersionInRangePredicate(ver, verRange)), nil
}

func predicateForRequiredLabelProperty(value string) (cache.Predicate, error) {
Expand Down
Loading