@@ -29,10 +29,14 @@ import (
2929 apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
3030 "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
3131 "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/model"
32+ apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
3233 "k8s.io/apimachinery/pkg/util/version"
3334 celconfig "k8s.io/apiserver/pkg/apis/cel"
3435 "k8s.io/apiserver/pkg/cel"
3536 "k8s.io/apiserver/pkg/cel/environment"
37+ utilfeature "k8s.io/apiserver/pkg/util/feature"
38+ featuregatetesting "k8s.io/component-base/featuregate/testing"
39+ "k8s.io/utils/ptr"
3640)
3741
3842const (
@@ -151,12 +155,99 @@ func (v transitionRuleMatcher) String() string {
151155}
152156
153157func TestCelCompilation (t * testing.T ) {
158+ defer featuregatetesting .SetFeatureGateDuringTest (t , utilfeature .DefaultFeatureGate , apiextensionsfeatures .CRDValidationRatcheting , true )()
154159 cases := []struct {
155160 name string
156161 input schema.Structural
157162 expectedResults []validationMatcher
158163 unmodified bool
159164 }{
165+ {
166+ name : "optional primitive transition rule type checking" ,
167+ input : schema.Structural {
168+ Generic : schema.Generic {
169+ Type : "integer" ,
170+ },
171+ Extensions : schema.Extensions {
172+ XValidations : apiextensions.ValidationRules {
173+ {Rule : "self >= oldSelf.value()" , OptionalOldSelf : ptr .To (true )},
174+ {Rule : "self >= oldSelf.orValue(1)" , OptionalOldSelf : ptr .To (true )},
175+ {Rule : "oldSelf.hasValue() ? self >= oldSelf.value() : true" , OptionalOldSelf : ptr .To (true )},
176+ {Rule : "self >= oldSelf" , OptionalOldSelf : ptr .To (true )},
177+ {Rule : "self >= oldSelf.orValue('')" , OptionalOldSelf : ptr .To (true )},
178+ },
179+ },
180+ },
181+ expectedResults : []validationMatcher {
182+ matchesAll (noError (), transitionRule (true )),
183+ matchesAll (noError (), transitionRule (true )),
184+ matchesAll (noError (), transitionRule (true )),
185+ matchesAll (invalidError ("optional" )),
186+ matchesAll (invalidError ("orValue" )),
187+ },
188+ },
189+ {
190+ name : "optional complex transition rule type checking" ,
191+ input : schema.Structural {
192+ Generic : schema.Generic {
193+ Type : "object" ,
194+ },
195+ Properties : map [string ]schema.Structural {
196+ "i" : {Generic : schema.Generic {Type : "integer" }},
197+ "b" : {Generic : schema.Generic {Type : "boolean" }},
198+ "s" : {Generic : schema.Generic {Type : "string" }},
199+ "a" : {
200+ Generic : schema.Generic {Type : "array" },
201+ Items : & schema.Structural {Generic : schema.Generic {Type : "integer" }},
202+ },
203+ "o" : {
204+ Generic : schema.Generic {Type : "object" },
205+ Properties : map [string ]schema.Structural {
206+ "i" : {Generic : schema.Generic {Type : "integer" }},
207+ "b" : {Generic : schema.Generic {Type : "boolean" }},
208+ "s" : {Generic : schema.Generic {Type : "string" }},
209+ "a" : {
210+ Generic : schema.Generic {Type : "array" },
211+ Items : & schema.Structural {Generic : schema.Generic {Type : "integer" }},
212+ },
213+ "o" : {
214+ Generic : schema.Generic {Type : "object" },
215+ },
216+ },
217+ },
218+ },
219+ Extensions : schema.Extensions {
220+ XValidations : apiextensions.ValidationRules {
221+ {Rule : "self.i >= oldSelf.i.value()" , OptionalOldSelf : ptr .To (true )},
222+ {Rule : "self.s == oldSelf.s.value()" , OptionalOldSelf : ptr .To (true )},
223+ {Rule : "self.b == oldSelf.b.value()" , OptionalOldSelf : ptr .To (true )},
224+ {Rule : "self.o == oldSelf.o.value()" , OptionalOldSelf : ptr .To (true )},
225+ {Rule : "self.o.i >= oldSelf.o.i.value()" , OptionalOldSelf : ptr .To (true )},
226+ {Rule : "self.o.s == oldSelf.o.s.value()" , OptionalOldSelf : ptr .To (true )},
227+ {Rule : "self.o.b == oldSelf.o.b.value()" , OptionalOldSelf : ptr .To (true )},
228+ {Rule : "self.o.o == oldSelf.o.o.value()" , OptionalOldSelf : ptr .To (true )},
229+ {Rule : "self.o.i >= oldSelf.o.i.orValue(1)" , OptionalOldSelf : ptr .To (true )},
230+ {Rule : "oldSelf.hasValue() ? self.o.i >= oldSelf.o.i.value() : true" , OptionalOldSelf : ptr .To (true )},
231+ {Rule : "self.o.i >= oldSelf.o.i" , OptionalOldSelf : ptr .To (true )},
232+ {Rule : "self.o.i >= oldSelf.o.s.orValue(0)" , OptionalOldSelf : ptr .To (true )},
233+ },
234+ },
235+ },
236+ expectedResults : []validationMatcher {
237+ matchesAll (noError (), transitionRule (true )),
238+ matchesAll (noError (), transitionRule (true )),
239+ matchesAll (noError (), transitionRule (true )),
240+ matchesAll (noError (), transitionRule (true )),
241+ matchesAll (noError (), transitionRule (true )),
242+ matchesAll (noError (), transitionRule (true )),
243+ matchesAll (noError (), transitionRule (true )),
244+ matchesAll (noError (), transitionRule (true )),
245+ matchesAll (noError (), transitionRule (true )),
246+ matchesAll (noError (), transitionRule (true )),
247+ matchesAll (invalidError ("optional" )),
248+ matchesAll (invalidError ("orValue" )),
249+ },
250+ },
160251 {
161252 name : "valid object" ,
162253 input : schema.Structural {
0 commit comments