diff --git a/staging/src/k8s.io/apiserver/go.mod b/staging/src/k8s.io/apiserver/go.mod index e9eaf2449ab..dbbfb437a3d 100644 --- a/staging/src/k8s.io/apiserver/go.mod +++ b/staging/src/k8s.io/apiserver/go.mod @@ -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 diff --git a/staging/src/k8s.io/apiserver/pkg/cel/format.go b/staging/src/k8s.io/apiserver/pkg/cel/format.go index 1bcfddfe765..31216806f78 100644 --- a/staging/src/k8s.io/apiserver/pkg/cel/format.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/format.go @@ -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 } diff --git a/staging/src/k8s.io/apiserver/pkg/cel/library/cidr.go b/staging/src/k8s.io/apiserver/pkg/cel/library/cidr.go index 8686e6c17c0..2992e99e658 100644 --- a/staging/src/k8s.io/apiserver/pkg/cel/library/cidr.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/library/cidr.go @@ -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 { diff --git a/staging/src/k8s.io/apiserver/pkg/cel/library/cost.go b/staging/src/k8s.io/apiserver/pkg/cel/library/cost.go index 2ffb0755d6b..14b74dc6b02 100644 --- a/staging/src/k8s.io/apiserver/pkg/cel/library/cost.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/library/cost.go @@ -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 -} diff --git a/staging/src/k8s.io/apiserver/pkg/cel/library/cost_test.go b/staging/src/k8s.io/apiserver/pkg/cel/library/cost_test.go index b629a007b98..4da1934f66d 100644 --- a/staging/src/k8s.io/apiserver/pkg/cel/library/cost_test.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/library/cost_test.go @@ -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 diff --git a/staging/src/k8s.io/apiserver/pkg/cel/library/format.go b/staging/src/k8s.io/apiserver/pkg/cel/library/format.go index f76c6a29d56..83c821c8c11 100644 --- a/staging/src/k8s.io/apiserver/pkg/cel/library/format.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/library/format.go @@ -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) } diff --git a/staging/src/k8s.io/apiserver/pkg/cel/library/ip.go b/staging/src/k8s.io/apiserver/pkg/cel/library/ip.go index b957afe769b..8edc4463a07 100644 --- a/staging/src/k8s.io/apiserver/pkg/cel/library/ip.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/library/ip.go @@ -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 { diff --git a/staging/src/k8s.io/apiserver/pkg/cel/library/libraries.go b/staging/src/k8s.io/apiserver/pkg/cel/library/libraries.go index 75a9b0e998f..e3689e3e0eb 100644 --- a/staging/src/k8s.io/apiserver/pkg/cel/library/libraries.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/library/libraries.go @@ -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 +} diff --git a/staging/src/k8s.io/apiserver/pkg/cel/library/library_compatibility_test.go b/staging/src/k8s.io/apiserver/pkg/cel/library/library_compatibility_test.go index d42a1b91a15..d76100a8c93 100644 --- a/staging/src/k8s.io/apiserver/pkg/cel/library/library_compatibility_test.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/library/library_compatibility_test.go @@ -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) } } - } diff --git a/staging/src/k8s.io/dynamic-resource-allocation/cel/semver_test.go b/staging/src/k8s.io/apiserver/pkg/cel/library/semver_test.go similarity index 96% rename from staging/src/k8s.io/dynamic-resource-allocation/cel/semver_test.go rename to staging/src/k8s.io/apiserver/pkg/cel/library/semver_test.go index d71cfdf91ff..3b1471ddaa8 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/cel/semver_test.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/library/semver_test.go @@ -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", diff --git a/staging/src/k8s.io/dynamic-resource-allocation/cel/semverlib.go b/staging/src/k8s.io/apiserver/pkg/cel/library/semverlib.go similarity index 80% rename from staging/src/k8s.io/dynamic-resource-allocation/cel/semverlib.go rename to staging/src/k8s.io/apiserver/pkg/cel/library/semverlib.go index c9470761e73..d8c79ae0217 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/cel/semverlib.go +++ b/staging/src/k8s.io/apiserver/pkg/cel/library/semverlib.go @@ -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 { diff --git a/staging/src/k8s.io/dynamic-resource-allocation/cel/semver.go b/staging/src/k8s.io/apiserver/pkg/cel/semver.go similarity index 100% rename from staging/src/k8s.io/dynamic-resource-allocation/cel/semver.go rename to staging/src/k8s.io/apiserver/pkg/cel/semver.go diff --git a/staging/src/k8s.io/dynamic-resource-allocation/cel/compile.go b/staging/src/k8s.io/dynamic-resource-allocation/cel/compile.go index 7cad34651e2..3c140a1a220 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/cel/compile.go +++ b/staging/src/k8s.io/dynamic-resource-allocation/cel/compile.go @@ -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 //