Skip to content

Commit

Permalink
Update CEL configurations to support current k8s 1.30 base
Browse files Browse the repository at this point in the history
Signed-off-by: Kevin Conner <kev.conner@gmail.com>
  • Loading branch information
knrc committed Nov 28, 2023
1 parent 56b611b commit e71149a
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 59 deletions.
35 changes: 32 additions & 3 deletions eval/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ import (
"reflect"

"github.com/google/cel-go/cel"
"github.com/google/cel-go/checker"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/ext"
"github.com/google/cel-go/interpreter"
"google.golang.org/protobuf/types/known/structpb"
k8s "k8s.io/apiserver/pkg/cel/library"
)
Expand All @@ -32,19 +34,46 @@ type EvalResponse struct {
}

var celEnvOptions = []cel.EnvOption{
// 1.0 (1.23)
cel.HomogeneousAggregateLiterals(),
cel.EagerlyValidateDeclarations(true),
cel.DefaultUTCTimeZone(true),
ext.Strings(ext.StringsVersion(2)),
cel.CrossTypeNumericComparisons(true),
cel.OptionalTypes(),
k8s.URLs(),
k8s.Regex(),
k8s.Lists(),

// 1.27
// k8s.Authz(),

// 1.28
cel.CrossTypeNumericComparisons(true),
cel.OptionalTypes(),
k8s.Quantity(),

// 1.29 (see also validator.ExtendedValidations())
cel.ASTValidators(
cel.ValidateDurationLiterals(),
cel.ValidateTimestampLiterals(),
cel.ValidateRegexLiterals(),
cel.ValidateHomogeneousAggregateLiterals(),
),

// Strings (from 1.29 onwards)
ext.Strings(ext.StringsVersion(2)),
// Set library (1.29 onwards)
ext.Sets(),

// cel-go v0.17.7 introduced CostEstimatorOptions.
// Previous the presence has a cost of 0 but cel fixed it to 1. We still set to 0 here to avoid breaking changes.
cel.CostEstimatorOptions(checker.PresenceTestHasCost(false)),
}

var celProgramOptions = []cel.ProgramOption{
cel.EvalOptions(cel.OptOptimize, cel.OptTrackCost),

// cel-go v0.17.7 introduced CostTrackerOptions.
// Previous the presence has a cost of 0 but cel fixed it to 1. We still set to 0 here to avoid breaking changes.
cel.CostTrackerOptions(interpreter.PresenceTestHasCost(false)),
}

// Eval evaluates the cel expression against the given input
Expand Down
206 changes: 206 additions & 0 deletions eval/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import (
"encoding/json"
"reflect"
"testing"

"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
)

var input = map[string]any{
Expand Down Expand Up @@ -95,6 +98,62 @@ func TestEval(t *testing.T) {
exp: `isQuantity(object.memory) && quantity(object.memory).add(quantity("700M")).sub(1).isLessThan(quantity("2G"))`,
want: true,
},
{
name: "sets.contains test 1",
exp: `sets.contains([], [])`,
want: true,
},
{
name: "sets.contains test 2",
exp: `sets.contains([], [1])`,
want: false,
},
{
name: "sets.contains test 3",
exp: `sets.contains([1, 2, 3, 4], [2, 3])`,
want: true,
},
{
name: "sets.contains test 4",
exp: `sets.contains([1, 2, 3], [3, 2, 1])`,
want: true,
},
{
name: "sets.equivalent test 1",
exp: `sets.equivalent([], [])`,
want: true,
},
{
name: "sets.equivalent test 2",
exp: `sets.equivalent([1], [1, 1])`,
want: true,
},
{
name: "sets.equivalent test 3",
exp: `sets.equivalent([1], [1, 1])`,
want: true,
},
{
name: "sets.equivalent test 4",
exp: `sets.equivalent([1, 2, 3], [3, 2, 1])`,
want: true,
},

{
name: "sets.intersects test 1",
exp: `sets.intersects([1], [])`,
want: false,
},
{
name: "sets.intersects test 2",
exp: `sets.intersects([1], [1, 2])`,
want: true,
},
{
name: "sets.intersects test 3",
exp: `sets.intersects([[1], [2, 3]], [[1, 2], [2, 3]])`,
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -121,3 +180,150 @@ func TestEval(t *testing.T) {
})
}
}

