mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-12 13:31:52 +00:00
Add CEL library lifecycle linter
This commit is contained in:
parent
3fb14cf4e7
commit
980fa6a2b9
@ -66,13 +66,6 @@ var baseOpts = []VersionedOptions{
|
|||||||
cel.CostLimit(celconfig.PerCallLimit),
|
cel.CostLimit(celconfig.PerCallLimit),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
IntroducedVersion: version.MajorMinor(1, 0),
|
|
||||||
RemovedVersion: version.MajorMinor(1, 29),
|
|
||||||
EnvOptions: []cel.EnvOption{
|
|
||||||
ext.Strings(ext.StringsVersion(0)),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
IntroducedVersion: version.MajorMinor(1, 27),
|
IntroducedVersion: version.MajorMinor(1, 27),
|
||||||
EnvOptions: []cel.EnvOption{
|
EnvOptions: []cel.EnvOption{
|
||||||
@ -87,6 +80,15 @@ var baseOpts = []VersionedOptions{
|
|||||||
library.Quantity(),
|
library.Quantity(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// String library
|
||||||
|
{
|
||||||
|
IntroducedVersion: version.MajorMinor(1, 0),
|
||||||
|
RemovedVersion: version.MajorMinor(1, 29),
|
||||||
|
EnvOptions: []cel.EnvOption{
|
||||||
|
ext.Strings(ext.StringsVersion(0)),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
IntroducedVersion: version.MajorMinor(1, 29),
|
IntroducedVersion: version.MajorMinor(1, 29),
|
||||||
EnvOptions: []cel.EnvOption{
|
EnvOptions: []cel.EnvOption{
|
||||||
|
@ -17,8 +17,11 @@ limitations under the License.
|
|||||||
package environment
|
package environment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/cel-go/cel"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/version"
|
"k8s.io/apimachinery/pkg/util/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,3 +44,90 @@ func BenchmarkLoadBaseEnvDifferentVersions(b *testing.B) {
|
|||||||
MustBaseEnvSet(version.MajorMinor(1, uint(i)))
|
MustBaseEnvSet(version.MajorMinor(1, uint(i)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestLibraryCoverage lints the management of libraries in baseOpts by
|
||||||
|
// checking for:
|
||||||
|
//
|
||||||
|
// - No gaps and overlap in library inclusion, including when libraries are version bumped
|
||||||
|
// - RemovedVersion is always greater than IntroducedVersion
|
||||||
|
// - Libraries are not removed once added (although they can be replaced with new versions)
|
||||||
|
func TestLibraryCoverage(t *testing.T) {
|
||||||
|
vops := make([]VersionedOptions, len(baseOpts))
|
||||||
|
copy(vops, baseOpts)
|
||||||
|
sort.SliceStable(vops, func(i, j int) bool {
|
||||||
|
return vops[i].IntroducedVersion.LessThan(vops[j].IntroducedVersion)
|
||||||
|
})
|
||||||
|
|
||||||
|
tracked := map[string]*versionTracker{}
|
||||||
|
|
||||||
|
for _, vop := range vops {
|
||||||
|
if vop.RemovedVersion != nil {
|
||||||
|
if vop.IntroducedVersion == nil {
|
||||||
|
t.Errorf("VersionedOptions with RemovedVersion %v is missing required IntroducedVersion", vop.RemovedVersion)
|
||||||
|
}
|
||||||
|
if !vop.IntroducedVersion.LessThan(vop.RemovedVersion) {
|
||||||
|
t.Errorf("VersionedOptions with IntroducedVersion %s must be less than RemovedVersion %v", vop.IntroducedVersion, vop.RemovedVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range librariesInVersions(t, vop) {
|
||||||
|
versionTracking, ok := tracked[name]
|
||||||
|
if !ok {
|
||||||
|
versionTracking = &versionTracker{}
|
||||||
|
tracked[name] = versionTracking
|
||||||
|
}
|
||||||
|
if versionTracking.added != nil {
|
||||||
|
t.Errorf("Did not expect %s library to be added again at version %v. It was already added at version %v", name, vop.IntroducedVersion, versionTracking.added)
|
||||||
|
} else {
|
||||||
|
versionTracking.added = vop.IntroducedVersion
|
||||||
|
if versionTracking.removed != nil {
|
||||||
|
if versionTracking.removed.LessThan(vop.IntroducedVersion) {
|
||||||
|
t.Errorf("Did not expect gap in presence of %s library. It was "+
|
||||||
|
"removed in %v and not added again until %v. When versioning "+
|
||||||
|
"libraries, introduce a new version of the library as the same "+
|
||||||
|
"kubernetes version that the old version of the library is removed.", name, versionTracking.removed, vop.IntroducedVersion)
|
||||||
|
} else if vop.IntroducedVersion.LessThan(versionTracking.removed) {
|
||||||
|
t.Errorf("Did not expect overlap in presence of %s library. It was "+
|
||||||
|
"added again at version %v while scheduled to be removed at %v. When versioning "+
|
||||||
|
"libraries, introduce a new version of the library as the same "+
|
||||||
|
"kubernetes version that the old version of the library is removed.", name, vop.IntroducedVersion, versionTracking.removed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
versionTracking.removed = nil
|
||||||
|
}
|
||||||
|
if vop.RemovedVersion != nil {
|
||||||
|
if versionTracking.removed != nil {
|
||||||
|
t.Errorf("Unexpected RemovedVersion of %v for library %s already removed at version %v", vop.RemovedVersion, name, versionTracking.removed)
|
||||||
|
}
|
||||||
|
versionTracking.added = nil
|
||||||
|
versionTracking.removed = vop.RemovedVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for name, lib := range tracked {
|
||||||
|
if lib.removed != nil {
|
||||||
|
t.Errorf("Unexpected RemovedVersion of %v for library %s without replacement. "+
|
||||||
|
"For backward compatibility, libraries should not be removed without being replaced by a new version.", lib.removed, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func librariesInVersions(t *testing.T, vops ...VersionedOptions) []string {
|
||||||
|
env, err := cel.NewCustomEnv()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating env: %v", err)
|
||||||
|
}
|
||||||
|
for _, vop := range vops {
|
||||||
|
env, err = env.Extend(vop.EnvOptions...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error updating env: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
libs := env.Libraries()
|
||||||
|
return libs
|
||||||
|
}
|
||||||
|
|
||||||
|
type versionTracker struct {
|
||||||
|
added *version.Version
|
||||||
|
removed *version.Version
|
||||||
|
}
|
||||||
|
@ -202,6 +202,10 @@ var authzLib = &authz{}
|
|||||||
|
|
||||||
type authz struct{}
|
type authz struct{}
|
||||||
|
|
||||||
|
func (*authz) LibraryName() string {
|
||||||
|
return "k8s.authz"
|
||||||
|
}
|
||||||
|
|
||||||
var authzLibraryDecls = map[string][]cel.FunctionOpt{
|
var authzLibraryDecls = map[string][]cel.FunctionOpt{
|
||||||
"path": {
|
"path": {
|
||||||
cel.MemberOverload("authorizer_path", []*cel.Type{AuthorizerType, cel.StringType}, PathCheckType,
|
cel.MemberOverload("authorizer_path", []*cel.Type{AuthorizerType, cel.StringType}, PathCheckType,
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
|
|
||||||
"github.com/google/cel-go/cel"
|
"github.com/google/cel-go/cel"
|
||||||
"github.com/google/cel-go/checker"
|
"github.com/google/cel-go/checker"
|
||||||
|
"github.com/google/cel-go/common/types"
|
||||||
"github.com/google/cel-go/ext"
|
"github.com/google/cel-go/ext"
|
||||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||||
|
|
||||||
@ -538,11 +539,11 @@ func (t testSizeNode) Path() []string {
|
|||||||
return nil // not needed
|
return nil // not needed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t testSizeNode) Type() *expr.Type {
|
func (t testSizeNode) Type() *types.Type {
|
||||||
return nil // not needed
|
return nil // not needed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t testSizeNode) Expr() *expr.Expr {
|
func (t testSizeNode) Expr() *exprpb.Expr {
|
||||||
return nil // not needed
|
return nil // not needed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
|
|
||||||
func TestLibraryCompatibility(t *testing.T) {
|
func TestLibraryCompatibility(t *testing.T) {
|
||||||
var libs []map[string][]cel.FunctionOpt
|
var libs []map[string][]cel.FunctionOpt
|
||||||
libs = append(libs, authzLibraryDecls, listsLibraryDecls, regexLibraryDecls, urlLibraryDecls)
|
libs = append(libs, authzLibraryDecls, listsLibraryDecls, regexLibraryDecls, urlLibraryDecls, quantityLibraryDecls)
|
||||||
functionNames := sets.New[string]()
|
functionNames := sets.New[string]()
|
||||||
for _, lib := range libs {
|
for _, lib := range libs {
|
||||||
for name := range lib {
|
for name := range lib {
|
||||||
@ -45,6 +45,8 @@ func TestLibraryCompatibility(t *testing.T) {
|
|||||||
"path", "group", "serviceAccount", "resource", "subresource", "namespace", "name", "check", "allowed", "reason",
|
"path", "group", "serviceAccount", "resource", "subresource", "namespace", "name", "check", "allowed", "reason",
|
||||||
// Kubernetes <1.28>:
|
// Kubernetes <1.28>:
|
||||||
"errored", "error",
|
"errored", "error",
|
||||||
|
// Kubernetes <1.29>:
|
||||||
|
"add", "asApproximateFloat", "asInteger", "compareTo", "isGreaterThan", "isInteger", "isLessThan", "isQuantity", "quantity", "sign", "sub",
|
||||||
// Kubernetes <1.??>:
|
// Kubernetes <1.??>:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -95,6 +95,10 @@ var listsLib = &lists{}
|
|||||||
|
|
||||||
type lists struct{}
|
type lists struct{}
|
||||||
|
|
||||||
|
func (*lists) LibraryName() string {
|
||||||
|
return "k8s.lists"
|
||||||
|
}
|
||||||
|
|
||||||
var paramA = cel.TypeParamType("A")
|
var paramA = cel.TypeParamType("A")
|
||||||
|
|
||||||
// CEL typeParams can be used to constraint to a specific trait (e.g. traits.ComparableType) if the 1st operand is the type to constrain.
|
// CEL typeParams can be used to constraint to a specific trait (e.g. traits.ComparableType) if the 1st operand is the type to constrain.
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/google/cel-go/cel"
|
"github.com/google/cel-go/cel"
|
||||||
"github.com/google/cel-go/common/types"
|
"github.com/google/cel-go/common/types"
|
||||||
"github.com/google/cel-go/common/types/ref"
|
"github.com/google/cel-go/common/types/ref"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||||
)
|
)
|
||||||
@ -141,6 +142,10 @@ var quantityLib = &quantity{}
|
|||||||
|
|
||||||
type quantity struct{}
|
type quantity struct{}
|
||||||
|
|
||||||
|
func (*quantity) LibraryName() string {
|
||||||
|
return "k8s.quantity"
|
||||||
|
}
|
||||||
|
|
||||||
var quantityLibraryDecls = map[string][]cel.FunctionOpt{
|
var quantityLibraryDecls = map[string][]cel.FunctionOpt{
|
||||||
"quantity": {
|
"quantity": {
|
||||||
cel.Overload("string_to_quantity", []*cel.Type{cel.StringType}, apiservercel.QuantityType, cel.UnaryBinding((stringToQuantity))),
|
cel.Overload("string_to_quantity", []*cel.Type{cel.StringType}, apiservercel.QuantityType, cel.UnaryBinding((stringToQuantity))),
|
||||||
|
@ -51,6 +51,10 @@ var regexLib = ®ex{}
|
|||||||
|
|
||||||
type regex struct{}
|
type regex struct{}
|
||||||
|
|
||||||
|
func (*regex) LibraryName() string {
|
||||||
|
return "k8s.regex"
|
||||||
|
}
|
||||||
|
|
||||||
var regexLibraryDecls = map[string][]cel.FunctionOpt{
|
var regexLibraryDecls = map[string][]cel.FunctionOpt{
|
||||||
"find": {
|
"find": {
|
||||||
cel.MemberOverload("string_find_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType,
|
cel.MemberOverload("string_find_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType,
|
||||||
|
@ -37,6 +37,10 @@ type testLib struct {
|
|||||||
version uint32
|
version uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (*testLib) LibraryName() string {
|
||||||
|
return "k8s.test"
|
||||||
|
}
|
||||||
|
|
||||||
type TestOption func(*testLib) *testLib
|
type TestOption func(*testLib) *testLib
|
||||||
|
|
||||||
func TestVersion(version uint32) func(lib *testLib) *testLib {
|
func TestVersion(version uint32) func(lib *testLib) *testLib {
|
||||||
|
@ -112,6 +112,10 @@ var urlsLib = &urls{}
|
|||||||
|
|
||||||
type urls struct{}
|
type urls struct{}
|
||||||
|
|
||||||
|
func (*urls) LibraryName() string {
|
||||||
|
return "k8s.urls"
|
||||||
|
}
|
||||||
|
|
||||||
var urlLibraryDecls = map[string][]cel.FunctionOpt{
|
var urlLibraryDecls = map[string][]cel.FunctionOpt{
|
||||||
"url": {
|
"url": {
|
||||||
cel.Overload("string_to_url", []*cel.Type{cel.StringType}, apiservercel.URLType,
|
cel.Overload("string_to_url", []*cel.Type{cel.StringType}, apiservercel.URLType,
|
||||||
|
Loading…
Reference in New Issue
Block a user