mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-17 15:50:10 +00:00
Move CEL semver library into common libs, fix cost tests to use registered types
This commit is contained in:
parent
0a2dfba067
commit
e085f3818a
@ -6,6 +6,7 @@ go 1.22.0
|
||||
|
||||
require (
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
|
||||
github.com/blang/semver/v4 v4.0.0
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/coreos/go-systemd/v22 v22.5.0
|
||||
github.com/emicklei/go-restful/v3 v3.11.0
|
||||
@ -63,7 +64,6 @@ require (
|
||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
|
@ -41,11 +41,11 @@ type Format struct {
|
||||
MaxRegexSize int
|
||||
}
|
||||
|
||||
func (d *Format) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
func (d Format) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
return nil, fmt.Errorf("type conversion error from 'Format' to '%v'", typeDesc)
|
||||
}
|
||||
|
||||
func (d *Format) ConvertToType(typeVal ref.Type) ref.Val {
|
||||
func (d Format) ConvertToType(typeVal ref.Type) ref.Val {
|
||||
switch typeVal {
|
||||
case FormatType:
|
||||
return d
|
||||
@ -56,18 +56,18 @@ func (d *Format) ConvertToType(typeVal ref.Type) ref.Val {
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Format) Equal(other ref.Val) ref.Val {
|
||||
otherDur, ok := other.(*Format)
|
||||
func (d Format) Equal(other ref.Val) ref.Val {
|
||||
otherDur, ok := other.(Format)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
return types.Bool(d.Name == otherDur.Name)
|
||||
}
|
||||
|
||||
func (d *Format) Type() ref.Type {
|
||||
func (d Format) Type() ref.Type {
|
||||
return FormatType
|
||||
}
|
||||
|
||||
func (d *Format) Value() interface{} {
|
||||
func (d Format) Value() interface{} {
|
||||
return d
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ var cidrsLib = &cidrs{}
|
||||
type cidrs struct{}
|
||||
|
||||
func (*cidrs) LibraryName() string {
|
||||
return "net.cidr"
|
||||
return "kubernetes.net.cidr"
|
||||
}
|
||||
|
||||
func (*cidrs) declarations() map[string][]cel.FunctionOpt {
|
||||
|
@ -25,7 +25,6 @@ import (
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apiserver/pkg/cel"
|
||||
)
|
||||
@ -201,7 +200,7 @@ func (l *CostEstimator) CallCost(function, overloadId string, args []ref.Val, re
|
||||
}
|
||||
case "validate":
|
||||
if len(args) >= 2 {
|
||||
format, isFormat := args[0].Value().(*cel.Format)
|
||||
format, isFormat := args[0].Value().(cel.Format)
|
||||
if isFormat {
|
||||
strSize := actualSize(args[1])
|
||||
|
||||
@ -243,13 +242,14 @@ func (l *CostEstimator) CallCost(function, overloadId string, args []ref.Val, re
|
||||
case *cel.Quantity, cel.Quantity,
|
||||
*cel.IP, cel.IP,
|
||||
*cel.CIDR, cel.CIDR,
|
||||
*cel.Format, // Formats have a small max size. Format takes pointer receiver.
|
||||
*cel.Format, cel.Format, // Formats have a small max size. Format takes pointer receiver.
|
||||
*cel.URL, cel.URL, // TODO: Computing the actual cost is expensive, and changing this would be a breaking change
|
||||
*cel.Semver, cel.Semver,
|
||||
*authorizerVal, authorizerVal, *pathCheckVal, pathCheckVal, *groupCheckVal, groupCheckVal,
|
||||
*resourceCheckVal, resourceCheckVal, *decisionVal, decisionVal:
|
||||
return &unitCost
|
||||
default:
|
||||
if panicOnUnknown && isKubernetesType(lhs.Type()) {
|
||||
if panicOnUnknown && lhs.Type() != nil && isRegisteredType(lhs.Type().TypeName()) {
|
||||
panic(fmt.Errorf("CallCost: unhandled equality for Kubernetes type %T", lhs))
|
||||
}
|
||||
}
|
||||
@ -509,7 +509,7 @@ func (l *CostEstimator) EstimateCallCost(function, overloadId string, target *ch
|
||||
if t.Kind() == types.StructKind {
|
||||
switch t {
|
||||
case cel.QuantityType, AuthorizerType, PathCheckType, // O(1) cost equality checks
|
||||
GroupCheckType, ResourceCheckType, DecisionType:
|
||||
GroupCheckType, ResourceCheckType, DecisionType, cel.SemverType:
|
||||
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 1, Max: 1}}
|
||||
case cel.FormatType:
|
||||
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 1, Max: cel.MaxFormatSize}.MultiplyByCostFactor(common.StringTraversalCostFactor)}
|
||||
@ -523,7 +523,7 @@ func (l *CostEstimator) EstimateCallCost(function, overloadId string, target *ch
|
||||
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 1, Max: size.Max}.MultiplyByCostFactor(common.StringTraversalCostFactor)}
|
||||
}
|
||||
}
|
||||
if panicOnUnknown && isKubernetesType(t) {
|
||||
if panicOnUnknown && isRegisteredType(t.TypeName()) {
|
||||
panic(fmt.Errorf("EstimateCallCost: unhandled equality for Kubernetes type %v", t))
|
||||
}
|
||||
}
|
||||
@ -632,17 +632,3 @@ func traversalCost(v ref.Val) uint64 {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// isKubernetesType returns ture if a type is type defined by Kubernetes,
|
||||
// as identified by opaque or struct types with a "kubernetes." prefix.
|
||||
func isKubernetesType(t ref.Type) bool {
|
||||
if tt, ok := t.(*types.Type); ok {
|
||||
switch tt.Kind() {
|
||||
case types.OpaqueKind, types.StructKind:
|
||||
return strings.HasPrefix(tt.TypeName(), "kubernetes.")
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -1262,7 +1262,8 @@ func TestTypeEquality(t *testing.T) {
|
||||
"kubernetes.Quantity": apiservercel.Quantity{},
|
||||
"net.IP": apiservercel.IP{},
|
||||
"net.CIDR": apiservercel.CIDR{},
|
||||
"kubernetes.NamedFormat": &apiservercel.Format{},
|
||||
"kubernetes.NamedFormat": apiservercel.Format{},
|
||||
"kubernetes.Semver": apiservercel.Semver{},
|
||||
}
|
||||
|
||||
originalPanicOnUnknown := panicOnUnknown
|
||||
|
@ -133,7 +133,7 @@ func (*format) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
var ConstantFormats map[string]*apiservercel.Format = map[string]*apiservercel.Format{
|
||||
var ConstantFormats = map[string]apiservercel.Format{
|
||||
"dns1123Label": {
|
||||
Name: "DNS1123Label",
|
||||
ValidateFunc: func(s string) []string { return apimachineryvalidation.NameIsDNSLabel(s, false) },
|
||||
@ -261,7 +261,7 @@ var formatLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
}
|
||||
|
||||
func formatValidate(arg1, arg2 ref.Val) ref.Val {
|
||||
f, ok := arg1.Value().(*apiservercel.Format)
|
||||
f, ok := arg1.Value().(apiservercel.Format)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
@ -132,7 +132,7 @@ var ipLib = &ip{}
|
||||
type ip struct{}
|
||||
|
||||
func (*ip) LibraryName() string {
|
||||
return "net.ip"
|
||||
return "kubernetes.net.ip"
|
||||
}
|
||||
|
||||
func (*ip) declarations() map[string][]cel.FunctionOpt {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -16,7 +16,9 @@ limitations under the License.
|
||||
|
||||
package library
|
||||
|
||||
import "github.com/google/cel-go/cel"
|
||||
import (
|
||||
"github.com/google/cel-go/cel"
|
||||
)
|
||||
|
||||
// Library represents a CEL library used by kubernetes.
|
||||
type Library interface {
|
||||
@ -42,5 +44,17 @@ func KnownLibraries() []Library {
|
||||
ipLib,
|
||||
cidrsLib,
|
||||
formatLib,
|
||||
semverLib,
|
||||
}
|
||||
}
|
||||
|
||||
func isRegisteredType(typeName string) bool {
|
||||
for _, lib := range KnownLibraries() {
|
||||
for _, rt := range lib.Types() {
|
||||
if rt.TypeName() == typeName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ func TestLibraryCompatibility(t *testing.T) {
|
||||
// Kubernetes <1.30>:
|
||||
"ip", "family", "isUnspecified", "isLoopback", "isLinkLocalMulticast", "isLinkLocalUnicast", "isGlobalUnicast", "ip.isCanonical", "isIP", "cidr", "containsIP", "containsCIDR", "masked", "prefixLength", "isCIDR", "string",
|
||||
// Kubernetes <1.31>:
|
||||
"fieldSelector", "labelSelector", "validate", "format.named",
|
||||
"fieldSelector", "labelSelector", "validate", "format.named", "isSemver", "major", "minor", "patch", "semver",
|
||||
// Kubernetes <1.??>:
|
||||
)
|
||||
|
||||
@ -101,5 +101,4 @@ func TestTypeRegistration(t *testing.T) {
|
||||
t.Errorf("Expected types to be registered with the %s library Type() functions, but they were not: %v", lib.LibraryName(), unregistered)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cel_test
|
||||
package library_test
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
@ -27,7 +27,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
library "k8s.io/dynamic-resource-allocation/cel"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
library "k8s.io/apiserver/pkg/cel/library"
|
||||
)
|
||||
|
||||
func testSemver(t *testing.T, expr string, expectResult ref.Val, expectRuntimeErrPattern string, expectCompileErrs []string) {
|
||||
@ -117,7 +118,7 @@ func TestSemver(t *testing.T) {
|
||||
{
|
||||
name: "parse",
|
||||
expr: `semver("1.2.3")`,
|
||||
expectValue: library.Semver{Version: semver.MustParse("1.2.3")},
|
||||
expectValue: apiservercel.Semver{Version: semver.MustParse("1.2.3")},
|
||||
},
|
||||
{
|
||||
name: "parseInvalidVersion",
|
@ -14,13 +14,15 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cel
|
||||
package library
|
||||
|
||||
import (
|
||||
"github.com/blang/semver/v4"
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
)
|
||||
|
||||
// Semver provides a CEL function library extension for [semver.Version].
|
||||
@ -91,38 +93,45 @@ var semverLib = &semverLibType{}
|
||||
type semverLibType struct{}
|
||||
|
||||
func (*semverLibType) LibraryName() string {
|
||||
return "k8s.semver"
|
||||
return "kubernetes.Semver"
|
||||
}
|
||||
|
||||
func (*semverLibType) CompileOptions() []cel.EnvOption {
|
||||
// Defined in this function to avoid an initialization order problem.
|
||||
semverLibraryDecls := map[string][]cel.FunctionOpt{
|
||||
func (*semverLibType) Types() []*cel.Type {
|
||||
return []*cel.Type{apiservercel.SemverType}
|
||||
}
|
||||
|
||||
func (*semverLibType) declarations() map[string][]cel.FunctionOpt {
|
||||
return map[string][]cel.FunctionOpt{
|
||||
"semver": {
|
||||
cel.Overload("string_to_semver", []*cel.Type{cel.StringType}, SemverType, cel.UnaryBinding((stringToSemver))),
|
||||
cel.Overload("string_to_semver", []*cel.Type{cel.StringType}, apiservercel.SemverType, cel.UnaryBinding((stringToSemver))),
|
||||
},
|
||||
"isSemver": {
|
||||
cel.Overload("is_semver_string", []*cel.Type{cel.StringType}, cel.BoolType, cel.UnaryBinding(isSemver)),
|
||||
},
|
||||
"isGreaterThan": {
|
||||
cel.MemberOverload("semver_is_greater_than", []*cel.Type{SemverType, SemverType}, cel.BoolType, cel.BinaryBinding(semverIsGreaterThan)),
|
||||
cel.MemberOverload("semver_is_greater_than", []*cel.Type{apiservercel.SemverType, apiservercel.SemverType}, cel.BoolType, cel.BinaryBinding(semverIsGreaterThan)),
|
||||
},
|
||||
"isLessThan": {
|
||||
cel.MemberOverload("semver_is_less_than", []*cel.Type{SemverType, SemverType}, cel.BoolType, cel.BinaryBinding(semverIsLessThan)),
|
||||
cel.MemberOverload("semver_is_less_than", []*cel.Type{apiservercel.SemverType, apiservercel.SemverType}, cel.BoolType, cel.BinaryBinding(semverIsLessThan)),
|
||||
},
|
||||
"compareTo": {
|
||||
cel.MemberOverload("semver_compare_to", []*cel.Type{SemverType, SemverType}, cel.IntType, cel.BinaryBinding(semverCompareTo)),
|
||||
cel.MemberOverload("semver_compare_to", []*cel.Type{apiservercel.SemverType, apiservercel.SemverType}, cel.IntType, cel.BinaryBinding(semverCompareTo)),
|
||||
},
|
||||
"major": {
|
||||
cel.MemberOverload("semver_major", []*cel.Type{SemverType}, cel.IntType, cel.UnaryBinding(semverMajor)),
|
||||
cel.MemberOverload("semver_major", []*cel.Type{apiservercel.SemverType}, cel.IntType, cel.UnaryBinding(semverMajor)),
|
||||
},
|
||||
"minor": {
|
||||
cel.MemberOverload("semver_minor", []*cel.Type{SemverType}, cel.IntType, cel.UnaryBinding(semverMinor)),
|
||||
cel.MemberOverload("semver_minor", []*cel.Type{apiservercel.SemverType}, cel.IntType, cel.UnaryBinding(semverMinor)),
|
||||
},
|
||||
"patch": {
|
||||
cel.MemberOverload("semver_patch", []*cel.Type{SemverType}, cel.IntType, cel.UnaryBinding(semverPatch)),
|
||||
cel.MemberOverload("semver_patch", []*cel.Type{apiservercel.SemverType}, cel.IntType, cel.UnaryBinding(semverPatch)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *semverLibType) CompileOptions() []cel.EnvOption {
|
||||
// Defined in this function to avoid an initialization order problem.
|
||||
semverLibraryDecls := s.declarations()
|
||||
options := make([]cel.EnvOption, 0, len(semverLibraryDecls))
|
||||
for name, overloads := range semverLibraryDecls {
|
||||
options = append(options, cel.Function(name, overloads...))
|
||||
@ -168,7 +177,7 @@ func stringToSemver(arg ref.Val) ref.Val {
|
||||
return types.WrapErr(err)
|
||||
}
|
||||
|
||||
return Semver{Version: v}
|
||||
return apiservercel.Semver{Version: v}
|
||||
}
|
||||
|
||||
func semverMajor(arg ref.Val) ref.Val {
|
@ -37,6 +37,7 @@ import (
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
"k8s.io/apiserver/pkg/cel/library"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -151,7 +152,7 @@ func getAttributeValue(attr resourceapi.DeviceAttribute) (any, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse semantic version: %w", err)
|
||||
}
|
||||
return Semver{Version: v}, nil
|
||||
return apiservercel.Semver{Version: v}, nil
|
||||
default:
|
||||
return nil, errors.New("unsupported attribute value")
|
||||
}
|
||||
@ -236,7 +237,7 @@ func mustBuildEnv() *environment.EnvSet {
|
||||
EnvOptions: []cel.EnvOption{
|
||||
cel.Variable(deviceVar, deviceType.CelType()),
|
||||
|
||||
SemverLib(),
|
||||
library.SemverLib(),
|
||||
|
||||
// https://pkg.go.dev/github.com/google/cel-go/ext#Bindings
|
||||
//
|
||||
|
Loading…
Reference in New Issue
Block a user