openapi.go 14 KB

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