diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/groupversion.go b/staging/src/k8s.io/apiserver/pkg/endpoints/groupversion.go index 1a5a7696663..f33d4bac64d 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/groupversion.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/groupversion.go @@ -34,7 +34,6 @@ import ( "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storageversion" - "k8s.io/kube-openapi/pkg/validation/spec" ) // ConvertabilityChecker indicates what versions a GroupKind is available in. @@ -95,9 +94,6 @@ type APIGroupVersion struct { MinRequestTimeout time.Duration - // OpenAPIModels exposes the OpenAPI models to each individual handler. - OpenAPIModels *spec.Swagger - // The limit on the request body size that would be accepted and decoded in a write request. // 0 means no limit. MaxRequestBodyBytes int64 diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go index b61965bd4fd..1306528653e 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go @@ -47,11 +47,16 @@ var fakeTypeConverter = func() fieldmanager.TypeConverter { if err != nil { panic(err) } - spec := spec.Swagger{} - if err := json.Unmarshal(data, &spec); err != nil { + swag := spec.Swagger{} + if err := json.Unmarshal(data, &swag); err != nil { panic(err) } - typeConverter, err := fieldmanager.NewTypeConverter(&spec, false) + convertedDefs := map[string]*spec.Schema{} + for k, v := range swag.Definitions { + vCopy := v + convertedDefs[k] = &vCopy + } + typeConverter, err := fieldmanager.NewTypeConverter(convertedDefs, false) if err != nil { panic(err) } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fieldmanager_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fieldmanager_test.go index 0d3c432a867..b51f5b5efa3 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fieldmanager_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/fieldmanager_test.go @@ -33,11 +33,18 @@ var fakeTypeConverter = func() internal.TypeConverter { if err != nil { panic(err) } + convertedDefs := map[string]*spec.Schema{} spec := spec.Swagger{} if err := json.Unmarshal(data, &spec); err != nil { panic(err) } - typeConverter, err := internal.NewTypeConverter(&spec, false) + + for k, v := range spec.Definitions { + vCopy := v + convertedDefs[k] = &vCopy + } + + typeConverter, err := internal.NewTypeConverter(convertedDefs, false) if err != nil { panic(err) } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go index a6254a4dc50..1ac96d7f7bd 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go @@ -42,23 +42,14 @@ type typeConverter struct { var _ TypeConverter = &typeConverter{} -// NewTypeConverter builds a TypeConverter from a spec.Swagger. This -// will automatically find the proper version of the object, and the -// corresponding schema information. -func NewTypeConverter(openapiSpec *spec.Swagger, preserveUnknownFields bool) (TypeConverter, error) { - pointerDefs := map[string]*spec.Schema{} - for k, v := range openapiSpec.Definitions { - vCopy := v - pointerDefs[k] = &vCopy - } - - typeSchema, err := schemaconv.ToSchemaFromOpenAPI(pointerDefs, preserveUnknownFields) +func NewTypeConverter(openapiSpec map[string]*spec.Schema, preserveUnknownFields bool) (TypeConverter, error) { + typeSchema, err := schemaconv.ToSchemaFromOpenAPI(openapiSpec, preserveUnknownFields) if err != nil { return nil, fmt.Errorf("failed to convert models to schema: %v", err) } typeParser := typed.Parser{Schema: smdschema.Schema{Types: typeSchema.Types}} - tr := indexModels(&typeParser, pointerDefs) + tr := indexModels(&typeParser, openapiSpec) return &typeConverter{parser: tr}, nil } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/versionconverter_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/versionconverter_test.go index 19e4476cb02..32f8edb34e9 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/versionconverter_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal/versionconverter_test.go @@ -36,11 +36,17 @@ var testTypeConverter = func() TypeConverter { if err != nil { panic(err) } - spec := spec.Swagger{} - if err := json.Unmarshal(data, &spec); err != nil { + swag := spec.Swagger{} + if err := json.Unmarshal(data, &swag); err != nil { panic(err) } - typeConverter, err := NewTypeConverter(&spec, false) + + convertedDefs := map[string]*spec.Schema{} + for k, v := range swag.Definitions { + vCopy := v + convertedDefs[k] = &vCopy + } + typeConverter, err := NewTypeConverter(convertedDefs, false) if err != nil { panic(err) } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/typeconverter.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/typeconverter.go index 0aa97f7e790..8772a307651 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/typeconverter.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/typeconverter.go @@ -34,9 +34,14 @@ func NewDeducedTypeConverter() TypeConverter { return internal.NewDeducedTypeConverter() } -// NewTypeConverter builds a TypeConverter from a proto.Models. This -// will automatically find the proper version of the object, and the +// NewTypeConverter builds a TypeConverter from a map of OpenAPIV3 schemas. +// This will automatically find the proper version of the object, and the // corresponding schema information. -func NewTypeConverter(openapiSpec *spec.Swagger, preserveUnknownFields bool) (TypeConverter, error) { +// The keys to the map must be consistent with the names +// used by Refs within the schemas. +// The schemas should conform to the Kubernetes Structural Schema OpenAPI +// restrictions found in docs: +// https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema +func NewTypeConverter(openapiSpec map[string]*spec.Schema, preserveUnknownFields bool) (TypeConverter, error) { return internal.NewTypeConverter(openapiSpec, preserveUnknownFields) } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go b/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go index 0b8f6ef509b..3fd5f437123 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go @@ -345,13 +345,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag isCreater = true } - var resetFields map[fieldpath.APIVersion]*fieldpath.Set - if a.group.OpenAPIModels != nil { - if resetFieldsStrategy, isResetFieldsStrategy := storage.(rest.ResetFieldsStrategy); isResetFieldsStrategy { - resetFields = resetFieldsStrategy.GetResetFields() - } - } - var versionedList interface{} if isLister { list := lister.NewList() @@ -680,7 +673,16 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag if a.group.MetaGroupVersion != nil { reqScope.MetaGroupVersion = *a.group.MetaGroupVersion } - if a.group.OpenAPIModels != nil { + + // Use TypeConverter's nil-ness as a proxy for whether SSA/OpenAPI is enabled + // This should be removed in the future and made unconditional + // https://github.com/kubernetes/kubernetes/pull/114998 + if a.group.TypeConverter != nil { + var resetFields map[fieldpath.APIVersion]*fieldpath.Set + if resetFieldsStrategy, isResetFieldsStrategy := storage.(rest.ResetFieldsStrategy); isResetFieldsStrategy { + resetFields = resetFieldsStrategy.GetResetFields() + } + reqScope.FieldManager, err = fieldmanager.NewDefaultFieldManager( a.group.TypeConverter, a.group.UnsafeConvertor, @@ -695,6 +697,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag return nil, nil, fmt.Errorf("failed to create field manager: %v", err) } } + for _, action := range actions { producedObject := storageMeta.ProducesObject(action.Verb) if producedObject == nil { diff --git a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go index 1a2554048bc..5d3c175b94d 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go +++ b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go @@ -51,7 +51,7 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" restclient "k8s.io/client-go/rest" "k8s.io/klog/v2" - openapibuilder2 "k8s.io/kube-openapi/pkg/builder" + openapibuilder3 "k8s.io/kube-openapi/pkg/builder3" openapicommon "k8s.io/kube-openapi/pkg/common" "k8s.io/kube-openapi/pkg/handler" "k8s.io/kube-openapi/pkg/handler3" @@ -87,7 +87,7 @@ type APIGroupInfo struct { // StaticOpenAPISpec is the spec derived from the definitions of all resources installed together. // It is set during InstallAPIGroups, InstallAPIGroup, and InstallLegacyAPIGroup. - StaticOpenAPISpec *spec.Swagger + StaticOpenAPISpec map[string]*spec.Schema } func (a *APIGroupInfo) destroyStorage() { @@ -666,7 +666,16 @@ func (s preparedGenericAPIServer) NonBlockingRun(stopCh <-chan struct{}, shutdow } // installAPIResources is a private method for installing the REST storage backing each api groupversionresource -func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo, openAPIModels *spec.Swagger) error { +func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo, openAPIModels map[string]*spec.Schema) error { + var typeConverter fieldmanager.TypeConverter + + if len(openAPIModels) > 0 { + var err error + typeConverter, err = fieldmanager.NewTypeConverter(openAPIModels, false) + if err != nil { + return err + } + } var resourceInfos []*storageversion.ResourceInfo for _, groupVersion := range apiGroupInfo.PrioritizedVersions { if len(apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version]) == 0 { @@ -681,16 +690,7 @@ func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *A if apiGroupInfo.OptionsExternalVersion != nil { apiGroupVersion.OptionsExternalVersion = apiGroupInfo.OptionsExternalVersion } - apiGroupVersion.OpenAPIModels = openAPIModels - - if openAPIModels != nil { - typeConverter, err := fieldmanager.NewTypeConverter(openAPIModels, false) - if err != nil { - return err - } - apiGroupVersion.TypeConverter = typeConverter - } - + apiGroupVersion.TypeConverter = typeConverter apiGroupVersion.MaxRequestBodyBytes = s.maxRequestBodyBytes discoveryAPIResources, r, err := apiGroupVersion.InstallREST(s.Handler.GoRestfulContainer) @@ -881,8 +881,10 @@ func NewDefaultAPIGroupInfo(group string, scheme *runtime.Scheme, parameterCodec } // getOpenAPIModels is a private method for getting the OpenAPI models -func (s *GenericAPIServer) getOpenAPIModels(apiPrefix string, apiGroupInfos ...*APIGroupInfo) (*spec.Swagger, error) { - if s.openAPIConfig == nil { +func (s *GenericAPIServer) getOpenAPIModels(apiPrefix string, apiGroupInfos ...*APIGroupInfo) (map[string]*spec.Schema, error) { + if s.openAPIV3Config == nil { + //!TODO: A future work should add a requirement that + // OpenAPIV3 config is required. May require some refactoring of tests. return nil, nil } pathsToIgnore := openapiutil.NewTrie(s.openAPIConfig.IgnorePrefixes) @@ -896,7 +898,7 @@ func (s *GenericAPIServer) getOpenAPIModels(apiPrefix string, apiGroupInfos ...* } // Build the openapi definitions for those resources and convert it to proto models - openAPISpec, err := openapibuilder2.BuildOpenAPIDefinitionsForResources(s.openAPIConfig, resourceNames...) + openAPISpec, err := openapibuilder3.BuildOpenAPIDefinitionsForResources(s.openAPIV3Config, resourceNames...) if err != nil { return nil, err }