Skip to content

Commit 59d2d86

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 7a9e1af commit 59d2d86

File tree

5 files changed

+182
-0
lines changed

5 files changed

+182
-0
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
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

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"fmt"
77

88
"github.com/blang/semver/v4"
9+
10+
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/constraints"
911
opregistry "github.com/operator-framework/operator-registry/pkg/registry"
1012
)
1113

@@ -354,3 +356,41 @@ func (c countingPredicate) String() string {
354356
func CountingPredicate(p Predicate, n *int) Predicate {
355357
return countingPredicate{p: p, n: n}
356358
}
359+
360+
type evaluatorPredicate struct {
361+
evaluator constraints.Evaluator
362+
rule string
363+
message string
364+
}
365+
366+
func (ep *evaluatorPredicate) Test(entry *Entry) bool {
367+
props := make([]map[string]interface{}, len(entry.Properties))
368+
for i, p := range entry.Properties {
369+
var v interface{}
370+
if err := json.Unmarshal([]byte(p.Value), &v); err != nil {
371+
continue
372+
}
373+
props[i] = map[string]interface{}{
374+
"type": p.Type,
375+
"value": v,
376+
}
377+
}
378+
379+
ok, err := ep.evaluator.Evaluate(map[string]interface{}{"properties": props})
380+
if err != nil {
381+
return false
382+
}
383+
return ok
384+
}
385+
386+
func EvaluatorPredicate(provider constraints.EvaluatorProvider, rule, message string) (Predicate, error) {
387+
eval, err := provider.Evaluator(rule)
388+
if err != nil {
389+
return nil, err
390+
}
391+
return &evaluatorPredicate{evaluator: eval, rule: rule, message: message}, nil
392+
}
393+
394+
func (ep *evaluatorPredicate) String() string {
395+
return fmt.Sprintf("with constraint: %q and message: %q", ep.rule, ep.message)
396+
}
Lines changed: 113 additions & 0 deletions
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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/operator-framework/api/pkg/operators/v1alpha1"
1616
v1alpha1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
1717
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
18+
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/constraints"
1819
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection"
1920
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/solver"
2021
"github.com/operator-framework/operator-registry/pkg/api"
@@ -346,6 +347,30 @@ func (r *SatResolver) getBundleInstallables(preferredNamespace string, bundleSta
346347
continue
347348
}
348349

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

vendor/modules.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ github.com/golang/protobuf/ptypes/wrappers
290290
# github.com/google/btree v1.0.1
291291
github.com/google/btree
292292
# github.com/google/cel-go v0.9.0
293+
## explicit
293294
github.com/google/cel-go/cel
294295
github.com/google/cel-go/checker
295296
github.com/google/cel-go/checker/decls
@@ -856,6 +857,7 @@ google.golang.org/appengine/internal/remote_api
856857
google.golang.org/appengine/internal/urlfetch
857858
google.golang.org/appengine/urlfetch
858859
# google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2
860+
## explicit
859861
google.golang.org/genproto/googleapis/api/annotations
860862
google.golang.org/genproto/googleapis/api/expr/v1alpha1
861863
google.golang.org/genproto/googleapis/api/httpbody

0 commit comments

Comments
 (0)