func TestValidation(t *testing.T) {
tests := []struct {
name string
exp string
wantErr bool
}{
// Duration Literals
{
name: "Duration Validation test 1",
exp: `duration('1')`,
wantErr: true,
},
{
name: "Duration Validation test 2",
exp: `duration('1d')`,
wantErr: true,
},
{
name: "Duration Validation test 3",
exp: `duration('1us') < duration('1nns')`,
wantErr: true,
},
{
name: "Duration Validation test 4",
exp: `duration('2h3m4s5us')`,
},
{
name: "Duration Validation test 5",
exp: `duration(x)`,
},

// Timestamp Literals
{
name: "Timestamp Validation test 1",
exp: `timestamp('1000-00-00T00:00:00Z')`,
wantErr: true,
},
{
name: "Timestamp Validation test 2",
exp: `timestamp('1000-01-01T00:00:00ZZ')`,
wantErr: true,
},
{
name: "Timestamp Validation test 3",
exp: `timestamp('1000-01-01T00:00:00Z')`,
},
{
name: "Timestamp Validation test 4",
exp: `timestamp(-6213559680)`, // min unix epoch time.
},
{
name: "Timestamp Validation test 5",
exp: `timestamp(-62135596801)`,
wantErr: true,
},
{
name: "Timestamp Validation test 6",
exp: `timestamp(x)`,
},

// Regex Literals
{
name: "Regex Validation test 1",
exp: `'hello'.matches('el*')`,
},
{
name: "Regex Validation test 2",
exp: `'hello'.matches('x++')`,
wantErr: true,
},
{
name: "Regex Validation test 3",
exp: `'hello'.matches('(?<name%>el*)')`,
wantErr: true,
},
{
name: "Regex Validation test 4",
exp: `'hello'.matches('??el*')`,
wantErr: true,
},
{
name: "Regex Validation test 5",
exp: `'hello'.matches(x)`,
},

// Homogeneous Aggregate Literals
{
name: "Homogeneous Aggregate Validation test 1",
exp: `name in ['hello', 0]`,
wantErr: true,
},
{
name: "Homogeneous Aggregate Validation test 2",
exp: `{'hello':'world', 1:'!'}`,
wantErr: true,
},
{
name: "Homogeneous Aggregate Validation test 3",
exp: `name in {'hello':'world', 'goodbye':true}`,
wantErr: true,
},
{
name: "Homogeneous Aggregate Validation test 4",
exp: `name in ['hello', 'world']`,
},
{
name: "Homogeneous Aggregate Validation test 5",
exp: `name in ['hello', ?optional.ofNonZeroValue('')]`,
},
{
name: "Homogeneous Aggregate Validation test 6",
exp: `name in [?optional.ofNonZeroValue(''), 'hello', ?optional.of('')]`,
},
{
name: "Homogeneous Aggregate Validation test 7",
exp: `name in {'hello': false, 'world': true}`,
},
{
name: "Homogeneous Aggregate Validation test 8",
exp: `{'hello': false, ?'world': optional.ofNonZeroValue(true)}`,
},
{
name: "Homogeneous Aggregate Validation test 9",
exp: `{?'hello': optional.ofNonZeroValue(false), 'world': true}`,
},
}
env, err := cel.NewEnv(append(celEnvOptions,
cel.Variable("x", types.StringType),
cel.Variable("name", types.StringType),
)...)
if err != nil {
t.Errorf("failed to create CEL env: %v", err)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, issues := env.Compile(tt.exp)
if tt.wantErr {
if issues.Err() == nil {
t.Fatalf("Compilation should have failed, expr: %v", tt.exp)
}
} else if issues.Err() != nil {
t.Fatalf("Compilation failed, expr: %v, error: %v", tt.exp, issues.Err())
}
})
}
}
37 changes: 19 additions & 18 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,23 @@ module github.com/undistro/cel-playground
go 1.21

require (
github.com/google/cel-go v0.16.0
github.com/google/cel-go v0.17.7
google.golang.org/protobuf v1.31.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/apiserver v0.28.1
k8s.io/apiserver v0.28.4
)

require (
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
Expand All @@ -31,24 +30,26 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
golang.org/x/net v0.13.0 // indirect
golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/term v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/oauth2 v0.10.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/api v0.28.1 // indirect
k8s.io/apimachinery v0.28.1 // indirect
k8s.io/client-go v0.28.1 // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect
k8s.io/api v0.28.4 // indirect
k8s.io/apimachinery v0.28.4 // indirect
k8s.io/client-go v0.28.4 // indirect
k8s.io/klog/v2 v2.110.1 // indirect
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

replace k8s.io/apiserver v0.28.4 => github.com/kubernetes/kubernetes/staging/src/k8s.io/apiserver v0.0.0-20231128103858-022d50fe3a1b
Loading

0 comments on commit e71149a

Please sign in to comment.