1
16
17 package builder
18
19 import (
20 "encoding/json"
21 "fmt"
22 "net/http"
23 "strings"
24
25 restful "github.com/emicklei/go-restful/v3"
26
27 "k8s.io/kube-openapi/pkg/common"
28 "k8s.io/kube-openapi/pkg/common/restfuladapter"
29 "k8s.io/kube-openapi/pkg/util"
30 "k8s.io/kube-openapi/pkg/validation/spec"
31 )
32
33 const (
34 OpenAPIVersion = "2.0"
35 )
36
37 type openAPI struct {
38 config *common.Config
39 swagger *spec.Swagger
40 protocolList []string
41 definitions map[string]common.OpenAPIDefinition
42 }
43
44
45
46
47 func BuildOpenAPISpec(routeContainers []*restful.WebService, config *common.Config) (*spec.Swagger, error) {
48 return BuildOpenAPISpecFromRoutes(restfuladapter.AdaptWebServices(routeContainers), config)
49 }
50
51
52 func BuildOpenAPISpecFromRoutes(routeContainers []common.RouteContainer, config *common.Config) (*spec.Swagger, error) {
53 o := newOpenAPI(config)
54 err := o.buildPaths(routeContainers)
55 if err != nil {
56 return nil, err
57 }
58 return o.finalizeSwagger()
59 }
60
61
62 func BuildOpenAPIDefinitionsForResource(model interface{}, config *common.Config) (*spec.Definitions, error) {
63 o := newOpenAPI(config)
64
65
66 _, err := o.toSchema(util.GetCanonicalTypeName(model))
67 if err != nil {
68 return nil, err
69 }
70 swagger, err := o.finalizeSwagger()
71 if err != nil {
72 return nil, err
73 }
74 return &swagger.Definitions, nil
75 }
76
77
78
79 func BuildOpenAPIDefinitionsForResources(config *common.Config, names ...string) (*spec.Swagger, error) {
80 o := newOpenAPI(config)
81
82
83 for _, name := range names {
84 _, err := o.toSchema(name)
85 if err != nil {
86 return nil, err
87 }
88 }
89 return o.finalizeSwagger()
90 }
91
92
93 func newOpenAPI(config *common.Config) openAPI {
94 o := openAPI{
95 config: config,
96 swagger: &spec.Swagger{
97 SwaggerProps: spec.SwaggerProps{
98 Swagger: OpenAPIVersion,
99 Definitions: spec.Definitions{},
100 Responses: config.ResponseDefinitions,
101 Paths: &spec.Paths{Paths: map[string]spec.PathItem{}},
102 Info: config.Info,
103 },
104 },
105 }
106
107 if o.config.GetOperationIDAndTagsFromRoute == nil {
108
109 if o.config.GetOperationIDAndTags != nil {
110 o.config.GetOperationIDAndTagsFromRoute = func(r common.Route) (string, []string, error) {
111 restfulRouteAdapter, ok := r.(*restfuladapter.RouteAdapter)
112 if !ok {
113 return "", nil, fmt.Errorf("config.GetOperationIDAndTags specified but route is not a restful v1 Route")
114 }
115
116 return o.config.GetOperationIDAndTags(restfulRouteAdapter.Route)
117 }
118 } else {
119 o.config.GetOperationIDAndTagsFromRoute = func(r common.Route) (string, []string, error) {
120 return r.OperationName(), nil, nil
121 }
122 }
123 }
124
125 if o.config.GetDefinitionName == nil {
126 o.config.GetDefinitionName = func(name string) (string, spec.Extensions) {
127 return name[strings.LastIndex(name, "/")+1:], nil
128 }
129 }
130 o.definitions = o.config.GetDefinitions(func(name string) spec.Ref {
131 defName, _ := o.config.GetDefinitionName(name)
132 return spec.MustCreateRef("#/definitions/" + common.EscapeJsonPointer(defName))
133 })
134 if o.config.CommonResponses == nil {
135 o.config.CommonResponses = map[int]spec.Response{}
136 }
137 return o
138 }
139
140
141
142 func (o *openAPI) finalizeSwagger() (*spec.Swagger, error) {
143 if o.config.SecurityDefinitions != nil {
144 o.swagger.SecurityDefinitions = *o.config.SecurityDefinitions
145 o.swagger.Security = o.config.DefaultSecurity
146 }
147 if o.config.PostProcessSpec != nil {
148 var err error
149 o.swagger, err = o.config.PostProcessSpec(o.swagger)
150 if err != nil {
151 return nil, err
152 }
153 }
154
155 return deduplicateParameters(o.swagger)
156 }
157
158 func (o *openAPI) buildDefinitionRecursively(name string) error {
159 uniqueName, extensions := o.config.GetDefinitionName(name)
160 if _, ok := o.swagger.Definitions[uniqueName]; ok {
161 return nil
162 }
163 if item, ok := o.definitions[name]; ok {
164 schema := spec.Schema{
165 VendorExtensible: item.Schema.VendorExtensible,
166 SchemaProps: item.Schema.SchemaProps,
167 SwaggerSchemaProps: item.Schema.SwaggerSchemaProps,
168 }
169 if extensions != nil {
170 if schema.Extensions == nil {
171 schema.Extensions = spec.Extensions{}
172 }
173 for k, v := range extensions {
174 schema.Extensions[k] = v
175 }
176 }
177 if v, ok := item.Schema.Extensions[common.ExtensionV2Schema]; ok {
178 if v2Schema, isOpenAPISchema := v.(spec.Schema); isOpenAPISchema {
179 schema = v2Schema
180 }
181 }
182 o.swagger.Definitions[uniqueName] = schema
183 for _, v := range item.Dependencies {
184 if err := o.buildDefinitionRecursively(v); err != nil {
185 return err
186 }
187 }
188 } else {
189 return fmt.Errorf("cannot find model definition for %v. If you added a new type, you may need to add +k8s:openapi-gen=true to the package or type and run code-gen again", name)
190 }
191 return nil
192 }
193
194
195
196
197 func (o *openAPI) buildDefinitionForType(name string) (string, error) {
198 if err := o.buildDefinitionRecursively(name); err != nil {
199 return "", err
200 }
201 defName, _ := o.config.GetDefinitionName(name)
202 return "#/definitions/" + common.EscapeJsonPointer(defName), nil
203 }
204
205
206 func (o *openAPI) buildPaths(routeContainers []common.RouteContainer) error {
207 pathsToIgnore := util.NewTrie(o.config.IgnorePrefixes)
208 duplicateOpId := make(map[string]string)
209 for _, w := range routeContainers {
210 rootPath := w.RootPath()
211 if pathsToIgnore.HasPrefix(rootPath) {
212 continue
213 }
214 commonParams, err := o.buildParameters(w.PathParameters())
215 if err != nil {
216 return err
217 }
218 for path, routes := range groupRoutesByPath(w.Routes()) {
219
220
221 if strings.HasSuffix(path, ":*}") {
222 path = path[:len(path)-3] + "}"
223 }
224 if pathsToIgnore.HasPrefix(path) {
225 continue
226 }
227
228 inPathCommonParamsMap, err := o.findCommonParameters(routes)
229 if err != nil {
230 return err
231 }
232 pathItem, exists := o.swagger.Paths.Paths[path]
233 if exists {
234 return fmt.Errorf("duplicate webservice route has been found for path: %v", path)
235 }
236 pathItem = spec.PathItem{
237 PathItemProps: spec.PathItemProps{
238 Parameters: make([]spec.Parameter, 0),
239 },
240 }
241
242 pathItem.Parameters = append(pathItem.Parameters, commonParams...)
243 for _, p := range inPathCommonParamsMap {
244 pathItem.Parameters = append(pathItem.Parameters, p)
245 }
246 sortParameters(pathItem.Parameters)
247 for _, route := range routes {
248 op, err := o.buildOperations(route, inPathCommonParamsMap)
249 sortParameters(op.Parameters)
250 if err != nil {
251 return err
252 }
253 dpath, exists := duplicateOpId[op.ID]
254 if exists {
255 return fmt.Errorf("duplicate Operation ID %v for path %v and %v", op.ID, dpath, path)
256 } else {
257 duplicateOpId[op.ID] = path
258 }
259 switch strings.ToUpper(route.Method()) {
260 case "GET":
261 pathItem.Get = op
262 case "POST":
263 pathItem.Post = op
264 case "HEAD":
265 pathItem.Head = op
266 case "PUT":
267 pathItem.Put = op
268 case "DELETE":
269 pathItem.Delete = op
270 case "OPTIONS":
271 pathItem.Options = op
272 case "PATCH":
273 pathItem.Patch = op
274 }
275 }
276 o.swagger.Paths.Paths[path] = pathItem
277 }
278 }
279 return nil
280 }
281
282
283 func (o *openAPI) buildOperations(route common.Route, inPathCommonParamsMap map[interface{}]spec.Parameter) (ret *spec.Operation, err error) {
284 ret = &spec.Operation{
285 OperationProps: spec.OperationProps{
286 Description: route.Description(),
287 Consumes: route.Consumes(),
288 Produces: route.Produces(),
289 Schemes: o.config.ProtocolList,
290 Responses: &spec.Responses{
291 ResponsesProps: spec.ResponsesProps{
292 StatusCodeResponses: make(map[int]spec.Response),
293 },
294 },
295 },
296 }
297 for k, v := range route.Metadata() {
298 if strings.HasPrefix(k, common.ExtensionPrefix) {
299 if ret.Extensions == nil {
300 ret.Extensions = spec.Extensions{}
301 }
302 ret.Extensions.Add(k, v)
303 }
304 }
305 if ret.ID, ret.Tags, err = o.config.GetOperationIDAndTagsFromRoute(route); err != nil {
306 return ret, err
307 }
308
309
310 for _, resp := range route.StatusCodeResponses() {
311 ret.Responses.StatusCodeResponses[resp.Code()], err = o.buildResponse(resp.Model(), resp.Message())
312 if err != nil {
313 return ret, err
314 }
315 }
316
317 if len(ret.Responses.StatusCodeResponses) == 0 && route.ResponsePayloadSample() != nil {
318 ret.Responses.StatusCodeResponses[http.StatusOK], err = o.buildResponse(route.ResponsePayloadSample(), "OK")
319 if err != nil {
320 return ret, err
321 }
322 }
323 for code, resp := range o.config.CommonResponses {
324 if _, exists := ret.Responses.StatusCodeResponses[code]; !exists {
325 ret.Responses.StatusCodeResponses[code] = resp
326 }
327 }
328
329 if len(ret.Responses.StatusCodeResponses) == 0 {
330 ret.Responses.Default = o.config.DefaultResponse
331 }
332
333
334 ret.Parameters = make([]spec.Parameter, 0)
335 for _, param := range route.Parameters() {
336 if _, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)]; !isCommon {
337 openAPIParam, err := o.buildParameter(param, route.RequestPayloadSample())
338 if err != nil {
339 return ret, err
340 }
341 ret.Parameters = append(ret.Parameters, openAPIParam)
342 }
343 }
344 return ret, nil
345 }
346
347 func (o *openAPI) buildResponse(model interface{}, description string) (spec.Response, error) {
348 schema, err := o.toSchema(util.GetCanonicalTypeName(model))
349 if err != nil {
350 return spec.Response{}, err
351 }
352 return spec.Response{
353 ResponseProps: spec.ResponseProps{
354 Description: description,
355 Schema: schema,
356 },
357 }, nil
358 }
359
360 func (o *openAPI) findCommonParameters(routes []common.Route) (map[interface{}]spec.Parameter, error) {
361 commonParamsMap := make(map[interface{}]spec.Parameter, 0)
362 paramOpsCountByName := make(map[interface{}]int, 0)
363 paramNameKindToDataMap := make(map[interface{}]common.Parameter, 0)
364 for _, route := range routes {
365 routeParamDuplicateMap := make(map[interface{}]bool)
366 s := ""
367 params := route.Parameters()
368 for _, param := range params {
369 m, _ := json.Marshal(param)
370 s += string(m) + "\n"
371 key := mapKeyFromParam(param)
372 if routeParamDuplicateMap[key] {
373 msg, _ := json.Marshal(params)
374 return commonParamsMap, fmt.Errorf("duplicate parameter %v for route %v, %v", param.Name(), string(msg), s)
375 }
376 routeParamDuplicateMap[key] = true
377 paramOpsCountByName[key]++
378 paramNameKindToDataMap[key] = param
379 }
380 }
381 for key, count := range paramOpsCountByName {
382 paramData := paramNameKindToDataMap[key]
383 if count == len(routes) && paramData.Kind() != common.BodyParameterKind {
384 openAPIParam, err := o.buildParameter(paramData, nil)
385 if err != nil {
386 return commonParamsMap, err
387 }
388 commonParamsMap[key] = openAPIParam
389 }
390 }
391 return commonParamsMap, nil
392 }
393
394 func (o *openAPI) toSchema(name string) (_ *spec.Schema, err error) {
395 if openAPIType, openAPIFormat := common.OpenAPITypeFormat(name); openAPIType != "" {
396 return &spec.Schema{
397 SchemaProps: spec.SchemaProps{
398 Type: []string{openAPIType},
399 Format: openAPIFormat,
400 },
401 }, nil
402 } else {
403 ref, err := o.buildDefinitionForType(name)
404 if err != nil {
405 return nil, err
406 }
407 return &spec.Schema{
408 SchemaProps: spec.SchemaProps{
409 Ref: spec.MustCreateRef(ref),
410 },
411 }, nil
412 }
413 }
414
415 func (o *openAPI) buildParameter(restParam common.Parameter, bodySample interface{}) (ret spec.Parameter, err error) {
416 ret = spec.Parameter{
417 ParamProps: spec.ParamProps{
418 Name: restParam.Name(),
419 Description: restParam.Description(),
420 Required: restParam.Required(),
421 },
422 }
423 switch restParam.Kind() {
424 case common.BodyParameterKind:
425 if bodySample != nil {
426 ret.In = "body"
427 ret.Schema, err = o.toSchema(util.GetCanonicalTypeName(bodySample))
428 return ret, err
429 } else {
430
431
432
433 return ret, fmt.Errorf("restful body parameters are not supported: %v", restParam.DataType())
434 }
435 case common.PathParameterKind:
436 ret.In = "path"
437 if !restParam.Required() {
438 return ret, fmt.Errorf("path parameters should be marked at required for parameter %v", restParam)
439 }
440 case common.QueryParameterKind:
441 ret.In = "query"
442 case common.HeaderParameterKind:
443 ret.In = "header"
444 case common.FormParameterKind:
445 ret.In = "formData"
446 default:
447 return ret, fmt.Errorf("unknown restful operation kind : %v", restParam.Kind())
448 }
449 openAPIType, openAPIFormat := common.OpenAPITypeFormat(restParam.DataType())
450 if openAPIType == "" {
451 return ret, fmt.Errorf("non-body Restful parameter type should be a simple type, but got : %v", restParam.DataType())
452 }
453 ret.Type = openAPIType
454 ret.Format = openAPIFormat
455 ret.UniqueItems = !restParam.AllowMultiple()
456 return ret, nil
457 }
458
459 func (o *openAPI) buildParameters(restParam []common.Parameter) (ret []spec.Parameter, err error) {
460 ret = make([]spec.Parameter, len(restParam))
461 for i, v := range restParam {
462 ret[i], err = o.buildParameter(v, nil)
463 if err != nil {
464 return ret, err
465 }
466 }
467 return ret, nil
468 }
469
View as plain text