allow CEL and additionalProperties used in allof

additionalProperties makes sense to use in a NestedValueValidation, but it is currently forbidden. This change makes the treatment of additionalProperties more in line with the treatment of `items` or `properties` - so users can specify additional value validations for schemas which use `additionalProperties` structurally.

This change also move XValidations so that it is allowed to be used inside an allOf in a CRD schema
This commit is contained in:
Alexander Zielenski 2024-04-18 07:58:53 -07:00
parent 0aa01be424
commit bf7770f92f
16 changed files with 381 additions and 341 deletions

View File

@ -122,10 +122,10 @@ func Compile(s *schema.Structural, declType *apiservercel.DeclType, perCallLimit
metrics.Metrics.ObserveCompilation(time.Since(t)) metrics.Metrics.ObserveCompilation(time.Since(t))
}() }()
if len(s.Extensions.XValidations) == 0 { if len(s.XValidations) == 0 {
return nil, nil return nil, nil
} }
celRules := s.Extensions.XValidations celRules := s.XValidations
oldSelfEnvSet, optionalOldSelfEnvSet, err := prepareEnvSet(baseEnvSet, declType) oldSelfEnvSet, optionalOldSelfEnvSet, err := prepareEnvSet(baseEnvSet, declType)
if err != nil { if err != nil {

View File

@ -168,7 +168,7 @@ func TestCelCompilation(t *testing.T) {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "integer", Type: "integer",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{Rule: "self >= oldSelf.value()", OptionalOldSelf: ptr.To(true)}, {Rule: "self >= oldSelf.value()", OptionalOldSelf: ptr.To(true)},
{Rule: "self >= oldSelf.orValue(1)", OptionalOldSelf: ptr.To(true)}, {Rule: "self >= oldSelf.orValue(1)", OptionalOldSelf: ptr.To(true)},
@ -216,7 +216,7 @@ func TestCelCompilation(t *testing.T) {
}, },
}, },
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{Rule: "self.i >= oldSelf.i.value()", OptionalOldSelf: ptr.To(true)}, {Rule: "self.i >= oldSelf.i.value()", OptionalOldSelf: ptr.To(true)},
{Rule: "self.s == oldSelf.s.value()", OptionalOldSelf: ptr.To(true)}, {Rule: "self.s == oldSelf.s.value()", OptionalOldSelf: ptr.To(true)},
@ -266,7 +266,7 @@ func TestCelCompilation(t *testing.T) {
}, },
}, },
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "self.minReplicas < self.maxReplicas", Rule: "self.minReplicas < self.maxReplicas",
@ -285,7 +285,7 @@ func TestCelCompilation(t *testing.T) {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "string", Type: "string",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "self.startsWith('s')", Rule: "self.startsWith('s')",
@ -307,7 +307,7 @@ func TestCelCompilation(t *testing.T) {
ValueValidation: &schema.ValueValidation{ ValueValidation: &schema.ValueValidation{
Format: "byte", Format: "byte",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "string(self).endsWith('s')", Rule: "string(self).endsWith('s')",
@ -326,7 +326,7 @@ func TestCelCompilation(t *testing.T) {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "boolean", Type: "boolean",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "self == true", Rule: "self == true",
@ -345,7 +345,7 @@ func TestCelCompilation(t *testing.T) {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "integer", Type: "integer",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "self > 0", Rule: "self > 0",
@ -364,7 +364,7 @@ func TestCelCompilation(t *testing.T) {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "number", Type: "number",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "self > 1.0", Rule: "self > 1.0",
@ -400,7 +400,7 @@ func TestCelCompilation(t *testing.T) {
}, },
}, },
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "self.nestedObj.val == 10", Rule: "self.nestedObj.val == 10",
@ -436,7 +436,7 @@ func TestCelCompilation(t *testing.T) {
}, },
}, },
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "size(self.nestedObj[0]) == 10", Rule: "size(self.nestedObj[0]) == 10",
@ -470,7 +470,7 @@ func TestCelCompilation(t *testing.T) {
}, },
}, },
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "size(self[0][0]) == 10", Rule: "size(self[0][0]) == 10",
@ -511,7 +511,7 @@ func TestCelCompilation(t *testing.T) {
}, },
}, },
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "self[0].nestedObj.val == 10", Rule: "self[0].nestedObj.val == 10",
@ -529,17 +529,17 @@ func TestCelCompilation(t *testing.T) {
input: schema.Structural{ input: schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{ },
Bool: true, AdditionalProperties: &schema.StructuralOrBool{
Structural: &schema.Structural{ Bool: true,
Generic: schema.Generic{ Structural: &schema.Structural{
Type: "boolean", Generic: schema.Generic{
Nullable: false, Type: "boolean",
}, Nullable: false,
}, },
}, },
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "size(self) > 0", Rule: "size(self) > 0",
@ -558,7 +558,7 @@ func TestCelCompilation(t *testing.T) {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "number", Type: "number",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "size(self) == 10", Rule: "size(self) == 10",
@ -577,7 +577,7 @@ func TestCelCompilation(t *testing.T) {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "integer", Type: "integer",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "size(self) == 10", Rule: "size(self) == 10",
@ -623,7 +623,7 @@ func TestCelCompilation(t *testing.T) {
}, },
}, },
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "size(self.__namespace__[0]) == 10", Rule: "size(self.__namespace__[0]) == 10",
@ -677,7 +677,7 @@ func TestCelCompilation(t *testing.T) {
}, },
}, },
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "size(self.namespace[0]) == 10", Rule: "size(self.namespace[0]) == 10",
@ -704,7 +704,7 @@ func TestCelCompilation(t *testing.T) {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "integer", Type: "integer",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{Rule: "self > 0"}, {Rule: "self > 0"},
{Rule: "self >= oldSelf"}, {Rule: "self >= oldSelf"},
@ -722,7 +722,7 @@ func TestCelCompilation(t *testing.T) {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{Rule: " \t"}, {Rule: " \t"},
}, },
@ -745,7 +745,7 @@ func TestCelCompilation(t *testing.T) {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{Rule: "42"}, {Rule: "42"},
}, },
@ -761,7 +761,7 @@ func TestCelCompilation(t *testing.T) {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "string", Type: "string",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "self.startsWith('s')", Rule: "self.startsWith('s')",
@ -780,7 +780,7 @@ func TestCelCompilation(t *testing.T) {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "integer", Type: "integer",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "self == 5", Rule: "self == 5",
@ -799,7 +799,7 @@ func TestCelCompilation(t *testing.T) {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "number", Type: "number",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: "self < 32.0", Rule: "self < 32.0",
@ -818,7 +818,7 @@ func TestCelCompilation(t *testing.T) {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{Rule: "fakeFunction('abc') == 'ABC'"}, {Rule: "fakeFunction('abc') == 'ABC'"},
}, },
@ -835,7 +835,7 @@ func TestCelCompilation(t *testing.T) {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{Rule: "fakeFunction('abc') == 'ABC'"}, {Rule: "fakeFunction('abc') == 'ABC'"},
}, },
@ -917,7 +917,7 @@ func genArrayWithRule(arrayType, rule string) func(maxItems *int64) *schema.Stru
ValueValidation: &schema.ValueValidation{ ValueValidation: &schema.ValueValidation{
MaxItems: maxItems, MaxItems: maxItems,
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,
@ -947,7 +947,7 @@ func genArrayOfArraysWithRule(arrayType, rule string) func(maxItems *int64) *sch
ValueValidation: &schema.ValueValidation{ ValueValidation: &schema.ValueValidation{
MaxItems: maxItems, MaxItems: maxItems,
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,
@ -987,7 +987,7 @@ func genObjectArrayWithRule(rule string) func(maxItems *int64) *schema.Structura
ValueValidation: &schema.ValueValidation{ ValueValidation: &schema.ValueValidation{
MaxItems: maxItems, MaxItems: maxItems,
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,
@ -1007,17 +1007,17 @@ func getMapArrayWithRule(mapType, rule string) func(maxItems *int64) *schema.Str
Items: &schema.Structural{ Items: &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
Generic: schema.Generic{
Type: mapType,
},
}},
}, },
AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
Generic: schema.Generic{
Type: mapType,
},
}},
}, },
ValueValidation: &schema.ValueValidation{ ValueValidation: &schema.ValueValidation{
MaxItems: maxItems, MaxItems: maxItems,
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,
@ -1034,22 +1034,22 @@ func genMapWithRule(mapType, rule string) func(maxProperties *int64) *schema.Str
return &schema.Structural{ return &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
Generic: schema.Generic{
Type: passedType,
},
ValueValidation: &schema.ValueValidation{
Format: passedFormat,
},
Extensions: schema.Extensions{
XIntOrString: xIntString,
},
}},
}, },
AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
Generic: schema.Generic{
Type: passedType,
},
ValueValidation: &schema.ValueValidation{
Format: passedFormat,
},
Extensions: schema.Extensions{
XIntOrString: xIntString,
},
}},
ValueValidation: &schema.ValueValidation{ ValueValidation: &schema.ValueValidation{
MaxProperties: maxProperties, MaxProperties: maxProperties,
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,
@ -1069,7 +1069,7 @@ func genStringWithRule(rule string) func(maxLength *int64) *schema.Structural {
ValueValidation: &schema.ValueValidation{ ValueValidation: &schema.ValueValidation{
MaxLength: maxLength, MaxLength: maxLength,
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,
@ -1098,7 +1098,7 @@ func genEnumWithRuleAndValues(rule string, values ...string) func(maxLength *int
MaxLength: maxLength, MaxLength: maxLength,
Enum: enums, Enum: enums,
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,
@ -1119,7 +1119,7 @@ func genBytesWithRule(rule string) func(maxLength *int64) *schema.Structural {
MaxLength: maxLength, MaxLength: maxLength,
Format: "byte", Format: "byte",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,
@ -1135,19 +1135,19 @@ func genNestedSpecWithRule(rule string) func(maxLength *int64) *schema.Structura
return &schema.Structural{ return &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
Generic: schema.Generic{
Type: "string",
},
ValueValidation: &schema.ValueValidation{
MaxLength: maxLength,
},
}},
}, },
AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
Generic: schema.Generic{
Type: "string",
},
ValueValidation: &schema.ValueValidation{
MaxLength: maxLength,
},
}},
ValueValidation: &schema.ValueValidation{ ValueValidation: &schema.ValueValidation{
MaxProperties: maxLength, MaxProperties: maxLength,
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,
@ -1167,28 +1167,28 @@ func genAllMaxNestedSpecWithRootRule(rule string) func(maxLength *int64) *schema
Items: &schema.Structural{ Items: &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
Generic: schema.Generic{
Type: "object",
},
ValueValidation: &schema.ValueValidation{
Required: []string{"required"},
MaxProperties: maxLength,
},
Properties: map[string]schema.Structural{
"required": {
Generic: schema.Generic{
Type: "string",
},
},
"optional": {
Generic: schema.Generic{
Type: "string",
},
},
},
}},
}, },
AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
Generic: schema.Generic{
Type: "object",
},
ValueValidation: &schema.ValueValidation{
Required: []string{"required"},
MaxProperties: maxLength,
},
Properties: map[string]schema.Structural{
"required": {
Generic: schema.Generic{
Type: "string",
},
},
"optional": {
Generic: schema.Generic{
Type: "string",
},
},
},
}},
ValueValidation: &schema.ValueValidation{ ValueValidation: &schema.ValueValidation{
MaxProperties: maxLength, MaxProperties: maxLength,
}, },
@ -1196,7 +1196,7 @@ func genAllMaxNestedSpecWithRootRule(rule string) func(maxLength *int64) *schema
ValueValidation: &schema.ValueValidation{ ValueValidation: &schema.ValueValidation{
MaxItems: maxLength, MaxItems: maxLength,
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,
@ -1216,32 +1216,32 @@ func genOneMaxNestedSpecWithRootRule(rule string) func(maxLength *int64) *schema
Items: &schema.Structural{ Items: &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
Generic: schema.Generic{
Type: "object",
},
ValueValidation: &schema.ValueValidation{
Required: []string{"required"},
},
Properties: map[string]schema.Structural{
"required": {
Generic: schema.Generic{
Type: "string",
},
},
"optional": {
Generic: schema.Generic{
Type: "string",
},
},
},
}},
}, },
AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
Generic: schema.Generic{
Type: "object",
},
ValueValidation: &schema.ValueValidation{
Required: []string{"required"},
},
Properties: map[string]schema.Structural{
"required": {
Generic: schema.Generic{
Type: "string",
},
},
"optional": {
Generic: schema.Generic{
Type: "string",
},
},
},
}},
ValueValidation: &schema.ValueValidation{ ValueValidation: &schema.ValueValidation{
MaxProperties: maxLength, MaxProperties: maxLength,
}, },
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,
@ -1292,12 +1292,12 @@ func genMapForMap() *schema.Structural {
return &schema.Structural{ return &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
Generic: schema.Generic{
Type: "number",
},
}},
}, },
AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
Generic: schema.Generic{
Type: "number",
},
}},
} }
} }
@ -1305,13 +1305,13 @@ func genMapWithCustomItemRule(item *schema.Structural, rule string) func(maxProp
return func(maxProperties *int64) *schema.Structural { return func(maxProperties *int64) *schema.Structural {
return &schema.Structural{ return &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{Structural: item},
}, },
AdditionalProperties: &schema.StructuralOrBool{Structural: item},
ValueValidation: &schema.ValueValidation{ ValueValidation: &schema.ValueValidation{
MaxProperties: maxProperties, MaxProperties: maxProperties,
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,

View File

@ -279,11 +279,18 @@ func nestedValueValidationToStructural(nvv *schema.NestedValueValidation) *Struc
newProperties[k] = *nestedValueValidationToStructural(&v).Structural newProperties[k] = *nestedValueValidationToStructural(&v).Structural
} }
var newAdditionalProperties *schema.StructuralOrBool
if nvv.AdditionalProperties != nil {
newAdditionalProperties = &schema.StructuralOrBool{Structural: nestedValueValidationToStructural(nvv.AdditionalProperties).Structural}
}
return &Structural{ return &Structural{
Structural: &schema.Structural{ Structural: &schema.Structural{
Items: newItems, Items: newItems,
Properties: newProperties, Properties: newProperties,
ValueValidation: &nvv.ValueValidation, AdditionalProperties: newAdditionalProperties,
ValueValidation: &nvv.ValueValidation,
ValidationExtensions: nvv.ValidationExtensions,
}, },
} }
} }

