Use serializable struct for x-kubernetes-validations in openapi

This commit is contained in:
Jordan Liggitt 2022-02-05 17:58:07 -05:00
parent 064763ebdd
commit 5efe1e648b
10 changed files with 98 additions and 9 deletions

View File

@ -18,6 +18,7 @@ package v1
import ( import (
"bytes" "bytes"
unsafe "unsafe"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiequality "k8s.io/apimachinery/pkg/api/equality" apiequality "k8s.io/apimachinery/pkg/api/equality"
@ -207,3 +208,8 @@ func Convert_apiextensions_CustomResourceConversion_To_v1_CustomResourceConversi
} }
return nil return nil
} }
func Convert_apiextensions_ValidationRules_To_v1_ValidationRules(in *apiextensions.ValidationRules, out *ValidationRules, s conversion.Scope) error {
*out = *(*ValidationRules)(unsafe.Pointer(in))
return nil
}

View File

@ -18,6 +18,7 @@ package v1
import ( import (
"encoding/json" "encoding/json"
fmt "fmt"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
@ -660,3 +661,71 @@ func TestJSONRoundTrip(t *testing.T) {
}) })
} }
} }
func TestMemoryEqual(t *testing.T) {
testcases := []struct {
a interface{}
b interface{}
}{
{apiextensions.JSONSchemaProps{}.XValidations, JSONSchemaProps{}.XValidations},
}
for _, tc := range testcases {
aType := reflect.TypeOf(tc.a)
bType := reflect.TypeOf(tc.b)
t.Run(aType.String(), func(t *testing.T) {
assertEqualTypes(t, nil, aType, bType)
})
}
}
func assertEqualTypes(t *testing.T, path []string, a, b reflect.Type) {
if a == b {
return
}
if a.Kind() != b.Kind() {
fatalTypeError(t, path, a, b, "mismatched Kind")
}
switch a.Kind() {
case reflect.Struct:
aFields := a.NumField()
bFields := b.NumField()
if aFields != bFields {
fatalTypeError(t, path, a, b, "mismatched field count")
}
for i := 0; i < aFields; i++ {
aField := a.Field(i)
bField := b.Field(i)
if aField.Name != bField.Name {
fatalTypeError(t, path, a, b, fmt.Sprintf("mismatched field name %d: %s %s", i, aField.Name, bField.Name))
}
if aField.Offset != bField.Offset {
fatalTypeError(t, path, a, b, fmt.Sprintf("mismatched field offset %d: %v %v", i, aField.Offset, bField.Offset))
}
if aField.Anonymous != bField.Anonymous {
fatalTypeError(t, path, a, b, fmt.Sprintf("mismatched field anonymous %d: %v %v", i, aField.Anonymous, bField.Anonymous))
}
if !reflect.DeepEqual(aField.Index, bField.Index) {
fatalTypeError(t, path, a, b, fmt.Sprintf("mismatched field index %d: %v %v", i, aField.Index, bField.Index))
}
path = append(path, aField.Name)
assertEqualTypes(t, path, aField.Type, bField.Type)
path = path[:len(path)-1]
}
case reflect.Ptr, reflect.Slice:
aElemType := a.Elem()
bElemType := b.Elem()
assertEqualTypes(t, path, aElemType, bElemType)
default:
fatalTypeError(t, path, a, b, "unhandled kind")
}
}
func fatalTypeError(t *testing.T, path []string, a, b reflect.Type, message string) {
t.Helper()
t.Fatalf("%s: %s: %s %s", strings.Join(path, "."), message, a, b)
}

View File

