openapi.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package builder
  14. import (
  15. "encoding/json"
  16. "fmt"
  17. "net/http"
  18. "strings"
  19. restful "github.com/emicklei/go-restful"
  20. "github.com/go-openapi/spec"
  21. "k8s.io/kube-openapi/pkg/common"
  22. "k8s.io/kube-openapi/pkg/util"
  23. )
  24. const (
  25. OpenAPIVersion = "2.0"
  26. )
  27. type openAPI struct {
  28. config *common.Config
  29. swagger *spec.Swagger
  30. protocolList []string
  31. definitions map[string]common.OpenAPIDefinition
  32. }
  33. // BuildOpenAPISpec builds OpenAPI spec given a list of webservices (containing routes) and common.Config to customize it.
  34. func BuildOpenAPISpec(webServices []*restful.WebService, config *common.Config) (*spec.Swagger, error) {
  35. o := newOpenAPI(config)
  36. err := o.buildPaths(webServices)
  37. if err != nil {
  38. return nil, err
  39. }
  40. return o.finalizeSwagger()
  41. }
  42. // BuildOpenAPIDefinitionsForResource builds a partial OpenAPI spec given a sample object and common.Config to customize it.
  43. func BuildOpenAPIDefinitionsForResource(model interface{}, config *common.Config) (*spec.Definitions, error) {
  44. o := newOpenAPI(config)
  45. // We can discard the return value of toSchema because all we care about is the side effect of calling it.
  46. // All the models created for this resource get added to o.swagger.Definitions
  47. _, err := o.toSchema(util.GetCanonicalTypeName(model))
  48. if err != nil {
  49. return nil, err
  50. }
  51. swagger, err := o.finalizeSwagger()
  52. if err != nil {
  53. return nil, err
  54. }
  55. return &swagger.Definitions, nil
  56. }
  57. // BuildOpenAPIDefinitionsForResources returns the OpenAPI spec which includes the definitions for the
  58. // passed type names.
  59. func BuildOpenAPIDefinitionsForResources(config *common.Config, names ...string) (*spec.Swagger, error) {
  60. o := newOpenAPI(config)
  61. // We can discard the return value of toSchema because all we care about is the side effect of calling it.
  62. // All the models created for this resource get added to o.swagger.Definitions
  63. for _, name := range names {
  64. _, err := o.toSchema(name)
  65. if err != nil {
  66. return nil, err
  67. }
  68. }
  69. return o.finalizeSwagger()
  70. }
  71. // newOpenAPI sets up the openAPI object so we can build the spec.
  72. func newOpenAPI(config *common.Config) openAPI {
  73. o := openAPI{
  74. config: config,
  75. swagger: &spec.Swagger{
  76. SwaggerProps: spec.SwaggerProps{
  77. Swagger: OpenAPIVersion,
  78. Definitions: spec.Definitions{},
  79. Responses: config.ResponseDefinitions,
  80. Paths: &spec.Paths{Paths: map[string]spec.PathItem{}},
  81. Info: config.Info,
  82. },
  83. },
  84. }
  85. if o.config.GetOperationIDAndTags == nil {
  86. o.config.GetOperationIDAndTags = func(r *restful.Route) (string, []string, error) {
  87. return r.Operation, nil, nil
  88. }
  89. }
  90. if o.config.GetDefinitionName == nil {
  91. o.config.GetDefinitionName = func(name string) (string, spec.Extensions) {
  92. return name[strings.LastIndex(name, "/")+1:], nil
  93. }
  94. }
  95. o.definitions = o.config.GetDefinitions(func(name string) spec.Ref {
  96. defName, _ := o.config.GetDefinitionName(name)
  97. return spec.MustCreateRef("#/definitions/" + common.EscapeJsonPointer(defName))
  98. })
  99. if o.config.CommonResponses == nil {
  100. o.config.CommonResponses = map[int]spec.Response{}
  101. }
  102. return o
  103. }
  104. // finalizeSwagger is called after the spec is built and returns the final spec.
  105. // NOTE: finalizeSwagger also make changes to the final spec, as specified in the config.
  106. func (o *openAPI) finalizeSwagger() (*spec.Swagger, error) {
  107. if o.config.SecurityDefinitions != nil {
  108. o.swagger.SecurityDefinitions = *o.config.SecurityDefinitions
  109. o.swagger.Security = o.config.DefaultSecurity
  110. }
  111. if o.config.PostProcessSpec != nil {
  112. var err error
  113. o.swagger, err = o.config.PostProcessSpec(o.swagger)
  114. if err != nil {
  115. return nil, err
  116. }
  117. }
  118. return o.swagger, nil
  119. }
  120. func (o *openAPI) buildDefinitionRecursively(name string) error {
  121. uniqueName, extensions := o.config.GetDefinitionName(name)
  122. if _, ok := o.swagger.Definitions[uniqueName]; ok {
  123. return nil
  124. }
  125. if item, ok := o.definitions[name]; ok {
  126. schema := spec.Schema{
  127. VendorExtensible: item.Schema.VendorExtensible,
  128. SchemaProps: item.Schema.SchemaProps,
  129. SwaggerSchemaProps: item.Schema.SwaggerSchemaProps,
  130. }
  131. if extensions != nil {
  132. if schema.Extensions == nil {
  133. schema.Extensions = spec.Extensions{}
  134. }
  135. for k, v := range extensions {
  136. schema.Extensions[k] = v
  137. }
  138. }
  139. if v, ok := item.Schema.Extensions[common.ExtensionV2Schema]; ok {
  140. if v2Schema, isOpenAPISchema := v.(spec.Schema); isOpenAPISchema {
  141. schema = v2Schema
  142. }
  143. }
  144. o.swagger.Definitions[uniqueName] = schema
  145. for _, v := range item.Dependencies {
  146. if err := o.buildDefinitionRecursively(v); err != nil {
  147. return err
  148. }
  149. }
  150. } else {
  151. 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)
  152. }
  153. return nil
  154. }
  155. // buildDefinitionForType build a definition for a given type and return a referable name to its definition.
  156. // This is the main function that keep track of definitions used in this spec and is depend on code generated
  157. // by k8s.io/kubernetes/cmd/libs/go2idl/openapi-gen.
  158. func (o *openAPI) buildDefinitionForType(name string) (string, error) {
  159. if err := o.buildDefinitionRecursively(name); err != nil {
  160. return "", err
  161. }
  162. defName, _ := o.config.GetDefinitionName(name)
  163. return "#/definitions/" + common.EscapeJsonPointer(defName), nil
  164. }
  165. // buildPaths builds OpenAPI paths using go-restful's web services.
  166. func (o *openAPI) buildPaths(webServices []*restful.WebService) error {
  167. pathsToIgnore := util.NewTrie(o.config.IgnorePrefixes)
  168. duplicateOpId := make(map[string]string)
  169. for _, w := range webServices {
  170. rootPath := w.RootPath()
  171. if pathsToIgnore.HasPrefix(rootPath) {
  172. continue
  173. }
  174. commonParams, err := o.buildParameters(w.PathParameters())
  175. if err != nil {
  176. return err
  177. }
  178. for path, routes := range groupRoutesByPath(w.Routes()) {
  179. // go-swagger has special variable definition {$NAME:*} that can only be
  180. // used at the end of the path and it is not recognized by OpenAPI.
  181. if strings.HasSuffix(path, ":*}") {
  182. path = path[:len(path)-3] + "}"
  183. }
  184. if pathsToIgnore.HasPrefix(path) {
  185. continue
  186. }
  187. // Aggregating common parameters make API spec (and generated clients) simpler
  188. inPathCommonParamsMap, err := o.findCommonParameters(routes)
  189. if err != nil {
  190. return err
  191. }
  192. pathItem, exists := o.swagger.Paths.Paths[path]
  193. if exists {
  194. return fmt.Errorf("duplicate webservice route has been found for path: %v", path)
  195. }
  196. pathItem = spec.PathItem{
  197. PathItemProps: spec.PathItemProps{
  198. Parameters: make([]spec.Parameter, 0),
  199. },
  200. }
  201. // add web services's parameters as well as any parameters appears in all ops, as common parameters
  202. pathItem.Parameters = append(pathItem.Parameters, commonParams...)
  203. for _, p := range inPathCommonParamsMap {
  204. pathItem.Parameters = append(pathItem.Parameters, p)
  205. }
  206. sortParameters(pathItem.Parameters)
  207. for _, route := range routes {
  208. op, err := o.buildOperations(route, inPathCommonParamsMap)
  209. sortParameters(op.Parameters)
  210. if err != nil {
  211. return err
  212. }
  213. dpath, exists := duplicateOpId[op.ID]
  214. if exists {
  215. return fmt.Errorf("duplicate Operation ID %v for path %v and %v", op.ID, dpath, path)
  216. } else {
  217. duplicateOpId[op.ID] = path
  218. }
  219. switch strings.ToUpper(route.Method) {
  220. case "GET":
  221. pathItem.Get = op
  222. case "POST":
  223. pathItem.Post = op
  224. case "HEAD":
  225. pathItem.Head = op
  226. case "PUT":
  227. pathItem.Put = op
  228. case "DELETE":
  229. pathItem.Delete = op
  230. case "OPTIONS":
  231. pathItem.Options = op
  232. case "PATCH":
  233. pathItem.Patch = op
  234. }
  235. }
  236. o.swagger.Paths.Paths[path] = pathItem
  237. }
  238. }
  239. return nil
  240. }
  241. // buildOperations builds operations for each webservice path
  242. func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map[interface{}]spec.Parameter) (ret *spec.Operation, err error) {
  243. ret = &spec.Operation{
  244. OperationProps: spec.OperationProps{
  245. Description: route.Doc,
  246. Consumes: route.Consumes,
  247. Produces: route.Produces,
  248. Schemes: o.config.ProtocolList,
  249. Responses: &spec.Responses{
  250. ResponsesProps: spec.ResponsesProps{
  251. StatusCodeResponses: make(map[int]spec.Response),
  252. },
  253. },
  254. },
  255. }
  256. for k, v := range route.Metadata {
  257. if strings.HasPrefix(k, common.ExtensionPrefix) {
  258. if ret.Extensions == nil {
  259. ret.Extensions = spec.Extensions{}
  260. }
  261. ret.Extensions.Add(k, v)
  262. }
  263. }
  264. if ret.ID, ret.Tags, err = o.config.GetOperationIDAndTags(&route); err != nil {
  265. return ret, err
  266. }
  267. // Build responses
  268. for _, resp := range route.ResponseErrors {
  269. ret.Responses.StatusCodeResponses[resp.Code], err = o.buildResponse(resp.Model, resp.Message)
  270. if err != nil {
  271. return ret, err
  272. }
  273. }
  274. // If there is no response but a write sample, assume that write sample is an http.StatusOK response.
  275. if len(ret.Responses.StatusCodeResponses) == 0 && route.WriteSample != nil {
  276. ret.Responses.StatusCodeResponses[http.StatusOK], err = o.buildResponse(route.WriteSample, "OK")
  277. if err != nil {
  278. return ret, err
  279. }
  280. }
  281. for code, resp := range o.config.CommonResponses {
  282. if _, exists := ret.Responses.StatusCodeResponses[code]; !exists {
  283. ret.Responses.StatusCodeResponses[code] = resp
  284. }
  285. }
  286. // If there is still no response, use default response provided.
  287. if len(ret.Responses.StatusCodeResponses) == 0 {
  288. ret.Responses.Default = o.config.DefaultResponse
  289. }
  290. // Build non-common Parameters
  291. ret.Parameters = make([]spec.Parameter, 0)
  292. for _, param := range route.ParameterDocs {
  293. if _, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)]; !isCommon {
  294. openAPIParam, err := o.buildParameter(param.Data(), route.ReadSample)
  295. if err != nil {
  296. return ret, err
  297. }
  298. ret.Parameters = append(ret.Parameters, openAPIParam)
  299. }
  300. }
  301. return ret, nil
  302. }
  303. func (o *openAPI) buildResponse(model interface{}, description string) (spec.Response, error) {
  304. schema, err := o.toSchema(util.GetCanonicalTypeName(model))
  305. if err != nil {
  306. return spec.Response{}, err
  307. }
  308. return spec.Response{
  309. ResponseProps: spec.ResponseProps{
  310. Description: description,
  311. Schema: schema,
  312. },
  313. }, nil
  314. }
  315. func (o *openAPI) findCommonParameters(routes []restful.Route) (map[interface{}]spec.Parameter, error) {
  316. commonParamsMap := make(map[interface{}]spec.Parameter, 0)
  317. paramOpsCountByName := make(map[interface{}]int, 0)
  318. paramNameKindToDataMap := make(map[interface{}]restful.ParameterData, 0)
  319. for _, route := range routes {
  320. routeParamDuplicateMap := make(map[interface{}]bool)
  321. s := ""
  322. for _, param := range route.ParameterDocs {
  323. m, _ := json.Marshal(param.Data())
  324. s += string(m) + "\n"
  325. key := mapKeyFromParam(param)
  326. if routeParamDuplicateMap[key] {
  327. msg, _ := json.Marshal(route.ParameterDocs)
  328. return commonParamsMap, fmt.Errorf("duplicate parameter %v for route %v, %v", param.Data().Name, string(msg), s)
  329. }
  330. routeParamDuplicateMap[key] = true
  331. paramOpsCountByName[key]++
  332. paramNameKindToDataMap[key] = param.Data()
  333. }
  334. }
  335. for key, count := range paramOpsCountByName {
  336. paramData := paramNameKindToDataMap[key]
  337. if count == len(routes) && paramData.Kind != restful.BodyParameterKind {
  338. openAPIParam, err := o.buildParameter(paramData, nil)
  339. if err != nil {
  340. return commonParamsMap, err
  341. }
  342. commonParamsMap[key] = openAPIParam
  343. }
  344. }
  345. return commonParamsMap, nil
  346. }
  347. func (o *openAPI) toSchema(name string) (_ *spec.Schema, err error) {
  348. if openAPIType, openAPIFormat := common.GetOpenAPITypeFormat(name); openAPIType != "" {
  349. return &spec.Schema{
  350. SchemaProps: spec.SchemaProps{
  351. Type: []string{openAPIType},
  352. Format: openAPIFormat,
  353. },
  354. }, nil
  355. } else {
  356. ref, err := o.buildDefinitionForType(name)
  357. if err != nil {
  358. return nil, err
  359. }
  360. return &spec.Schema{
  361. SchemaProps: spec.SchemaProps{
  362. Ref: spec.MustCreateRef(ref),
  363. },
  364. }, nil
  365. }
  366. }
  367. func (o *openAPI) buildParameter(restParam restful.ParameterData, bodySample interface{}) (ret spec.Parameter, err error) {
  368. ret = spec.Parameter{
  369. ParamProps: spec.ParamProps{
  370. Name: restParam.Name,
  371. Description: restParam.Description,
  372. Required: restParam.Required,
  373. },
  374. }
  375. switch restParam.Kind {
  376. case restful.BodyParameterKind:
  377. if bodySample != nil {
  378. ret.In = "body"
  379. ret.Schema, err = o.toSchema(util.GetCanonicalTypeName(bodySample))
  380. return ret, err
  381. } else {
  382. // There is not enough information in the body parameter to build the definition.
  383. // Body parameter has a data type that is a short name but we need full package name
  384. // of the type to create a definition.
  385. return ret, fmt.Errorf("restful body parameters are not supported: %v", restParam.DataType)
  386. }
  387. case restful.PathParameterKind:
  388. ret.In = "path"
  389. if !restParam.Required {
  390. return ret, fmt.Errorf("path parameters should be marked at required for parameter %v", restParam)
  391. }
  392. case restful.QueryParameterKind:
  393. ret.In = "query"
  394. case restful.HeaderParameterKind:
  395. ret.In = "header"
  396. case restful.FormParameterKind:
  397. ret.In = "formData"
  398. default:
  399. return ret, fmt.Errorf("unknown restful operation kind : %v", restParam.Kind)
  400. }
  401. openAPIType, openAPIFormat := common.GetOpenAPITypeFormat(restParam.DataType)
  402. if openAPIType == "" {
  403. return ret, fmt.Errorf("non-body Restful parameter type should be a simple type, but got : %v", restParam.DataType)
  404. }
  405. ret.Type = openAPIType
  406. ret.Format = openAPIFormat
  407. ret.UniqueItems = !restParam.AllowMultiple
  408. return ret, nil
  409. }
  410. func (o *openAPI) buildParameters(restParam []*restful.Parameter) (ret []spec.Parameter, err error) {
  411. ret = make([]spec.Parameter, len(restParam))
  412. for i, v := range restParam {
  413. ret[i], err = o.buildParameter(v.Data(), nil)
  414. if err != nil {
  415. return ret, err
  416. }
  417. }
  418. return ret, nil
  419. }