2019-08-30 18:33:25 +00:00
/ *
Copyright 2019 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2019-09-27 21:51:53 +00:00
package builder
2019-08-30 18:33:25 +00:00
import (
"fmt"
"net/http"
"strings"
"sync"
2019-09-27 21:51:53 +00:00
"github.com/emicklei/go-restful"
2019-08-30 18:33:25 +00:00
"github.com/go-openapi/spec"
v1 "k8s.io/api/autoscaling/v1"
2020-03-26 21:07:15 +00:00
apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers"
apiextensionsinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2019-09-27 21:51:53 +00:00
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation"
2019-08-30 18:33:25 +00:00
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
2019-09-27 21:51:53 +00:00
openapiv2 "k8s.io/apiextensions-apiserver/pkg/controller/openapi/v2"
2019-09-19 23:17:14 +00:00
generatedopenapi "k8s.io/apiextensions-apiserver/pkg/generated/openapi"
2019-08-30 18:33:25 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
2019-09-19 23:17:14 +00:00
"k8s.io/apimachinery/pkg/types"
2019-08-30 18:33:25 +00:00
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/endpoints"
"k8s.io/apiserver/pkg/endpoints/openapi"
2019-09-19 23:17:14 +00:00
"k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
2019-08-30 18:33:25 +00:00
openapibuilder "k8s.io/kube-openapi/pkg/builder"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/util"
)
const (
// Reference and Go types for built-in metadata
objectMetaSchemaRef = "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
2020-08-10 17:43:49 +00:00
listMetaSchemaRef = "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ListMeta"
2019-08-30 18:33:25 +00:00
listMetaType = "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"
typeMetaType = "k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta"
definitionPrefix = "#/definitions/"
)
var (
2020-08-10 17:43:49 +00:00
swaggerPartialObjectMetadataDescriptions = metav1beta1 . PartialObjectMetadata { } . SwaggerDoc ( )
swaggerPartialObjectMetadataListDescriptions = metav1beta1 . PartialObjectMetadataList { } . SwaggerDoc ( )
2019-09-27 21:51:53 +00:00
nameToken = "{name}"
namespaceToken = "{namespace}"
2019-08-30 18:33:25 +00:00
)
var definitions map [ string ] common . OpenAPIDefinition
var buildDefinitions sync . Once
var namer * openapi . DefinitionNamer
2019-09-27 21:51:53 +00:00
// Options contains builder options.
type Options struct {
// Convert to OpenAPI v2.
V2 bool
// Strip value validation.
StripValueValidation bool
2019-12-12 01:27:03 +00:00
// Strip nullable.
StripNullable bool
2019-09-27 21:51:53 +00:00
// AllowNonStructural indicates swagger should be built for a schema that fits into the structural type but does not meet all structural invariants
AllowNonStructural bool
}
2019-08-30 18:33:25 +00:00
// BuildSwagger builds swagger for the given crd in the given version
2020-03-26 21:07:15 +00:00
func BuildSwagger ( crd * apiextensionsv1 . CustomResourceDefinition , version string , opts Options ) ( * spec . Swagger , error ) {
2019-08-30 18:33:25 +00:00
var schema * structuralschema . Structural
2020-03-26 21:07:15 +00:00
s , err := apiextensionshelpers . GetSchemaForVersion ( crd , version )
2019-08-30 18:33:25 +00:00
if err != nil {
return nil , err
}
2019-09-27 21:51:53 +00:00
2019-08-30 18:33:25 +00:00
if s != nil && s . OpenAPIV3Schema != nil {
2020-03-26 21:07:15 +00:00
internalCRDSchema := & apiextensionsinternal . CustomResourceValidation { }
if err := apiextensionsv1 . Convert_v1_CustomResourceValidation_To_apiextensions_CustomResourceValidation ( s , internalCRDSchema , nil ) ; err != nil {
return nil , fmt . Errorf ( "failed converting CRD validation to internal version: %v" , err )
}
if ! validation . SchemaHasInvalidTypes ( internalCRDSchema . OpenAPIV3Schema ) {
if ss , err := structuralschema . NewStructural ( internalCRDSchema . OpenAPIV3Schema ) ; err == nil {
2019-09-27 21:51:53 +00:00
// skip non-structural schemas unless explicitly asked to produce swagger from them
if opts . AllowNonStructural || len ( structuralschema . ValidateStructural ( nil , ss ) ) == 0 {
schema = ss
2021-03-18 22:40:29 +00:00
// This adds ValueValidation fields (anyOf, allOf) which may be stripped below if opts.StripValueValidation is true
schema = schema . Unfold ( )
2019-09-27 21:51:53 +00:00
if opts . StripValueValidation {
schema = schema . StripValueValidations ( )
}
2019-12-12 01:27:03 +00:00
if opts . StripNullable {
schema = schema . StripNullable ( )
}
2019-09-27 21:51:53 +00:00
}
}
2019-08-30 18:33:25 +00:00
}
}
// TODO(roycaihw): remove the WebService templating below. The following logic
// comes from function registerResourceHandlers() in k8s.io/apiserver.
// Alternatives are either (ideally) refactoring registerResourceHandlers() to
// reuse the code, or faking an APIInstaller for CR to feed to registerResourceHandlers().
2019-09-27 21:51:53 +00:00
b := newBuilder ( crd , version , schema , opts . V2 )
2019-08-30 18:33:25 +00:00
// Sample response types for building web service
sample := & CRDCanonicalTypeNamer {
group : b . group ,
version : b . version ,
kind : b . kind ,
}
sampleList := & CRDCanonicalTypeNamer {
group : b . group ,
version : b . version ,
kind : b . listKind ,
}
status := & metav1 . Status { }
patch := & metav1 . Patch { }
scale := & v1 . Scale { }
routes := make ( [ ] * restful . RouteBuilder , 0 )
root := fmt . Sprintf ( "/apis/%s/%s/%s" , b . group , b . version , b . plural )
2019-09-27 21:51:53 +00:00
2019-08-30 18:33:25 +00:00
if b . namespaced {
2019-09-27 21:51:53 +00:00
routes = append ( routes , b . buildRoute ( root , "" , "GET" , "list" , "list" , sampleList ) . Operation ( "list" + b . kind + "ForAllNamespaces" ) )
2019-08-30 18:33:25 +00:00
root = fmt . Sprintf ( "/apis/%s/%s/namespaces/{namespace}/%s" , b . group , b . version , b . plural )
}
2019-09-27 21:51:53 +00:00
routes = append ( routes , b . buildRoute ( root , "" , "GET" , "list" , "list" , sampleList ) )
routes = append ( routes , b . buildRoute ( root , "" , "POST" , "post" , "create" , sample ) . Reads ( sample ) )
routes = append ( routes , b . buildRoute ( root , "" , "DELETE" , "deletecollection" , "deletecollection" , status ) )
2019-08-30 18:33:25 +00:00
2019-09-27 21:51:53 +00:00
routes = append ( routes , b . buildRoute ( root , "/{name}" , "GET" , "get" , "read" , sample ) )
routes = append ( routes , b . buildRoute ( root , "/{name}" , "PUT" , "put" , "replace" , sample ) . Reads ( sample ) )
routes = append ( routes , b . buildRoute ( root , "/{name}" , "DELETE" , "delete" , "delete" , status ) )
routes = append ( routes , b . buildRoute ( root , "/{name}" , "PATCH" , "patch" , "patch" , sample ) . Reads ( patch ) )
2019-08-30 18:33:25 +00:00
2020-03-26 21:07:15 +00:00
subresources , err := apiextensionshelpers . GetSubresourcesForVersion ( crd , version )
2019-08-30 18:33:25 +00:00
if err != nil {
return nil , err
}
if subresources != nil && subresources . Status != nil {
2019-09-27 21:51:53 +00:00
routes = append ( routes , b . buildRoute ( root , "/{name}/status" , "GET" , "get" , "read" , sample ) )
routes = append ( routes , b . buildRoute ( root , "/{name}/status" , "PUT" , "put" , "replace" , sample ) . Reads ( sample ) )
routes = append ( routes , b . buildRoute ( root , "/{name}/status" , "PATCH" , "patch" , "patch" , sample ) . Reads ( patch ) )
2019-08-30 18:33:25 +00:00
}
if subresources != nil && subresources . Scale != nil {
2019-09-27 21:51:53 +00:00
routes = append ( routes , b . buildRoute ( root , "/{name}/scale" , "GET" , "get" , "read" , scale ) )
routes = append ( routes , b . buildRoute ( root , "/{name}/scale" , "PUT" , "put" , "replace" , scale ) . Reads ( scale ) )
routes = append ( routes , b . buildRoute ( root , "/{name}/scale" , "PATCH" , "patch" , "patch" , scale ) . Reads ( patch ) )
2019-08-30 18:33:25 +00:00
}
for _ , route := range routes {
b . ws . Route ( route )
}
openAPISpec , err := openapibuilder . BuildOpenAPISpec ( [ ] * restful . WebService { b . ws } , b . getOpenAPIConfig ( ) )
if err != nil {
return nil , err
}
return openAPISpec , nil
}
// Implements CanonicalTypeNamer
var _ = util . OpenAPICanonicalTypeNamer ( & CRDCanonicalTypeNamer { } )
// CRDCanonicalTypeNamer implements CanonicalTypeNamer interface for CRDs to
// seed kube-openapi canonical type name without Go types
type CRDCanonicalTypeNamer struct {
group string
version string
kind string
}
// 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 )
}
// builder contains validation schema and basic naming information for a CRD in
// one version. The builder works to build a WebService that kube-openapi can
// consume.
type builder struct {
schema * spec . Schema
listSchema * spec . Schema
ws * restful . WebService
group string
version string
kind string
listKind string
plural string
namespaced bool
}
// subresource is a handy method to get subresource name. Valid inputs are:
// input output
// "" ""
// "/" ""
// "/{name}" ""
// "/{name}/scale" "scale"
// "/{name}/scale/foo" invalid input
func subresource ( path string ) string {
parts := strings . Split ( path , "/" )
if len ( parts ) <= 2 {
return ""
}
if len ( parts ) == 3 {
return parts [ 2 ]
}
// panic to alert on programming error
panic ( "failed to parse subresource; invalid path" )
}
2019-09-27 21:51:53 +00:00
func ( b * builder ) descriptionFor ( path , operationVerb string ) string {
2019-08-30 18:33:25 +00:00
var article string
2019-09-27 21:51:53 +00:00
switch operationVerb {
2019-08-30 18:33:25 +00:00
case "list" :
article = " objects of kind "
case "read" , "replace" :
article = " the specified "
case "patch" :
article = " the specified "
case "create" , "delete" :
article = endpoints . GetArticleForNoun ( b . kind , " " )
default :
article = ""
}
var description string
sub := subresource ( path )
if len ( sub ) > 0 {
sub = " " + sub + " of"
}
2019-09-27 21:51:53 +00:00
switch operationVerb {
2019-08-30 18:33:25 +00:00
case "patch" :
description = "partially update" + sub + article + b . kind
case "deletecollection" :
// to match the text for built-in APIs
if len ( sub ) > 0 {
sub = sub + " a"
}
description = "delete collection of" + sub + " " + b . kind
default :
2019-09-27 21:51:53 +00:00
description = operationVerb + sub + article + b . kind
2019-08-30 18:33:25 +00:00
}
return description
}
// buildRoute returns a RouteBuilder for WebService to consume and builds path in swagger
// action can be one of: GET, PUT, PATCH, POST, DELETE;
// verb can be one of: list, read, replace, patch, create, delete, deletecollection;
// sample is the sample Go type for response type.
2019-09-27 21:51:53 +00:00
func ( b * builder ) buildRoute ( root , path , httpMethod , actionVerb , operationVerb string , sample interface { } ) * restful . RouteBuilder {
2019-08-30 18:33:25 +00:00
var namespaced string
if b . namespaced {
namespaced = "Namespaced"
}
2019-09-27 21:51:53 +00:00
route := b . ws . Method ( httpMethod ) .
2019-08-30 18:33:25 +00:00
Path ( root + path ) .
To ( func ( req * restful . Request , res * restful . Response ) { } ) .
2019-09-27 21:51:53 +00:00
Doc ( b . descriptionFor ( path , operationVerb ) ) .
2019-08-30 18:33:25 +00:00
Param ( b . ws . QueryParameter ( "pretty" , "If 'true', then the output is pretty printed." ) ) .
2019-09-27 21:51:53 +00:00
Operation ( operationVerb + namespaced + b . kind + strings . Title ( subresource ( path ) ) ) .
2019-08-30 18:33:25 +00:00
Metadata ( endpoints . ROUTE_META_GVK , metav1 . GroupVersionKind {
Group : b . group ,
Version : b . version ,
Kind : b . kind ,
} ) .
2019-09-27 21:51:53 +00:00
Metadata ( endpoints . ROUTE_META_ACTION , actionVerb ) .
2019-08-30 18:33:25 +00:00
Produces ( "application/json" , "application/yaml" ) .
Returns ( http . StatusOK , "OK" , sample ) .
Writes ( sample )
2019-09-27 21:51:53 +00:00
if strings . Contains ( root , namespaceToken ) || strings . Contains ( path , namespaceToken ) {
route . Param ( b . ws . PathParameter ( "namespace" , "object name and auth scope, such as for teams and projects" ) . DataType ( "string" ) )
}
if strings . Contains ( root , nameToken ) || strings . Contains ( path , nameToken ) {
route . Param ( b . ws . PathParameter ( "name" , "name of the " + b . kind ) . DataType ( "string" ) )
}
2019-08-30 18:33:25 +00:00
// Build consume media types
2019-09-27 21:51:53 +00:00
if httpMethod == "PATCH" {
2019-09-19 23:17:14 +00:00
supportedTypes := [ ] string {
string ( types . JSONPatchType ) ,
string ( types . MergePatchType ) ,
}
if utilfeature . DefaultFeatureGate . Enabled ( features . ServerSideApply ) {
supportedTypes = append ( supportedTypes , string ( types . ApplyPatchType ) )
}
route . Consumes ( supportedTypes ... )
2019-08-30 18:33:25 +00:00
} else {
2019-09-19 23:17:14 +00:00
route . Consumes ( runtime . ContentTypeJSON , runtime . ContentTypeYAML )
2019-08-30 18:33:25 +00:00
}
// Build option parameters
2019-09-27 21:51:53 +00:00
switch actionVerb {
2019-08-30 18:33:25 +00:00
case "get" :
// TODO: CRD support for export is still under consideration
endpoints . AddObjectParams ( b . ws , route , & metav1 . GetOptions { } )
case "list" , "deletecollection" :
endpoints . AddObjectParams ( b . ws , route , & metav1 . ListOptions { } )
2019-09-27 21:51:53 +00:00
case "put" , "patch" :
2019-08-30 18:33:25 +00:00
// TODO: PatchOption added in feature branch but not in master yet
endpoints . AddObjectParams ( b . ws , route , & metav1 . UpdateOptions { } )
2019-09-27 21:51:53 +00:00
case "post" :
2019-08-30 18:33:25 +00:00
endpoints . AddObjectParams ( b . ws , route , & metav1 . CreateOptions { } )
case "delete" :
endpoints . AddObjectParams ( b . ws , route , & metav1 . DeleteOptions { } )
route . Reads ( & metav1 . DeleteOptions { } ) . ParameterNamed ( "body" ) . Required ( false )
}
// Build responses
2019-09-27 21:51:53 +00:00
switch actionVerb {
case "post" :
2019-08-30 18:33:25 +00:00
route . Returns ( http . StatusAccepted , "Accepted" , sample )
route . Returns ( http . StatusCreated , "Created" , sample )
case "delete" :
route . Returns ( http . StatusAccepted , "Accepted" , sample )
2019-09-27 21:51:53 +00:00
case "put" :
2019-08-30 18:33:25 +00:00
route . Returns ( http . StatusCreated , "Created" , sample )
}
return route
}
// buildKubeNative builds input schema with Kubernetes' native object meta, type meta and
// extensions
2019-09-19 23:17:14 +00:00
func ( b * builder ) buildKubeNative ( schema * structuralschema . Structural , v2 bool , crdPreserveUnknownFields bool ) ( ret * spec . Schema ) {
2019-08-30 18:33:25 +00:00
// only add properties if we have a schema. Otherwise, kubectl would (wrongly) assume additionalProperties=false
// 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
2019-09-19 23:17:14 +00:00
if schema == nil || ( v2 && ( schema . XPreserveUnknownFields || crdPreserveUnknownFields ) ) {
2019-08-30 18:33:25 +00:00
ret = & spec . Schema {
SchemaProps : spec . SchemaProps { Type : [ ] string { "object" } } ,
}
// no, we cannot add more properties here, not even TypeMeta/ObjectMeta because kubectl will complain about
// unknown fields for anything else.
} else {
if v2 {
2019-09-27 21:51:53 +00:00
schema = openapiv2 . ToStructuralOpenAPIV2 ( schema )
2019-08-30 18:33:25 +00:00
}
ret = schema . ToGoOpenAPI ( )
ret . SetProperty ( "metadata" , * spec . RefSchema ( objectMetaSchemaRef ) .
WithDescription ( swaggerPartialObjectMetadataDescriptions [ "metadata" ] ) )
addTypeMetaProperties ( ret )
addEmbeddedProperties ( ret , v2 )
}
ret . AddExtension ( endpoints . ROUTE_META_GVK , [ ] interface { } {
map [ string ] interface { } {
"group" : b . group ,
"version" : b . version ,
"kind" : b . kind ,
} ,
} )
return ret
}
func addEmbeddedProperties ( s * spec . Schema , v2 bool ) {
if s == nil {
return
}
for k := range s . Properties {
v := s . Properties [ k ]
addEmbeddedProperties ( & v , v2 )
s . Properties [ k ] = v
}
if s . Items != nil {
addEmbeddedProperties ( s . Items . Schema , v2 )
}
if s . AdditionalProperties != nil {
addEmbeddedProperties ( s . AdditionalProperties . Schema , v2 )
}
if isTrue , ok := s . VendorExtensible . Extensions . GetBool ( "x-kubernetes-preserve-unknown-fields" ) ; ok && isTrue && v2 {
// don't add metadata properties if we're publishing to openapi v2 and are allowing unknown fields.
// adding these metadata properties makes kubectl refuse to validate unknown fields.
return
}
if isTrue , ok := s . VendorExtensible . Extensions . GetBool ( "x-kubernetes-embedded-resource" ) ; ok && isTrue {
s . SetProperty ( "apiVersion" , withDescription ( getDefinition ( typeMetaType ) . SchemaProps . Properties [ "apiVersion" ] ,
2019-09-27 21:51:53 +00:00
"apiVersion defines the versioned schema of this representation of an object. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" ,
2019-08-30 18:33:25 +00:00
) )
s . SetProperty ( "kind" , withDescription ( getDefinition ( typeMetaType ) . SchemaProps . Properties [ "kind" ] ,
2019-09-27 21:51:53 +00:00
"kind is a string value representing the type of this object. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" ,
2019-08-30 18:33:25 +00:00
) )
s . SetProperty ( "metadata" , * spec . RefSchema ( objectMetaSchemaRef ) . WithDescription ( swaggerPartialObjectMetadataDescriptions [ "metadata" ] ) )
req := sets . NewString ( s . Required ... )
if ! req . Has ( "kind" ) {
s . Required = append ( s . Required , "kind" )
}
if ! req . Has ( "apiVersion" ) {
s . Required = append ( s . Required , "apiVersion" )
}
}
}
// getDefinition gets definition for given Kubernetes type. This function is extracted from
// kube-openapi builder logic
func getDefinition ( name string ) spec . Schema {
buildDefinitions . Do ( buildDefinitionsFunc )
return definitions [ name ] . Schema
}
func withDescription ( s spec . Schema , desc string ) spec . Schema {
return * s . WithDescription ( desc )
}
func buildDefinitionsFunc ( ) {
namer = openapi . NewDefinitionNamer ( runtime . NewScheme ( ) )
definitions = generatedopenapi . GetOpenAPIDefinitions ( func ( name string ) spec . Ref {
defName , _ := namer . GetDefinitionName ( name )
return spec . MustCreateRef ( definitionPrefix + common . EscapeJsonPointer ( defName ) )
} )
}
// addTypeMetaProperties adds Kubernetes-specific type meta properties to input schema:
// apiVersion and kind
func addTypeMetaProperties ( s * spec . Schema ) {
s . SetProperty ( "apiVersion" , getDefinition ( typeMetaType ) . SchemaProps . Properties [ "apiVersion" ] )
s . SetProperty ( "kind" , getDefinition ( typeMetaType ) . SchemaProps . Properties [ "kind" ] )
}
// buildListSchema builds the list kind schema for the CRD
func ( b * builder ) buildListSchema ( ) * spec . Schema {
name := definitionPrefix + util . ToRESTFriendlyName ( fmt . Sprintf ( "%s/%s/%s" , b . group , b . version , b . kind ) )
2019-09-27 21:51:53 +00:00
doc := fmt . Sprintf ( "List of %s. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md" , b . plural )
2019-08-30 18:33:25 +00:00
s := new ( spec . Schema ) . WithDescription ( fmt . Sprintf ( "%s is a list of %s" , b . listKind , b . kind ) ) .
WithRequired ( "items" ) .
SetProperty ( "items" , * spec . ArrayProperty ( spec . RefSchema ( name ) ) . WithDescription ( doc ) ) .
2020-08-10 17:43:49 +00:00
SetProperty ( "metadata" , * spec . RefSchema ( listMetaSchemaRef ) . WithDescription ( swaggerPartialObjectMetadataListDescriptions [ "metadata" ] ) )
2019-08-30 18:33:25 +00:00
addTypeMetaProperties ( s )
s . AddExtension ( endpoints . ROUTE_META_GVK , [ ] map [ string ] string {
{
"group" : b . group ,
"version" : b . version ,
"kind" : b . listKind ,
} ,
} )
return s
}
// getOpenAPIConfig builds config which wires up generated definitions for kube-openapi to consume
func ( b * builder ) getOpenAPIConfig ( ) * common . Config {
return & common . Config {
ProtocolList : [ ] string { "https" } ,
Info : & spec . Info {
InfoProps : spec . InfoProps {
Title : "Kubernetes CRD Swagger" ,
Version : "v0.1.0" ,
} ,
} ,
CommonResponses : map [ int ] spec . Response {
401 : {
ResponseProps : spec . ResponseProps {
Description : "Unauthorized" ,
} ,
} ,
} ,
GetOperationIDAndTags : openapi . GetOperationIDAndTags ,
GetDefinitionName : func ( name string ) ( string , spec . Extensions ) {
buildDefinitions . Do ( buildDefinitionsFunc )
return namer . GetDefinitionName ( name )
} ,
GetDefinitions : func ( ref common . ReferenceCallback ) map [ string ] common . OpenAPIDefinition {
def := generatedopenapi . GetOpenAPIDefinitions ( ref )
def [ fmt . Sprintf ( "%s/%s.%s" , b . group , b . version , b . kind ) ] = common . OpenAPIDefinition {
Schema : * b . schema ,
}
def [ fmt . Sprintf ( "%s/%s.%s" , b . group , b . version , b . listKind ) ] = common . OpenAPIDefinition {
Schema : * b . listSchema ,
}
return def
} ,
}
}
2020-03-26 21:07:15 +00:00
func newBuilder ( crd * apiextensionsv1 . CustomResourceDefinition , version string , schema * structuralschema . Structural , v2 bool ) * builder {
2019-08-30 18:33:25 +00:00
b := & builder {
schema : & spec . Schema {
SchemaProps : spec . SchemaProps { Type : [ ] string { "object" } } ,
} ,
listSchema : & spec . Schema { } ,
ws : & restful . WebService { } ,
group : crd . Spec . Group ,
version : version ,
kind : crd . Spec . Names . Kind ,
listKind : crd . Spec . Names . ListKind ,
plural : crd . Spec . Names . Plural ,
}
2020-03-26 21:07:15 +00:00
if crd . Spec . Scope == apiextensionsv1 . NamespaceScoped {
2019-08-30 18:33:25 +00:00
b . namespaced = true
}
// Pre-build schema with Kubernetes native properties
2020-03-26 21:07:15 +00:00
b . schema = b . buildKubeNative ( schema , v2 , crd . Spec . PreserveUnknownFields )
2019-08-30 18:33:25 +00:00
b . listSchema = b . buildListSchema ( )
return b
}