Add CEL library lifecycle linter
This commit is contained in:
		@@ -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,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user