@@ -23,6 +23,7 @@ import (
2323 "fmt"
2424 "net/http"
2525 "sort"
26+ "strings"
2627 "time"
2728
2829 "github.com/emicklei/go-restful/v3"
@@ -42,6 +43,7 @@ import (
4243 "k8s.io/kube-openapi/pkg/common/restfuladapter"
4344 "k8s.io/kube-openapi/pkg/handler3"
4445 "k8s.io/kube-openapi/pkg/spec3"
46+ validationspec "k8s.io/kube-openapi/pkg/validation/spec"
4547 "k8s.io/utils/keymutex"
4648 "k8s.io/utils/lru"
4749
@@ -305,12 +307,20 @@ func apiResourceSchemaToSpec(apiResourceSchema *apisv1alpha1.APIResourceSchema)
305307 },
306308 }
307309
308- for _ , ver := range versions {
310+ if isCRDResource (crd ) {
311+ ensureCompleteSchemaGeneration (crd )
312+ }
313+
314+ for i , ver := range versions {
309315 spec , err := builder .BuildOpenAPIV3 (crd , ver .Name , builder.Options {V2 : false })
310316 if err != nil {
311317 return nil , err
312318 }
313319
320+ if isCRDResource (crd ) {
321+ patchCRDSchemaForOpenAPI (spec , & apiResourceSchema .Spec .Versions [i ])
322+ }
323+
314324 gv := schema.GroupVersion {Group : crd .Spec .Group , Version : ver .Name }
315325 gvPath := groupVersionToOpenAPIV3Path (gv )
316326 specs [gvPath ] = append (specs [gvPath ], spec )
@@ -319,6 +329,112 @@ func apiResourceSchemaToSpec(apiResourceSchema *apisv1alpha1.APIResourceSchema)
319329 return specs , nil
320330}
321331
332+ // This is used to trigger special handling for CRD's recursive schema structure, ensuring
333+ // the OpenAPI builder generates a complete schema instead of a shallow one (due to
334+ // XPreserveUnknownFields behavior) and allowing validation of OpenAPI keywords.
335+ func ensureCompleteSchemaGeneration (crd * apiextensionsv1.CustomResourceDefinition ) {
336+ falseVal := false
337+ for i := range crd .Spec .Versions {
338+ if crd .Spec .Versions [i ].Schema != nil && crd .Spec .Versions [i ].Schema .OpenAPIV3Schema != nil {
339+ crd .Spec .Versions [i ].Schema .OpenAPIV3Schema .XPreserveUnknownFields = & falseVal
340+ }
341+ }
342+ }
343+
344+ func patchCRDSchemaForOpenAPI (spec * spec3.OpenAPI , ver * apisv1alpha1.APIResourceVersion ) {
345+ if spec .Components == nil || spec .Components .Schemas == nil {
346+ return
347+ }
348+
349+ for name , s := range spec .Components .Schemas {
350+ if ! strings .HasSuffix (name , "CustomResourceDefinition" ) {
351+ continue
352+ }
353+ if _ , hasSpec := s .Properties ["spec" ]; hasSpec {
354+ continue
355+ }
356+
357+ sourceSchema , err := ver .GetSchema ()
358+ if err != nil || sourceSchema == nil {
359+ continue
360+ }
361+
362+ patchedSchema , err := convertToOpenAPISchema (sourceSchema )
363+ if err != nil {
364+ continue
365+ }
366+
367+ patchedSchema .VendorExtensible .Extensions = s .VendorExtensible .Extensions
368+ enhanceOpenAPIV3SchemaProperty (& patchedSchema )
369+ spec .Components .Schemas [name ] = & patchedSchema
370+ }
371+ }
372+
373+ func convertToOpenAPISchema (source * apiextensionsv1.JSONSchemaProps ) (validationspec.Schema , error ) {
374+ bs , err := json .Marshal (source )
375+ if err != nil {
376+ return validationspec.Schema {}, err
377+ }
378+
379+ var result validationspec.Schema
380+ if err := json .Unmarshal (bs , & result ); err != nil {
381+ return validationspec.Schema {}, err
382+ }
383+
384+ return result , nil
385+ }
386+
387+ func enhanceOpenAPIV3SchemaProperty (schema * validationspec.Schema ) {
388+ specProp , ok := schema .Properties ["spec" ]
389+ if ! ok {
390+ return
391+ }
392+
393+ versionsProp , ok := specProp .Properties ["versions" ]
394+ if ! ok {
395+ return
396+ }
397+
398+ if versionsProp .Items == nil || versionsProp .Items .Schema == nil {
399+ return
400+ }
401+
402+ itemSchema := versionsProp .Items .Schema
403+ schemaProp , ok := itemSchema .Properties ["schema" ]
404+ if ! ok {
405+ return
406+ }
407+
408+ oaProp , ok := schemaProp .Properties ["openAPIV3Schema" ]
409+ if ! ok {
410+ return
411+ }
412+
413+ if len (oaProp .Properties ) > 0 {
414+ return
415+ }
416+ if oaProp .Properties == nil {
417+ oaProp .Properties = make (map [string ]validationspec.Schema )
418+ }
419+
420+ oaProp .Properties ["type" ] = validationspec.Schema {
421+ SchemaProps : validationspec.SchemaProps {Type : []string {"string" }},
422+ }
423+ oaProp .Properties ["properties" ] = validationspec.Schema {
424+ SchemaProps : validationspec.SchemaProps {
425+ Type : []string {"object" },
426+ AdditionalProperties : & validationspec.SchemaOrBool {Allows : true },
427+ },
428+ }
429+
430+ oaProp .AdditionalProperties = & validationspec.SchemaOrBool {Allows : true }
431+ schemaProp .Properties ["openAPIV3Schema" ] = oaProp
432+ itemSchema .Properties ["schema" ] = schemaProp
433+ versionsProp .Items .Schema = itemSchema
434+ specProp .Properties ["versions" ] = versionsProp
435+ schema .Properties ["spec" ] = specProp
436+ }
437+
322438func groupVersionToOpenAPIV3Path (gv schema.GroupVersion ) string {
323439 if gv .Group == "" {
324440 return "api/" + gv .Version
0 commit comments