Skip to content

Commit a7ef2b2

Browse files
committed
feat(resolver): support generic constraint using CEL
Introduce the new type of constraint that uses CEL (Common Expression Language) as an expression language to define the constraint in a generic way. This type of constraint will allow operator authors to specify a dependency on any arbitrary properties in the bundle. This constraint also supports logical operator such as AND and OR in the CEL expression. Signed-off-by: Vu Dinh <[email protected]>
1 parent 82fbb1a commit a7ef2b2

File tree

186 files changed

+46223
-4
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

186 files changed

+46223
-4
lines changed

go.mod

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ require (
1414
github.com/go-bindata/go-bindata/v3 v3.1.3
1515
github.com/go-logr/logr v0.4.0
1616
github.com/golang/mock v1.5.0
17+
github.com/google/cel-go v0.9.0
1718
github.com/google/go-cmp v0.5.6
1819
github.com/googleapis/gnostic v0.5.5
1920
github.com/itchyny/gojq v0.11.0
@@ -38,6 +39,7 @@ require (
3839
github.com/stretchr/testify v1.7.0
3940
golang.org/x/net v0.0.0-20210825183410-e898025ed96a
4041
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
42+
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2
4143
google.golang.org/grpc v1.40.0
4244
gopkg.in/yaml.v2 v2.4.0
4345
helm.sh/helm/v3 v3.6.1

go.sum

+3
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPp
130130
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
131131
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
132132
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
133+
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e h1:GCzyKMDDjSGnlpl3clrdAK7I1AaVoaiKDOYkUzChZzg=
133134
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
134135
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
135136
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
@@ -553,6 +554,7 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
553554
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
554555
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
555556
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
557+
github.com/google/cel-go v0.9.0 h1:u1hg7lcZ/XWw2d3aV1jFS30ijQQ6q0/h1C2ZBeBD1gY=
556558
github.com/google/cel-go v0.9.0/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w=
557559
github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA=
558560
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -1075,6 +1077,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
10751077
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
10761078
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
10771079
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
1080+
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
10781081
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
10791082
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
10801083
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=

pkg/controller/registry/resolver/cache/predicates.go

+39
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/blang/semver/v4"
99

10+
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/constraints"
1011
opregistry "github.com/operator-framework/operator-registry/pkg/registry"
1112
)
1213

@@ -329,3 +330,41 @@ func (c countingPredicate) String() string {
329330
func CountingPredicate(p Predicate, n *int) Predicate {
330331
return countingPredicate{p: p, n: n}
331332
}
333+
334+
type evaluatorPredicate struct {
335+
evaluator constraints.Evaluator
336+
rule string
337+
message string
338+
}
339+
340+
func (ep *evaluatorPredicate) Test(entry *Entry) bool {
341+
props := make([]map[string]interface{}, len(entry.Properties))
342+
for i, p := range entry.Properties {
343+
var v interface{}
344+
if err := json.Unmarshal([]byte(p.Value), &v); err != nil {
345+
continue
346+
}
347+
props[i] = map[string]interface{}{
348+
"type": p.Type,
349+
"value": v,
350+
}
351+
}
352+
353+
ok, err := ep.evaluator.Evaluate(map[string]interface{}{"properties": props})
354+
if err != nil {
355+
return false
356+
}
357+
return ok
358+
}
359+
360+
func EvaluatorPredicate(provider constraints.EvaluatorProvider, rule, message string) (Predicate, error) {
361+
eval, err := provider.Evaluator(rule)
362+
if err != nil {
363+
return nil, err
364+
}
365+
return &evaluatorPredicate{evaluator: eval, rule: rule, message: message}, nil
366+
}
367+
368+
func (ep *evaluatorPredicate) String() string {
369+
return fmt.Sprintf("with constraint: %q and message: %q", ep.rule, ep.message)
370+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package constraints
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/google/cel-go/cel"
7+
"github.com/google/cel-go/checker/decls"
8+
"github.com/google/cel-go/common/types"
9+
"github.com/google/cel-go/common/types/ref"
10+
"github.com/google/cel-go/interpreter/functions"
11+
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
12+
13+
"github.com/blang/semver/v4"
14+
)
15+
16+
type Cel struct {
17+
Rule string
18+
}
19+
20+
type Evaluator interface {
21+
Evaluate(env map[string]interface{}) (bool, error)
22+
}
23+
24+
type EvaluatorProvider interface {
25+
Evaluator(rule string) (Evaluator, error)
26+
}
27+
28+
func NewCelEvaluatorProvider() *celEvaluatorProvider {
29+
env, err := cel.NewEnv(cel.Declarations(
30+
decls.NewVar("properties", decls.NewListType(decls.NewMapType(decls.String, decls.Any)))),
31+
cel.Lib(semverLib{}),
32+
)
33+
if err != nil {
34+
panic(err)
35+
}
36+
return &celEvaluatorProvider{
37+
env: env,
38+
}
39+
}
40+
41+
type celEvaluatorProvider struct {
42+
env *cel.Env
43+
}
44+
45+
type celEvaluator struct {
46+
p cel.Program
47+
}
48+
49+
type semverLib struct{}
50+
51+
func (semverLib) CompileOptions() []cel.EnvOption {
52+
return []cel.EnvOption{
53+
cel.Declarations(
54+
decls.NewFunction("semver_compare",
55+
decls.NewOverload("semver_compare",
56+
[]*exprpb.Type{decls.Any, decls.Any},
57+
decls.Int))),
58+
}
59+
}
60+
61+
func (semverLib) ProgramOptions() []cel.ProgramOption {
62+
return []cel.ProgramOption{
63+
cel.Functions(
64+
&functions.Overload{
65+
Operator: "semver_compare",
66+
Binary: semverCompare,
67+
},
68+
),
69+
}
70+
}
71+
72+
func (e celEvaluator) Evaluate(env map[string]interface{}) (bool, error) {
73+
result, _, err := e.p.Eval(env)
74+
if err != nil {
75+
return false, err
76+
}
77+
78+
// we should have already ensured that this will be types.Bool during compilation
79+
if b, ok := result.Value().(bool); ok {
80+
return b, nil
81+
}
82+
return false, fmt.Errorf("cel expression evalutated to %T, not bool", result.Value())
83+
}
84+
85+
func (e *celEvaluatorProvider) Evaluator(rule string) (Evaluator, error) {
86+
ast, issues := e.env.Compile(rule)
87+
if err := issues.Err(); err != nil {
88+
return nil, err
89+
}
90+
91+
if ast.ResultType() != decls.Bool {
92+
return nil, fmt.Errorf("cel expressions must have type Bool")
93+
}
94+
95+
p, err := e.env.Program(ast)
96+
if err != nil {
97+
return nil, err
98+
}
99+
return celEvaluator{p: p}, nil
100+
}
101+
102+
func semverCompare(val1, val2 ref.Val) ref.Val {
103+
v1, err := semver.ParseTolerant(fmt.Sprint(val1.Value()))
104+
if err != nil {
105+
return types.ValOrErr(val1, "unable to parse '%v' to semver format", val1.Value())
106+
}
107+
108+
v2, err := semver.ParseTolerant(fmt.Sprint(val2.Value()))
109+
if err != nil {
110+
return types.ValOrErr(val2, "unable to parse '%v' to semver format", val2.Value())
111+
}
112+
return types.Int(v1.Compare(v2))
113+
}

pkg/controller/registry/resolver/resolver.go

+31-4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/operator-framework/api/pkg/operators/v1alpha1"
1515
v1alpha1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
1616
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
17+
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/constraints"
1718
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection"
1819
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver"
1920
"github.com/operator-framework/operator-registry/pkg/api"
@@ -25,14 +26,16 @@ type OperatorResolver interface {
2526
}
2627

2728
type SatResolver struct {
28-
cache cache.OperatorCacheProvider
29-
log logrus.FieldLogger
29+
cache cache.OperatorCacheProvider
30+
log logrus.FieldLogger
31+
evaluatorProvider constraints.EvaluatorProvider
3032
}
3133

3234
func NewDefaultSatResolver(rcp cache.SourceProvider, catsrcLister v1alpha1listers.CatalogSourceLister, logger logrus.FieldLogger) *SatResolver {
3335
return &SatResolver{
34-
cache: cache.New(rcp, cache.WithLogger(logger), cache.WithCatalogSourceLister(catsrcLister)),
35-
log: logger,
36+
cache: cache.New(rcp, cache.WithLogger(logger), cache.WithCatalogSourceLister(catsrcLister)),
37+
log: logger,
38+
evaluatorProvider: constraints.NewCelEvaluatorProvider(),
3639
}
3740
}
3841

@@ -343,6 +346,30 @@ func (r *SatResolver) getBundleInstallables(preferredNamespace string, bundleSta
343346
continue
344347
}
345348

349+
for _, prop := range bundle.Properties {
350+
if prop.Type != "olm.constraint" {
351+
continue
352+
}
353+
354+
var val struct {
355+
Message string `json:"message"`
356+
Cel *constraints.Cel `json:"cel"`
357+
}
358+
359+
if err := json.Unmarshal([]byte(prop.Value), &val); err != nil {
360+
errs = append(errs, err)
361+
continue
362+
}
363+
364+
pred, err := cache.EvaluatorPredicate(r.evaluatorProvider, val.Cel.Rule, val.Message)
365+
if err != nil {
366+
errs = append(errs, err)
367+
continue
368+
}
369+
370+
dependencyPredicates = append(dependencyPredicates, pred)
371+
}
372+
346373
for _, d := range dependencyPredicates {
347374
sourcePredicate := cache.False()
348375
// Build a filter matching all (catalog,

vendor/github.com/antlr/antlr4/runtime/Go/antlr/LICENSE

+26
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)