@ -242,6 +242,11 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil { }); err != nil {
return err return err
} }
if err := s.AddConversionFunc((*apiextensions.ValidationRules)(nil), (*ValidationRules)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_apiextensions_ValidationRules_To_v1_ValidationRules(a.(*apiextensions.ValidationRules), b.(*ValidationRules), scope)
}); err != nil {
return err
}
if err := s.AddConversionFunc((*CustomResourceConversion)(nil), (*apiextensions.CustomResourceConversion)(nil), func(a, b interface{}, scope conversion.Scope) error { if err := s.AddConversionFunc((*CustomResourceConversion)(nil), (*apiextensions.CustomResourceConversion)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_CustomResourceConversion_To_apiextensions_CustomResourceConversion(a.(*CustomResourceConversion), b.(*apiextensions.CustomResourceConversion), scope) return Convert_v1_CustomResourceConversion_To_apiextensions_CustomResourceConversion(a.(*CustomResourceConversion), b.(*apiextensions.CustomResourceConversion), scope)
}); err != nil { }); err != nil {

View File

@ -20,7 +20,7 @@ import (
"strings" "strings"
"testing" "testing"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
) )

View File

@ -24,7 +24,7 @@ import (
"github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter" "github.com/google/cel-go/interpreter"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
"k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model" "k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"

View File

@ -22,7 +22,7 @@ import (
"strings" "strings"
"testing" "testing"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
) )

View File

@ -19,6 +19,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"
) )
// NewStructural converts an OpenAPI v3 schema into a structural schema. A pre-validated JSONSchemaProps will // NewStructural converts an OpenAPI v3 schema into a structural schema. A pre-validated JSONSchemaProps will
@ -246,7 +247,9 @@ func newExtensions(s *apiextensions.JSONSchemaProps) (*Extensions, error) {
XListMapKeys: s.XListMapKeys, XListMapKeys: s.XListMapKeys,
XListType: s.XListType, XListType: s.XListType,
XMapType: s.XMapType, XMapType: s.XMapType,
XValidations: s.XValidations, }
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 {

View File

@ -17,7 +17,7 @@ limitations under the License.
package schema package schema
import ( import (
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -130,7 +130,8 @@ type Extensions struct {
XMapType *string XMapType *string
// x-kubernetes-validations describes a list of validation rules for expression validation. // x-kubernetes-validations describes a list of validation rules for expression validation.
XValidations apiextensions.ValidationRules // Use the v1 struct since this gets serialized as an extension.
XValidations apiextensionsv1.ValidationRules
} }
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true

View File

@ -22,7 +22,7 @@ limitations under the License.
package schema package schema
import ( import (
apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
) )
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@ -45,7 +45,7 @@ func (in *Extensions) DeepCopyInto(out *Extensions) {
} }
if in.XValidations != nil { if in.XValidations != nil {
in, out := &in.XValidations, &out.XValidations in, out := &in.XValidations, &out.XValidations
*out = make(apiextensions.ValidationRules, len(*in)) *out = make(v1.ValidationRules, len(*in))
copy(*out, *in) copy(*out, *in)
} }
return return

View File

@ -21,6 +21,7 @@ import (
"strings" "strings"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
openapierrors "k8s.io/kube-openapi/pkg/validation/errors" openapierrors "k8s.io/kube-openapi/pkg/validation/errors"
"k8s.io/kube-openapi/pkg/validation/spec" "k8s.io/kube-openapi/pkg/validation/spec"
@ -255,7 +256,11 @@ func ConvertJSONSchemaPropsWithPostProcess(in *apiextensions.JSONSchemaProps, ou
out.VendorExtensible.AddExtension("x-kubernetes-map-type", *in.XMapType) out.VendorExtensible.AddExtension("x-kubernetes-map-type", *in.XMapType)
} }
if len(in.XValidations) != 0 { if len(in.XValidations) != 0 {
out.VendorExtensible.AddExtension("x-kubernetes-validations", in.XValidations) var serializationValidationRules apiextensionsv1.ValidationRules
if err := apiextensionsv1.Convert_apiextensions_ValidationRules_To_v1_ValidationRules(&in.XValidations, &serializationValidationRules, nil); err != nil {
return err
}
out.VendorExtensible.AddExtension("x-kubernetes-validations", serializationValidationRules)
} }
return nil return nil
} }