View File

@ -49,9 +49,11 @@ func WithTypeAndObjectMeta(s *schema.Structural) *schema.Structural {
return s return s
} }
result := &schema.Structural{ result := &schema.Structural{
Generic: s.Generic, AdditionalProperties: s.AdditionalProperties,
Extensions: s.Extensions, Generic: s.Generic,
ValueValidation: s.ValueValidation, Extensions: s.Extensions,
ValueValidation: s.ValueValidation,
ValidationExtensions: s.ValidationExtensions,
} }
props := make(map[string]schema.Structural, len(s.Properties)) props := make(map[string]schema.Structural, len(s.Properties))
for k, prop := range s.Properties { for k, prop := range s.Properties {

View File

@ -199,11 +199,11 @@ func testSchema() *schema.Structural {
"flags": { "flags": {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{ },
Structural: &schema.Structural{ AdditionalProperties: &schema.StructuralOrBool{
Generic: schema.Generic{ Structural: &schema.Structural{
Type: "boolean", Generic: schema.Generic{
}, Type: "boolean",
}, },
}, },
}, },
@ -317,12 +317,12 @@ func TestEstimateMaxLengthJSON(t *testing.T) {
InputSchema: &schema.Structural{ InputSchema: &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
Generic: schema.Generic{
Type: "string",
},
}},
}, },
AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
Generic: schema.Generic{
Type: "string",
},
}},
}, },
// expected JSON is {"":"","":"",...} so our length should be (3000000 - 2) / 6 // expected JSON is {"":"","":"",...} so our length should be (3000000 - 2) / 6
ExpectedMaxElements: 393215, ExpectedMaxElements: 393215,
@ -382,12 +382,12 @@ func TestEstimateMaxLengthJSON(t *testing.T) {
InputSchema: &schema.Structural{ InputSchema: &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
Generic: schema.Generic{
Type: "string",
},
}},
}, },
AdditionalProperties: &schema.StructuralOrBool{Structural: &schema.Structural{
Generic: schema.Generic{
Type: "string",
},
}},
ValueValidation: &schema.ValueValidation{ ValueValidation: &schema.ValueValidation{
Format: "string", Format: "string",
MaxProperties: maxPtr(15), MaxProperties: maxPtr(15),
@ -519,15 +519,15 @@ func genNestedSchema(depth int) *schema.Structural {
generator = func(d int) schema.Structural { generator = func(d int) schema.Structural {
nodeTemplate := schema.Structural{ nodeTemplate := schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{},
}, },
AdditionalProperties: &schema.StructuralOrBool{},
} }
if d == 1 { if d == 1 {
return nodeTemplate return nodeTemplate
} else { } else {
mapType := generator(d - 1) mapType := generator(d - 1)
nodeTemplate.Generic.AdditionalProperties.Structural = &mapType nodeTemplate.AdditionalProperties.Structural = &mapType
return nodeTemplate return nodeTemplate
} }
} }

