diff --git a/pkg/endpoints/groupversion.go b/pkg/endpoints/groupversion.go index f33d4bac6..1a5a76966 100644 --- a/pkg/endpoints/groupversion.go +++ b/pkg/endpoints/groupversion.go @@ -34,6 +34,7 @@ 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. @@ -94,6 +95,9 @@ 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/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go b/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go index 130652865..b61965bd4 100644 --- a/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go +++ b/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go @@ -47,16 +47,11 @@ var fakeTypeConverter = func() fieldmanager.TypeConverter { if err != nil { panic(err) } - swag := spec.Swagger{} - if err := json.Unmarshal(data, &swag); err != nil { + spec := spec.Swagger{} + if err := json.Unmarshal(data, &spec); err != nil { panic(err) } - convertedDefs := map[string]*spec.Schema{} - for k, v := range swag.Definitions { - vCopy := v - convertedDefs[k] = &vCopy - } - typeConverter, err := fieldmanager.NewTypeConverter(convertedDefs, false) + typeConverter, err := fieldmanager.NewTypeConverter(&spec, false) if err != nil { panic(err) } diff --git a/pkg/endpoints/handlers/fieldmanager/internal/fieldmanager_test.go b/pkg/endpoints/handlers/fieldmanager/internal/fieldmanager_test.go index b51f5b5ef..0d3c432a8 100644 --- a/pkg/endpoints/handlers/fieldmanager/internal/fieldmanager_test.go +++ b/pkg/endpoints/handlers/fieldmanager/internal/fieldmanager_test.go @@ -33,18 +33,11 @@ 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) } - - for k, v := range spec.Definitions { - vCopy := v - convertedDefs[k] = &vCopy - } - - typeConverter, err := internal.NewTypeConverter(convertedDefs, false) + typeConverter, err := internal.NewTypeConverter(&spec, false) if err != nil { panic(err) } diff --git a/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go b/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go index 1ac96d7f7..a6254a4dc 100644 --- a/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go +++ b/pkg/endpoints/handlers/fieldmanager/internal/typeconverter.go @@ -42,14 +42,23 @@ type typeConverter struct { var _ TypeConverter = &typeConverter{} -func NewTypeConverter(openapiSpec map[string]*spec.Schema, preserveUnknownFields bool) (TypeConverter, error) { - typeSchema, err := schemaconv.ToSchemaFromOpenAPI(openapiSpec, preserveUnknownFields) +// 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) 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, openapiSpec) + tr := indexModels(&typeParser, pointerDefs) return &typeConverter{parser: tr}, nil } diff --git a/pkg/endpoints/handlers/fieldmanager/internal/versionconverter_test.go b/pkg/endpoints/handlers/fieldmanager/internal/versionconverter_test.go index 32f8edb34..19e4476cb 100644 --- a/pkg/endpoints/handlers/fieldmanager/internal/versionconverter_test.go +++ b/pkg/endpoints/handlers/fieldmanager/internal/versionconverter_test.go @@ -36,17 +36,11 @@ var testTypeConverter = func() TypeConverter { if err != nil { panic(err) } - swag := spec.Swagger{} - if err := json.Unmarshal(data, &swag); err != nil { + spec := spec.Swagger{} + if err := json.Unmarshal(data, &spec); err != nil { panic(err) } - - convertedDefs := map[string]*spec.Schema{} - for k, v := range swag.Definitions { - vCopy := v - convertedDefs[k] = &vCopy - } - typeConverter, err := NewTypeConverter(convertedDefs, false) + typeConverter, err := NewTypeConverter(&spec, false) if err != nil { panic(err) } diff --git a/pkg/endpoints/handlers/fieldmanager/typeconverter.go b/pkg/endpoints/handlers/fieldmanager/typeconverter.go index 8772a3076..0aa97f7e7 100644 --- a/pkg/endpoints/handlers/fieldmanager/typeconverter.go +++ b/pkg/endpoints/handlers/fieldmanager/typeconverter.go @@ -34,14 +34,9 @@ func NewDeducedTypeConverter() TypeConverter { return internal.NewDeducedTypeConverter() } -// NewTypeConverter builds a TypeConverter from a map of OpenAPIV3 schemas. -// This will automatically find the proper version of the object, and the +// NewTypeConverter builds a TypeConverter from a proto.Models. This +// will automatically find the proper version of the object, and the // corresponding schema information. -// 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) { +func NewTypeConverter(openapiSpec *spec.Swagger, preserveUnknownFields bool) (TypeConverter, error) { return internal.NewTypeConverter(openapiSpec, preserveUnknownFields) } diff --git a/pkg/endpoints/installer.go b/pkg/endpoints/installer.go index 3fd5f4371..0b8f6ef50 100644 --- a/pkg/endpoints/installer.go +++ b/pkg/endpoints/installer.go @@ -345,6 +345,13 @@ 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() @@ -673,16 +680,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag if a.group.MetaGroupVersion != nil { reqScope.MetaGroupVersion = *a.group.MetaGroupVersion } - - // 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() - } - + if a.group.OpenAPIModels != nil { reqScope.FieldManager, err = fieldmanager.NewDefaultFieldManager( a.group.TypeConverter, a.group.UnsafeConvertor, @@ -697,7 +695,6 @@ 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/pkg/server/genericapiserver.go b/pkg/server/genericapiserver.go index 5d3c175b9..1a2554048 100644 --- a/pkg/server/genericapiserver.go +++ b/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" - openapibuilder3 "k8s.io/kube-openapi/pkg/builder3" + openapibuilder2 "k8s.io/kube-openapi/pkg/builder" 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 map[string]*spec.Schema + StaticOpenAPISpec *spec.Swagger } func (a *APIGroupInfo) destroyStorage() { @@ -666,16 +666,7 @@ 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 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 - } - } +func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo, openAPIModels *spec.Swagger) error { var resourceInfos []*storageversion.ResourceInfo for _, groupVersion := range apiGroupInfo.PrioritizedVersions { if len(apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version]) == 0 { @@ -690,7 +681,16 @@ func (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *A if apiGroupInfo.OptionsExternalVersion != nil { apiGroupVersion.OptionsExternalVersion = apiGroupInfo.OptionsExternalVersion } - apiGroupVersion.TypeConverter = typeConverter + apiGroupVersion.OpenAPIModels = openAPIModels + + if openAPIModels != nil { + typeConverter, err := fieldmanager.NewTypeConverter(openAPIModels, false) + if err != nil { + return err + } + apiGroupVersion.TypeConverter = typeConverter + } + apiGroupVersion.MaxRequestBodyBytes = s.maxRequestBodyBytes discoveryAPIResources, r, err := apiGroupVersion.InstallREST(s.Handler.GoRestfulContainer) @@ -881,10 +881,8 @@ 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) (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. +func (s *GenericAPIServer) getOpenAPIModels(apiPrefix string, apiGroupInfos ...*APIGroupInfo) (*spec.Swagger, error) { + if s.openAPIConfig == nil { return nil, nil } pathsToIgnore := openapiutil.NewTrie(s.openAPIConfig.IgnorePrefixes) @@ -898,7 +896,7 @@ func (s *GenericAPIServer) getOpenAPIModels(apiPrefix string, apiGroupInfos ...* } // Build the openapi definitions for those resources and convert it to proto models - openAPISpec, err := openapibuilder3.BuildOpenAPIDefinitionsForResources(s.openAPIV3Config, resourceNames...) + openAPISpec, err := openapibuilder2.BuildOpenAPIDefinitionsForResources(s.openAPIConfig, resourceNames...) if err != nil { return nil, err }