diff --git a/pkg/generated/openapi/cmd/models-schema/main.go b/pkg/generated/openapi/cmd/models-schema/main.go index 1ea02a2036e..e3ca808c8e4 100644 --- a/pkg/generated/openapi/cmd/models-schema/main.go +++ b/pkg/generated/openapi/cmd/models-schema/main.go @@ -22,7 +22,6 @@ import ( "os" "k8s.io/kube-openapi/pkg/common" - "k8s.io/kube-openapi/pkg/util" "k8s.io/kube-openapi/pkg/validation/spec" "k8s.io/kubernetes/pkg/generated/openapi" ) @@ -38,7 +37,7 @@ func main() { func output() error { refFunc := func(name string) spec.Ref { - return spec.MustCreateRef(fmt.Sprintf("#/definitions/%s", util.ToRESTFriendlyName(name))) + return spec.MustCreateRef(fmt.Sprintf("#/definitions/%s", name)) } defs := openapi.GetOpenAPIDefinitions(refFunc) schemaDefs := make(map[string]spec.Schema, len(defs)) @@ -50,12 +49,12 @@ func output() error { // the type. if schema, ok := v.Schema.Extensions[common.ExtensionV2Schema]; ok { if v2Schema, isOpenAPISchema := schema.(spec.Schema); isOpenAPISchema { - schemaDefs[util.ToRESTFriendlyName(k)] = v2Schema + schemaDefs[k] = v2Schema continue } } - schemaDefs[util.ToRESTFriendlyName(k)] = v.Schema + schemaDefs[k] = v.Schema } data, err := json.Marshal(&spec.Swagger{ SwaggerProps: spec.SwaggerProps{ diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder.go index f99e564d767..f598be356ba 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/controller/openapi/builder/builder.go @@ -19,6 +19,7 @@ package builder import ( "fmt" "net/http" + "slices" "strings" "sync" @@ -26,6 +27,7 @@ import ( "golang.org/x/text/cases" "golang.org/x/text/language" + v1 "k8s.io/api/autoscaling/v1" apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers" apiextensionsinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" @@ -57,8 +59,8 @@ const ( objectMetaSchemaRef = "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" listMetaSchemaRef = "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta" - typeMetaType = "k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta" - objectMetaType = "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta" + typeMetaType = "io.k8s.apimachinery.pkg.apis.meta.v1.TypeMeta" + objectMetaType = "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta" definitionPrefix = "#/definitions/" v3DefinitionPrefix = "#/components/schemas/" @@ -225,7 +227,7 @@ type CRDCanonicalTypeNamer struct { // OpenAPICanonicalTypeName returns canonical type name for given CRD func (c *CRDCanonicalTypeNamer) OpenAPICanonicalTypeName() string { - return fmt.Sprintf("%s/%s.%s", c.group, c.version, c.kind) + return gvkToModelName(c.group, c.version, c.kind) } // builder contains validation schema and basic naming information for a CRD in @@ -383,7 +385,11 @@ func (b *builder) buildKubeNative(crd *apiextensionsv1.CustomResourceDefinition, // and forbid anything outside of apiVersion, kind and metadata. We have to fix kubectl to stop doing this, e.g. by // adding additionalProperties=true support to explicitly allow additional fields. // TODO: fix kubectl to understand additionalProperties=true - if schema == nil || (opts.V2 && (schema.XPreserveUnknownFields || crdPreserveUnknownFields)) { + if schema == nil { + ret = &spec.Schema{ + SchemaProps: spec.SchemaProps{Type: []string{"object"}}, + } + } else if opts.V2 && (schema.XPreserveUnknownFields || crdPreserveUnknownFields) { ret = &spec.Schema{ SchemaProps: spec.SchemaProps{Type: []string{"object"}}, } @@ -497,7 +503,7 @@ func addTypeMetaProperties(s *spec.Schema, v2 bool) { // buildListSchema builds the list kind schema for the CRD func (b *builder) buildListSchema(crd *apiextensionsv1.CustomResourceDefinition, opts Options) *spec.Schema { - name := definitionPrefix + util.ToRESTFriendlyName(fmt.Sprintf("%s/%s/%s", b.group, b.version, b.kind)) + name := definitionPrefix + gvkToModelName(b.group, b.version, b.kind) doc := fmt.Sprintf("List of %s. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md", b.plural) s := new(spec.Schema). Typed("object", ""). @@ -522,6 +528,13 @@ func (b *builder) buildListSchema(crd *apiextensionsv1.CustomResourceDefinition, return s } +func gvkToModelName(g, v, k string) string { + groupParts := strings.Split(g, ".") + slices.Reverse(groupParts) + g = strings.Join(groupParts, ".") + return fmt.Sprintf("%s.%s.%s", g, v, k) +} + // getOpenAPIConfig builds config which wires up generated definitions for kube-openapi to consume func (b *builder) getOpenAPIConfig() *common.Config { return &common.Config{ @@ -546,11 +559,11 @@ func (b *builder) getOpenAPIConfig() *common.Config { }, GetDefinitions: func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { def := utilopenapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(generatedopenapi.GetOpenAPIDefinitions)(ref) - def[fmt.Sprintf("%s/%s.%s", b.group, b.version, b.kind)] = common.OpenAPIDefinition{ + def[gvkToModelName(b.group, b.version, b.kind)] = common.OpenAPIDefinition{ Schema: *b.schema, Dependencies: []string{objectMetaType}, } - def[fmt.Sprintf("%s/%s.%s", b.group, b.version, b.listKind)] = common.OpenAPIDefinition{ + def[gvkToModelName(b.group, b.version, b.listKind)] = common.OpenAPIDefinition{ Schema: *b.listSchema, } return def @@ -580,11 +593,11 @@ func (b *builder) getOpenAPIV3Config() *common.OpenAPIV3Config { }, GetDefinitions: func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { def := utilopenapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(generatedopenapi.GetOpenAPIDefinitions)(ref) - def[fmt.Sprintf("%s/%s.%s", b.group, b.version, b.kind)] = common.OpenAPIDefinition{ + def[gvkToModelName(b.group, b.version, b.kind)] = common.OpenAPIDefinition{ Schema: *b.schema, Dependencies: []string{objectMetaType}, } - def[fmt.Sprintf("%s/%s.%s", b.group, b.version, b.listKind)] = common.OpenAPIDefinition{ + def[gvkToModelName(b.group, b.version, b.listKind)] = common.OpenAPIDefinition{ Schema: *b.listSchema, } return def diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go b/staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go index b0e22c5e30c..e2fbeabdd0d 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go @@ -29,6 +29,7 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/kube-openapi/pkg/util" ) // Scheme defines methods for serializing and deserializing API objects, a type @@ -752,6 +753,9 @@ var internalPackages = []string{"k8s.io/apimachinery/pkg/runtime/scheme.go"} // The OpenAPI definition name is the canonical name of the type, with the group and version removed. // For example, the OpenAPI definition name of Pod is `io.k8s.api.core.v1.Pod`. // +// This respects the util.OpenAPIModelNamer interface and will return the name returned by +// OpenAPIModelName() if it is defined on the type. +// // A known type that is registered as an unstructured.Unstructured type is treated as a custom resource and // which has an OpenAPI definition name of the form `.`. // For example, the OpenAPI definition name of `group: stable.example.com, version: v1, kind: Pod` is @@ -764,6 +768,12 @@ func (s *Scheme) ToOpenAPIDefinitionName(groupVersionKind schema.GroupVersionKin if err != nil { return "", err } + + // Use a namer if provided + if namer, ok := example.(util.OpenAPIModelNamer); ok { + return namer.OpenAPIModelName(), nil + } + if _, ok := example.(Unstructured); ok { if groupVersionKind.Group == "" || groupVersionKind.Kind == "" { return "", fmt.Errorf("unable to convert GroupVersionKind with empty fields to unstructured type to an OpenAPI definition name: %v", groupVersionKind) diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/openapi/openapi.go b/staging/src/k8s.io/apiserver/pkg/endpoints/openapi/openapi.go index 0da1ab05cda..75fbc6abc91 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/openapi/openapi.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/openapi/openapi.go @@ -19,7 +19,6 @@ package openapi import ( "bytes" "fmt" - "reflect" "sort" "strings" "unicode" @@ -29,6 +28,7 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/klog/v2" "k8s.io/kube-openapi/pkg/util" "k8s.io/kube-openapi/pkg/validation/spec" ) @@ -133,31 +133,28 @@ func gvkConvert(gvk schema.GroupVersionKind) v1.GroupVersionKind { } } -func typeName(t reflect.Type) string { - path := t.PkgPath() - if strings.Contains(path, "/vendor/") { - path = path[strings.Index(path, "/vendor/")+len("/vendor/"):] - } - return fmt.Sprintf("%s.%s", path, t.Name()) -} - // NewDefinitionNamer constructs a new DefinitionNamer to be used to customize OpenAPI spec. func NewDefinitionNamer(schemes ...*runtime.Scheme) *DefinitionNamer { ret := &DefinitionNamer{ typeGroupVersionKinds: map[string]groupVersionKinds{}, } for _, s := range schemes { - for gvk, rtype := range s.AllKnownTypes() { + for gvk := range s.AllKnownTypes() { newGVK := gvkConvert(gvk) exists := false - for _, existingGVK := range ret.typeGroupVersionKinds[typeName(rtype)] { + name, err := s.ToOpenAPIDefinitionName(gvk) + if err != nil { + klog.Fatalf("failed to get OpenAPI definition name for %v: %v", gvk, err) + continue + } + for _, existingGVK := range ret.typeGroupVersionKinds[name] { if newGVK == existingGVK { exists = true break } } if !exists { - ret.typeGroupVersionKinds[typeName(rtype)] = append(ret.typeGroupVersionKinds[typeName(rtype)], newGVK) + ret.typeGroupVersionKinds[name] = append(ret.typeGroupVersionKinds[name], newGVK) } } } @@ -170,9 +167,9 @@ func NewDefinitionNamer(schemes ...*runtime.Scheme) *DefinitionNamer { // GetDefinitionName returns the name and tags for a given definition func (d *DefinitionNamer) GetDefinitionName(name string) (string, spec.Extensions) { if groupVersionKinds, ok := d.typeGroupVersionKinds[name]; ok { - return util.ToRESTFriendlyName(name), spec.Extensions{ + return name, spec.Extensions{ extensionGVK: groupVersionKinds.JSON(), } } - return util.ToRESTFriendlyName(name), nil + return name, nil }