View File

@ -2787,7 +2787,7 @@ func TestReasonAndFldPath(t *testing.T) {
"field2": stringType, "field2": stringType,
"field3": stringType, "field3": stringType,
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: apiextensions.ValidationRules{ XValidations: apiextensions.ValidationRules{
{ {
Rule: `self.field2 != "value2"`, Rule: `self.field2 != "value2"`,
@ -2852,16 +2852,16 @@ func TestValidateFieldPath(t *testing.T) {
"foo": { "foo": {
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{ },
Structural: &schema.Structural{ AdditionalProperties: &schema.StructuralOrBool{
Generic: schema.Generic{ Structural: &schema.Structural{
Type: "object", Generic: schema.Generic{
}, Type: "object",
Properties: map[string]schema.Structural{ },
"subAdd": { Properties: map[string]schema.Structural{
Generic: schema.Generic{ "subAdd": {
Type: "number", Generic: schema.Generic{
}, Type: "number",
}, },
}, },
}, },
@ -4387,7 +4387,7 @@ func BenchmarkCELValidationWithAndWithoutOldSelfReference(b *testing.B) {
ValueValidation: &schema.ValueValidation{ ValueValidation: &schema.ValueValidation{
Format: "date-time", Format: "date-time",
}, },
Extensions: schema.Extensions{ ValidationExtensions: schema.ValidationExtensions{
XValidations: []apiextensions.ValidationRule{ XValidations: []apiextensions.ValidationRule{
{Rule: rule}, {Rule: rule},
}, },
@ -4520,9 +4520,9 @@ func objectTypePtr(props map[string]schema.Structural) *schema.Structural {
func mapType(valSchema *schema.Structural) schema.Structural { func mapType(valSchema *schema.Structural) schema.Structural {
result := schema.Structural{ result := schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{Bool: true, Structural: valSchema},
}, },
AdditionalProperties: &schema.StructuralOrBool{Bool: true, Structural: valSchema},
} }
return result return result
} }
@ -4541,7 +4541,7 @@ func intOrStringType() schema.Structural {
} }
func withRule(s schema.Structural, rule string) schema.Structural { func withRule(s schema.Structural, rule string) schema.Structural {
s.Extensions.XValidations = apiextensions.ValidationRules{ s.XValidations = apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,
}, },
@ -4550,7 +4550,7 @@ func withRule(s schema.Structural, rule string) schema.Structural {
} }
func withRuleMessageAndMessageExpression(s schema.Structural, rule, message, messageExpression string) schema.Structural { func withRuleMessageAndMessageExpression(s schema.Structural, rule, message, messageExpression string) schema.Structural {
s.Extensions.XValidations = apiextensions.ValidationRules{ s.XValidations = apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,
Message: message, Message: message,
@ -4561,7 +4561,7 @@ func withRuleMessageAndMessageExpression(s schema.Structural, rule, message, mes
} }
func withReasonAndFldPath(s schema.Structural, rule, jsonPath string, reason *apiextensions.FieldValueErrorReason) schema.Structural { func withReasonAndFldPath(s schema.Structural, rule, jsonPath string, reason *apiextensions.FieldValueErrorReason) schema.Structural {
s.Extensions.XValidations = apiextensions.ValidationRules{ s.XValidations = apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,
FieldPath: jsonPath, FieldPath: jsonPath,
@ -4572,7 +4572,7 @@ func withReasonAndFldPath(s schema.Structural, rule, jsonPath string, reason *ap
} }
func withRuleAndMessageExpression(s schema.Structural, rule, messageExpression string) schema.Structural { func withRuleAndMessageExpression(s schema.Structural, rule, messageExpression string) schema.Structural {
s.Extensions.XValidations = apiextensions.ValidationRules{ s.XValidations = apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,
MessageExpression: messageExpression, MessageExpression: messageExpression,
@ -4582,7 +4582,7 @@ func withRuleAndMessageExpression(s schema.Structural, rule, messageExpression s
} }
func withRulePtr(s *schema.Structural, rule string) *schema.Structural { func withRulePtr(s *schema.Structural, rule string) *schema.Structural {
s.Extensions.XValidations = apiextensions.ValidationRules{ s.XValidations = apiextensions.ValidationRules{
{ {
Rule: rule, Rule: rule,
}, },

View File

@ -100,10 +100,10 @@ var (
mapSchema = schema.Structural{ mapSchema = schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{ },
Bool: true, AdditionalProperties: &schema.StructuralOrBool{
Structural: &stringSchema, Bool: true,
}, Structural: &stringSchema,
}, },
} }
) )

View File

@ -18,6 +18,7 @@ package schema
import ( import (
"fmt" "fmt"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
) )
@ -62,10 +63,16 @@ func NewStructural(s *apiextensions.JSONSchemaProps) (*Structural, error) {
return nil, err return nil, err
} }
vx, err := newValidationExtensions(s)
if err != nil {
return nil, err
}
ss := &Structural{ ss := &Structural{
Generic: *g, Generic: *g,
Extensions: *x, Extensions: *x,
ValueValidation: vv, ValueValidation: vv,
ValidationExtensions: *vx,
} }
if s.Items != nil { if s.Items != nil {
@ -91,6 +98,18 @@ func NewStructural(s *apiextensions.JSONSchemaProps) (*Structural, error) {
} }
} }
if s.AdditionalProperties != nil {
if s.AdditionalProperties.Schema != nil {
additionalPropertiesSchema, err := NewStructural(s.AdditionalProperties.Schema)
if err != nil {
return nil, err
}
ss.AdditionalProperties = &StructuralOrBool{Structural: additionalPropertiesSchema, Bool: true}
} else {
ss.AdditionalProperties = &StructuralOrBool{Bool: s.AdditionalProperties.Allows}
}
}
return ss, nil return ss, nil
} }
@ -108,18 +127,6 @@ func newGenerics(s *apiextensions.JSONSchemaProps) (*Generic, error) {
g.Default = JSON{interface{}(*s.Default)} g.Default = JSON{interface{}(*s.Default)}
} }
if s.AdditionalProperties != nil {
if s.AdditionalProperties.Schema != nil {
ss, err := NewStructural(s.AdditionalProperties.Schema)
if err != nil {
return nil, err
}
g.AdditionalProperties = &StructuralOrBool{Structural: ss, Bool: true}
} else {
g.AdditionalProperties = &StructuralOrBool{Bool: s.AdditionalProperties.Allows}
}
}
return g, nil return g, nil
} }
@ -205,10 +212,16 @@ func newNestedValueValidation(s *apiextensions.JSONSchemaProps) (*NestedValueVal
return nil, err return nil, err
} }
vx, err := newValidationExtensions(s)
if err != nil {
return nil, err
}
v := &NestedValueValidation{ v := &NestedValueValidation{
ValueValidation: *vv, ValueValidation: *vv,
ForbiddenGenerics: *g, ValidationExtensions: *vx,
ForbiddenExtensions: *x, ForbiddenGenerics: *g,
ForbiddenExtensions: *x,
} }
if s.Items != nil { if s.Items != nil {
@ -232,6 +245,18 @@ func newNestedValueValidation(s *apiextensions.JSONSchemaProps) (*NestedValueVal
v.Properties[k] = *nvv v.Properties[k] = *nvv
} }
} }
if s.AdditionalProperties != nil {
if s.AdditionalProperties.Schema != nil {
additionalPropertiesSchema, err := newNestedValueValidation(s.AdditionalProperties.Schema)
if err != nil {
return nil, err
}
v.AdditionalProperties = additionalPropertiesSchema
} else if s.AdditionalProperties.Allows {
v.AdditionalProperties = &NestedValueValidation{}
}
}
return v, nil return v, nil
} }
@ -248,9 +273,6 @@ func newExtensions(s *apiextensions.JSONSchemaProps) (*Extensions, error) {
XListType: s.XListType, XListType: s.XListType,
XMapType: s.XMapType, XMapType: s.XMapType,
} }
if err := apiextensionsv1.Convert_apiextensions_ValidationRules_To_v1_ValidationRules(&s.XValidations, &ret.XValidations, nil); err != nil {
return nil, err
}
if s.XPreserveUnknownFields != nil { if s.XPreserveUnknownFields != nil {
if !*s.XPreserveUnknownFields { if !*s.XPreserveUnknownFields {
@ -262,6 +284,19 @@ func newExtensions(s *apiextensions.JSONSchemaProps) (*Extensions, error) {
return ret, nil return ret, nil
} }
func newValidationExtensions(s *apiextensions.JSONSchemaProps) (*ValidationExtensions, error) {
if s == nil {
return nil, nil
}
ret := &ValidationExtensions{}
if err := apiextensionsv1.Convert_apiextensions_ValidationRules_To_v1_ValidationRules(&s.XValidations, &ret.XValidations, nil); err != nil {
return nil, err
}
return ret, nil
}
// validateUnsupportedFields checks that those fields rejected by validation are actually unset. // validateUnsupportedFields checks that those fields rejected by validation are actually unset.
func validateUnsupportedFields(s *apiextensions.JSONSchemaProps) error { func validateUnsupportedFields(s *apiextensions.JSONSchemaProps) error {
if len(s.ID) > 0 { if len(s.ID) > 0 {

View File

@ -99,19 +99,17 @@ func TestDefault(t *testing.T) {
}, },
}, },
"additionalProperties": { "additionalProperties": {
Generic: structuralschema.Generic{ AdditionalProperties: &structuralschema.StructuralOrBool{
AdditionalProperties: &structuralschema.StructuralOrBool{ Structural: &structuralschema.Structural{
Structural: &structuralschema.Structural{ Properties: map[string]structuralschema.Structural{
Properties: map[string]structuralschema.Structural{ "a": {
"a": { Generic: structuralschema.Generic{
Generic: structuralschema.Generic{ Default: structuralschema.JSON{Object: "alpha"},
Default: structuralschema.JSON{Object: "alpha"},
},
}, },
"b": { },
Generic: structuralschema.Generic{ "b": {
Default: structuralschema.JSON{Object: "beta"}, Generic: structuralschema.Generic{
}, Default: structuralschema.JSON{Object: "beta"},
}, },
}, },
}, },
@ -182,34 +180,28 @@ func TestDefault(t *testing.T) {
}, },
}, `{"a": "A"}`}, }, `{"a": "A"}`},
{"null in nullable object with additionalProperties", `{"a": null}`, &structuralschema.Structural{ {"null in nullable object with additionalProperties", `{"a": null}`, &structuralschema.Structural{
Generic: structuralschema.Generic{ AdditionalProperties: &structuralschema.StructuralOrBool{
AdditionalProperties: &structuralschema.StructuralOrBool{ Structural: &structuralschema.Structural{
Structural: &structuralschema.Structural{ Generic: structuralschema.Generic{
Generic: structuralschema.Generic{ Nullable: true,
Nullable: true, Default: structuralschema.JSON{Object: "A"},
Default: structuralschema.JSON{Object: "A"},
},
}, },
}, },
}, },
}, `{"a": null}`}, }, `{"a": null}`},
{"null in non-nullable object with additionalProperties", `{"a": null}`, &structuralschema.Structural{ {"null in non-nullable object with additionalProperties", `{"a": null}`, &structuralschema.Structural{
Generic: structuralschema.Generic{ AdditionalProperties: &structuralschema.StructuralOrBool{
AdditionalProperties: &structuralschema.StructuralOrBool{ Structural: &structuralschema.Structural{
Structural: &structuralschema.Structural{ Generic: structuralschema.Generic{
Generic: structuralschema.Generic{ Nullable: false,
Nullable: false, Default: structuralschema.JSON{Object: "A"},
Default: structuralschema.JSON{Object: "A"},
},
}, },
}, },
}, },
}, `{"a": "A"}`}, }, `{"a": "A"}`},
{"null unknown field", `{"a": null}`, &structuralschema.Structural{ {"null unknown field", `{"a": null}`, &structuralschema.Structural{
Generic: structuralschema.Generic{ AdditionalProperties: &structuralschema.StructuralOrBool{
AdditionalProperties: &structuralschema.StructuralOrBool{ Bool: true,
Bool: true,
},
}, },
}, `{"a": null}`}, }, `{"a": null}`},
} }

View File

@ -37,10 +37,16 @@ func (s *Structural) ToKubeOpenAPI() *spec.Schema {
ret.Properties[k] = *v.ToKubeOpenAPI() ret.Properties[k] = *v.ToKubeOpenAPI()
} }
} }
if s.AdditionalProperties != nil {
ret.AdditionalProperties = &spec.SchemaOrBool{
Allows: s.AdditionalProperties.Bool,
Schema: s.AdditionalProperties.Structural.ToKubeOpenAPI(),
}
}
s.Generic.toKubeOpenAPI(ret) s.Generic.toKubeOpenAPI(ret)
s.Extensions.toKubeOpenAPI(ret) s.Extensions.toKubeOpenAPI(ret)
s.ValueValidation.toKubeOpenAPI(ret) s.ValueValidation.toKubeOpenAPI(ret)
s.ValidationExtensions.toKubeOpenAPI(ret)
return ret return ret
} }
@ -53,12 +59,6 @@ func (g *Generic) toKubeOpenAPI(ret *spec.Schema) {
ret.Type = spec.StringOrArray{g.Type} ret.Type = spec.StringOrArray{g.Type}
} }
ret.Nullable = g.Nullable ret.Nullable = g.Nullable
if g.AdditionalProperties != nil {
ret.AdditionalProperties = &spec.SchemaOrBool{
Allows: g.AdditionalProperties.Bool,
Schema: g.AdditionalProperties.Structural.ToKubeOpenAPI(),
}
}
ret.Description = g.Description ret.Description = g.Description
ret.Title = g.Title ret.Title = g.Title
ret.Default = g.Default.Object ret.Default = g.Default.Object
@ -87,6 +87,13 @@ func (x *Extensions) toKubeOpenAPI(ret *spec.Schema) {
if x.XMapType != nil { if x.XMapType != nil {
ret.VendorExtensible.AddExtension("x-kubernetes-map-type", *x.XMapType) ret.VendorExtensible.AddExtension("x-kubernetes-map-type", *x.XMapType)
} }
}
func (x *ValidationExtensions) toKubeOpenAPI(ret *spec.Schema) {
if x == nil {
return
}
if len(x.XValidations) > 0 { if len(x.XValidations) > 0 {
ret.VendorExtensible.AddExtension("x-kubernetes-validations", x.XValidations) ret.VendorExtensible.AddExtension("x-kubernetes-validations", x.XValidations)
} }
@ -138,6 +145,7 @@ func (vv *NestedValueValidation) toKubeOpenAPI() *spec.Schema {
ret := &spec.Schema{} ret := &spec.Schema{}
vv.ValueValidation.toKubeOpenAPI(ret) vv.ValueValidation.toKubeOpenAPI(ret)
vv.ValidationExtensions.toKubeOpenAPI(ret)
if vv.Items != nil { if vv.Items != nil {
ret.Items = &spec.SchemaOrArray{Schema: vv.Items.toKubeOpenAPI()} ret.Items = &spec.SchemaOrArray{Schema: vv.Items.toKubeOpenAPI()}
} }
@ -149,6 +157,5 @@ func (vv *NestedValueValidation) toKubeOpenAPI() *spec.Schema {
} }
vv.ForbiddenGenerics.toKubeOpenAPI(ret) // normally empty. Exception: int-or-string vv.ForbiddenGenerics.toKubeOpenAPI(ret) // normally empty. Exception: int-or-string
vv.ForbiddenExtensions.toKubeOpenAPI(ret) // shouldn't do anything vv.ForbiddenExtensions.toKubeOpenAPI(ret) // shouldn't do anything
return ret return ret
} }

