diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/unfold.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/unfold.go new file mode 100644 index 00000000000..d135757ee7b --- /dev/null +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/unfold.go @@ -0,0 +1,63 @@ +/* +Copyright 2019 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. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package schema + +// Unfold expands vendor extensions of a structural schema. +// It mutates the receiver. +func (s *Structural) Unfold() *Structural { + if s == nil { + return nil + } + + mapper := Visitor{ + Structural: func(s *Structural) bool { + if !s.XIntOrString { + return false + } + + skipAnyOf := isIntOrStringAnyOfPattern(s) + skipFirstAllOfAnyOf := isIntOrStringAllOfPattern(s) + if skipAnyOf || skipFirstAllOfAnyOf { + return false + } + + if s.AnyOf == nil { + s.AnyOf = []NestedValueValidation{ + {ForbiddenGenerics: Generic{Type: "integer"}}, + {ForbiddenGenerics: Generic{Type: "string"}}, + } + } else { + s.AllOf = append([]NestedValueValidation{ + { + ValueValidation: ValueValidation{ + AnyOf: []NestedValueValidation{ + {ForbiddenGenerics: Generic{Type: "integer"}}, + {ForbiddenGenerics: Generic{Type: "string"}}, + }, + }, + }, + }, s.AllOf...) + } + + return true + }, + NestedValueValidation: nil, // x-kubernetes-int-or-string cannot be set in nested value validation + } + mapper.Visit(s) + + return s +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/validation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/validation.go index a6da5b1c899..e864fe8c249 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/validation.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/validation.go @@ -97,15 +97,8 @@ func validateStructuralInvariants(s *Structural, lvl level, fldPath *field.Path) // - type: integer // - type: string // - ... zero or more - skipAnyOf := false - skipFirstAllOfAnyOf := false - if s.XIntOrString && s.ValueValidation != nil { - if len(s.ValueValidation.AnyOf) == 2 && reflect.DeepEqual(s.ValueValidation.AnyOf, intOrStringAnyOf) { - skipAnyOf = true - } else if len(s.ValueValidation.AllOf) >= 1 && len(s.ValueValidation.AllOf[0].AnyOf) == 2 && reflect.DeepEqual(s.ValueValidation.AllOf[0].AnyOf, intOrStringAnyOf) { - skipFirstAllOfAnyOf = true - } - } + skipAnyOf := isIntOrStringAnyOfPattern(s) + skipFirstAllOfAnyOf := isIntOrStringAllOfPattern(s) allErrs = append(allErrs, validateValueValidation(s.ValueValidation, skipAnyOf, skipFirstAllOfAnyOf, lvl, fldPath)...) @@ -157,6 +150,20 @@ func validateStructuralInvariants(s *Structural, lvl level, fldPath *field.Path) return allErrs } +func isIntOrStringAnyOfPattern(s *Structural) bool { + if s == nil || s.ValueValidation == nil { + return false + } + return len(s.ValueValidation.AnyOf) == 2 && reflect.DeepEqual(s.ValueValidation.AnyOf, intOrStringAnyOf) +} + +func isIntOrStringAllOfPattern(s *Structural) bool { + if s == nil || s.ValueValidation == nil { + return false + } + return len(s.ValueValidation.AllOf) >= 1 && len(s.ValueValidation.AllOf[0].AnyOf) == 2 && reflect.DeepEqual(s.ValueValidation.AllOf[0].AnyOf, intOrStringAnyOf) +} + // validateGeneric checks the generic fields of a structural schema. func validateGeneric(g *Generic, lvl level, fldPath *field.Path) field.ErrorList { if g == nil {