View File

@ -202,18 +202,18 @@ func TestValidateListSetsAndMaps(t *testing.T) {
schema: &schema.Structural{ schema: &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{ },
Structural: &schema.Structural{ AdditionalProperties: &schema.StructuralOrBool{
Structural: &schema.Structural{
Generic: schema.Generic{
Type: "array",
},
Extensions: schema.Extensions{
XListType: strPtr("set"),
},
Items: &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "array", Type: "string",
},
Extensions: schema.Extensions{
XListType: strPtr("set"),
},
Items: &schema.Structural{
Generic: schema.Generic{
Type: "string",
},
}, },
}, },
}, },
@ -387,11 +387,11 @@ func TestValidateListSetsAndMaps(t *testing.T) {
Items: &schema.Structural{ Items: &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{ },
Structural: &schema.Structural{ AdditionalProperties: &schema.StructuralOrBool{
Generic: schema.Generic{ Structural: &schema.Structural{
Type: "string", Generic: schema.Generic{
}, Type: "string",
}, },
}, },
}, },
@ -407,11 +407,11 @@ func TestValidateListSetsAndMaps(t *testing.T) {
Items: &schema.Structural{ Items: &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{ },
Structural: &schema.Structural{ AdditionalProperties: &schema.StructuralOrBool{
Generic: schema.Generic{ Structural: &schema.Structural{
Type: "string", Generic: schema.Generic{
}, Type: "string",
}, },
}, },
}, },
@ -427,12 +427,12 @@ func TestValidateListSetsAndMaps(t *testing.T) {
Items: &schema.Structural{ Items: &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{ },
Structural: &schema.Structural{ AdditionalProperties: &schema.StructuralOrBool{
Generic: schema.Generic{ Structural: &schema.Structural{
Type: "string", Generic: schema.Generic{
Nullable: true, Type: "string",
}, Nullable: true,
}, },
}, },
}, },
@ -485,11 +485,11 @@ func TestValidateListSetsAndMaps(t *testing.T) {
Items: &schema.Structural{ Items: &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{ },
Structural: &schema.Structural{ AdditionalProperties: &schema.StructuralOrBool{
Generic: schema.Generic{ Structural: &schema.Structural{
Type: "string", Generic: schema.Generic{
}, Type: "string",
}, },
}, },
}, },
@ -670,11 +670,11 @@ func TestValidateListSetsAndMaps(t *testing.T) {
Items: &schema.Structural{ Items: &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{ },
Structural: &schema.Structural{ AdditionalProperties: &schema.StructuralOrBool{
Generic: schema.Generic{ Structural: &schema.Structural{
Type: "string", Generic: schema.Generic{
}, Type: "string",
}, },
}, },
}, },
@ -691,11 +691,11 @@ func TestValidateListSetsAndMaps(t *testing.T) {
Items: &schema.Structural{ Items: &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{ },
Structural: &schema.Structural{ AdditionalProperties: &schema.StructuralOrBool{
Generic: schema.Generic{ Structural: &schema.Structural{
Type: "string", Generic: schema.Generic{
}, Type: "string",
}, },
}, },
}, },
@ -712,11 +712,11 @@ func TestValidateListSetsAndMaps(t *testing.T) {
Items: &schema.Structural{ Items: &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{ },
Structural: &schema.Structural{ AdditionalProperties: &schema.StructuralOrBool{
Generic: schema.Generic{ Structural: &schema.Structural{
Type: "string", Generic: schema.Generic{
}, Type: "string",
}, },
}, },
}, },
@ -733,12 +733,12 @@ func TestValidateListSetsAndMaps(t *testing.T) {
Items: &schema.Structural{ Items: &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{ },
Structural: &schema.Structural{ AdditionalProperties: &schema.StructuralOrBool{
Generic: schema.Generic{ Structural: &schema.Structural{
Type: "string", Generic: schema.Generic{
Nullable: true, Type: "string",
}, Nullable: true,
}, },
}, },
}, },
@ -856,12 +856,12 @@ func TestValidateListSetsAndMaps(t *testing.T) {
Items: &schema.Structural{ Items: &schema.Structural{
Generic: schema.Generic{ Generic: schema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &schema.StructuralOrBool{ },
Structural: &schema.Structural{ AdditionalProperties: &schema.StructuralOrBool{
Generic: schema.Generic{ Structural: &schema.Structural{
Type: "string", Generic: schema.Generic{
Nullable: true, Type: "string",
}, Nullable: true,
}, },
}, },
}, },

View File

@ -171,11 +171,9 @@ func TestValidate(t *testing.T) {
}, },
}, },
"additionalProperties": { "additionalProperties": {
Generic: structuralschema.Generic{ AdditionalProperties: &structuralschema.StructuralOrBool{
AdditionalProperties: &structuralschema.StructuralOrBool{ Structural: &structuralschema.Structural{
Structural: &structuralschema.Structural{ Extensions: structuralschema.Extensions{XEmbeddedResource: true},
Extensions: structuralschema.Extensions{XEmbeddedResource: true},
},
}, },
}, },
}, },

View File

@ -146,12 +146,12 @@ func TestPrune(t *testing.T) {
Extensions: structuralschema.Extensions{XPreserveUnknownFields: true}, Extensions: structuralschema.Extensions{XPreserveUnknownFields: true},
Generic: structuralschema.Generic{ Generic: structuralschema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &structuralschema.StructuralOrBool{ },
Structural: &structuralschema.Structural{ AdditionalProperties: &structuralschema.StructuralOrBool{
Generic: structuralschema.Generic{Type: "object"}, Structural: &structuralschema.Structural{
Properties: map[string]structuralschema.Structural{ Generic: structuralschema.Generic{Type: "object"},
"specified": {Generic: structuralschema.Generic{Type: "object"}}, Properties: map[string]structuralschema.Structural{
}, "specified": {Generic: structuralschema.Generic{Type: "object"}},
}, },
}, },
}, },
@ -159,12 +159,12 @@ func TestPrune(t *testing.T) {
"preservingAdditionalPropertiesKeyPruneValues": { "preservingAdditionalPropertiesKeyPruneValues": {
Generic: structuralschema.Generic{ Generic: structuralschema.Generic{
Type: "object", Type: "object",
AdditionalProperties: &structuralschema.StructuralOrBool{ },
Structural: &structuralschema.Structural{ AdditionalProperties: &structuralschema.StructuralOrBool{
Generic: structuralschema.Generic{Type: "object"}, Structural: &structuralschema.Structural{
Properties: map[string]structuralschema.Structural{ Generic: structuralschema.Generic{Type: "object"},
"specified": {Generic: structuralschema.Generic{Type: "object"}}, Properties: map[string]structuralschema.Structural{
}, "specified": {Generic: structuralschema.Generic{Type: "object"}},
}, },
}, },
}, },
@ -203,12 +203,10 @@ func TestPrune(t *testing.T) {
Properties: map[string]structuralschema.Structural{ Properties: map[string]structuralschema.Structural{
"a": {}, "a": {},
"c": { "c": {
Generic: structuralschema.Generic{ AdditionalProperties: &structuralschema.StructuralOrBool{
AdditionalProperties: &structuralschema.StructuralOrBool{ Structural: &structuralschema.Structural{
Structural: &structuralschema.Structural{ Generic: structuralschema.Generic{
Generic: structuralschema.Generic{ Type: "integer",
Type: "integer",
},
}, },
}, },
}, },
@ -220,10 +218,8 @@ func TestPrune(t *testing.T) {
Properties: map[string]structuralschema.Structural{ Properties: map[string]structuralschema.Structural{
"a": {}, "a": {},
"c": { "c": {
Generic: structuralschema.Generic{ AdditionalProperties: &structuralschema.StructuralOrBool{
AdditionalProperties: &structuralschema.StructuralOrBool{ Bool: false,
Bool: false,
},
}, },
}, },
}, },

View File

@ -25,11 +25,13 @@ import (
// Structural represents a structural schema. // Structural represents a structural schema.
type Structural struct { type Structural struct {
Items *Structural Items *Structural
Properties map[string]Structural Properties map[string]Structural
AdditionalProperties *StructuralOrBool
Generic Generic
Extensions Extensions
ValidationExtensions
ValueValidation *ValueValidation ValueValidation *ValueValidation
} }
@ -51,11 +53,10 @@ type Generic struct {
// It can be object, array, number, integer, boolean, string. // It can be object, array, number, integer, boolean, string.
// It is optional only if x-kubernetes-preserve-unknown-fields // It is optional only if x-kubernetes-preserve-unknown-fields
// or x-kubernetes-int-or-string is true. // or x-kubernetes-int-or-string is true.
Type string Type string
Title string Title string
Default JSON Default JSON
AdditionalProperties *StructuralOrBool Nullable bool
Nullable bool
} }
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true
@ -128,7 +129,13 @@ type Extensions struct {
// Atomic maps will be entirely replaced when updated. // Atomic maps will be entirely replaced when updated.
// +optional // +optional
XMapType *string XMapType *string
}
// +k8s:deepcopy-gen=true
// ValidationExtensions contains the Kubernetes OpenAPI v3 extensions that are
// used for validation rather than structure.
type ValidationExtensions struct {
// x-kubernetes-validations describes a list of validation rules for expression validation. // x-kubernetes-validations describes a list of validation rules for expression validation.
// Use the v1 struct since this gets serialized as an extension. // Use the v1 struct since this gets serialized as an extension.
XValidations apiextensionsv1.ValidationRules XValidations apiextensionsv1.ValidationRules
@ -166,9 +173,11 @@ type ValueValidation struct {
// under a logical junctor, and catch all structs for generic and vendor extensions schema fields. // under a logical junctor, and catch all structs for generic and vendor extensions schema fields.
type NestedValueValidation struct { type NestedValueValidation struct {
ValueValidation ValueValidation
ValidationExtensions
Items *NestedValueValidation Items *NestedValueValidation
Properties map[string]NestedValueValidation Properties map[string]NestedValueValidation
AdditionalProperties *NestedValueValidation
// Anything set in the following will make the scheme // Anything set in the following will make the scheme
// non-structural, with the exception of these two patterns if // non-structural, with the exception of these two patterns if

View File

@ -91,6 +91,16 @@ func validateStructuralInvariants(s *Structural, lvl level, fldPath *field.Path)
for k, v := range s.Properties { for k, v := range s.Properties {
allErrs = append(allErrs, validateStructuralInvariants(&v, fieldLevel, fldPath.Child("properties").Key(k))...) allErrs = append(allErrs, validateStructuralInvariants(&v, fieldLevel, fldPath.Child("properties").Key(k))...)
} }
if s.AdditionalProperties != nil {
if lvl == rootLevel {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "must not be used at the root"))
}
if s.AdditionalProperties.Structural != nil {
allErrs = append(allErrs, validateStructuralInvariants(s.AdditionalProperties.Structural, fieldLevel, fldPath.Child("additionalProperties"))...)
}
}
allErrs = append(allErrs, validateGeneric(&s.Generic, lvl, fldPath)...) allErrs = append(allErrs, validateGeneric(&s.Generic, lvl, fldPath)...)
allErrs = append(allErrs, validateExtensions(&s.Extensions, fldPath)...) allErrs = append(allErrs, validateExtensions(&s.Extensions, fldPath)...)
@ -207,18 +217,7 @@ func validateGeneric(g *Generic, lvl level, fldPath *field.Path) field.ErrorList
return nil return nil
} }
allErrs := field.ErrorList{} return nil
if g.AdditionalProperties != nil {
if lvl == rootLevel {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "must not be used at the root"))
}
if g.AdditionalProperties.Structural != nil {
allErrs = append(allErrs, validateStructuralInvariants(g.AdditionalProperties.Structural, fieldLevel, fldPath.Child("additionalProperties"))...)
}
}
return allErrs
} }
// validateExtensions checks Kubernetes vendor extensions of a structural schema. // validateExtensions checks Kubernetes vendor extensions of a structural schema.
@ -282,6 +281,7 @@ func validateNestedValueValidation(v *NestedValueValidation, skipAnyOf, skipAllO
allErrs = append(allErrs, validateValueValidation(&v.ValueValidation, skipAnyOf, skipAllOfAnyOf, lvl, fldPath)...) allErrs = append(allErrs, validateValueValidation(&v.ValueValidation, skipAnyOf, skipAllOfAnyOf, lvl, fldPath)...)
allErrs = append(allErrs, validateNestedValueValidation(v.Items, false, false, lvl, fldPath.Child("items"))...) allErrs = append(allErrs, validateNestedValueValidation(v.Items, false, false, lvl, fldPath.Child("items"))...)
allErrs = append(allErrs, validateNestedValueValidation(v.AdditionalProperties, false, false, lvl, fldPath.Child("additionalProperties"))...)
for k, fld := range v.Properties { for k, fld := range v.Properties {
allErrs = append(allErrs, validateNestedValueValidation(&fld, false, false, fieldLevel, fldPath.Child("properties").Key(k))...) allErrs = append(allErrs, validateNestedValueValidation(&fld, false, false, fieldLevel, fldPath.Child("properties").Key(k))...)
@ -290,9 +290,6 @@ func validateNestedValueValidation(v *NestedValueValidation, skipAnyOf, skipAllO
if len(v.ForbiddenGenerics.Type) > 0 { if len(v.ForbiddenGenerics.Type) > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("type"), "must be empty to be structural")) allErrs = append(allErrs, field.Forbidden(fldPath.Child("type"), "must be empty to be structural"))
} }
if v.ForbiddenGenerics.AdditionalProperties != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("additionalProperties"), "must be undefined to be structural"))
}
if v.ForbiddenGenerics.Default.Object != nil { if v.ForbiddenGenerics.Default.Object != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("default"), "must be undefined to be structural")) allErrs = append(allErrs, field.Forbidden(fldPath.Child("default"), "must be undefined to be structural"))
} }
@ -324,9 +321,6 @@ func validateNestedValueValidation(v *NestedValueValidation, skipAnyOf, skipAllO
if v.ForbiddenExtensions.XMapType != nil { if v.ForbiddenExtensions.XMapType != nil {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-map-type"), "must be undefined to be structural")) allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-map-type"), "must be undefined to be structural"))
} }
if len(v.ForbiddenExtensions.XValidations) > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("x-kubernetes-validations"), "must be empty to be structural"))
}
// forbid reasoning about metadata because it can lead to metadata restriction we don't want // forbid reasoning about metadata because it can lead to metadata restriction we don't want
if _, found := v.Properties["metadata"]; found { if _, found := v.Properties["metadata"]; found {

View File

@ -50,8 +50,8 @@ func (m *Visitor) visitStructural(s *Structural) bool {
s.Properties[k] = v s.Properties[k] = v
} }
} }
if s.Generic.AdditionalProperties != nil && s.Generic.AdditionalProperties.Structural != nil { if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil {
m.visitStructural(s.Generic.AdditionalProperties.Structural) m.visitStructural(s.AdditionalProperties.Structural)
} }
if s.ValueValidation != nil { if s.ValueValidation != nil {
for i := range s.ValueValidation.AllOf { for i := range s.ValueValidation.AllOf {
@ -86,8 +86,8 @@ func (m *Visitor) visitNestedValueValidation(vv *NestedValueValidation) bool {
vv.Properties[k] = v vv.Properties[k] = v
} }
} }
if vv.ForbiddenGenerics.AdditionalProperties != nil && vv.ForbiddenGenerics.AdditionalProperties.Structural != nil { if vv.AdditionalProperties != nil {
m.visitStructural(vv.ForbiddenGenerics.AdditionalProperties.Structural) m.visitNestedValueValidation(vv.AdditionalProperties)
} }
for i := range vv.ValueValidation.AllOf { for i := range vv.ValueValidation.AllOf {
m.visitNestedValueValidation(&vv.ValueValidation.AllOf[i]) m.visitNestedValueValidation(&vv.ValueValidation.AllOf[i])