openapi.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  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 openapi
  14. // Note: Any reference to swagger in this document is to swagger 1.2 spec.
  15. import (
  16. "fmt"
  17. "net/http"
  18. "net/url"
  19. "reflect"
  20. "strconv"
  21. "strings"
  22. "github.com/emicklei/go-restful"
  23. "github.com/emicklei/go-restful/swagger"
  24. "github.com/go-openapi/loads"
  25. "github.com/go-openapi/spec"
  26. "github.com/go-openapi/strfmt"
  27. "github.com/go-openapi/validate"
  28. "k8s.io/kubernetes/pkg/util/json"
  29. )
  30. const (
  31. // By convention, the Swagger specification file is named swagger.json
  32. OpenAPIServePath = "/swagger.json"
  33. OpenAPIVersion = "2.0"
  34. )
  35. // Config is set of configuration for openAPI spec generation.
  36. type Config struct {
  37. // SwaggerConfig is set of configuration for go-restful swagger spec generation. Currently
  38. // openAPI implementation depends on go-restful to generate models.
  39. SwaggerConfig *swagger.Config
  40. // Info is general information about the API.
  41. Info *spec.Info
  42. // DefaultResponse will be used if an operation does not have any responses listed. It
  43. // will show up as ... "responses" : {"default" : $DefaultResponse} in swagger spec.
  44. DefaultResponse *spec.Response
  45. // List of webservice's path prefixes to ignore
  46. IgnorePrefixes []string
  47. }
  48. type openAPI struct {
  49. config *Config
  50. swagger *spec.Swagger
  51. protocolList []string
  52. }
  53. // RegisterOpenAPIService registers a handler to provides standard OpenAPI specification.
  54. func RegisterOpenAPIService(config *Config, containers *restful.Container) (err error) {
  55. var _ = loads.Spec
  56. var _ = strfmt.ParseDuration
  57. var _ = validate.FormatOf
  58. o := openAPI{
  59. config: config,
  60. }
  61. err = o.buildSwaggerSpec()
  62. if err != nil {
  63. return err
  64. }
  65. containers.ServeMux.HandleFunc(OpenAPIServePath, func(w http.ResponseWriter, r *http.Request) {
  66. resp := restful.NewResponse(w)
  67. if r.URL.Path != OpenAPIServePath {
  68. resp.WriteErrorString(http.StatusNotFound, "Path not found!")
  69. }
  70. resp.WriteAsJson(o.swagger)
  71. })
  72. return nil
  73. }
  74. func (o *openAPI) buildSwaggerSpec() (err error) {
  75. if o.swagger != nil {
  76. return fmt.Errorf("Swagger spec is already built. Duplicate call to buildSwaggerSpec is not allowed.")
  77. }
  78. o.protocolList, err = o.buildProtocolList()
  79. if err != nil {
  80. return err
  81. }
  82. definitions, err := o.buildDefinitions()
  83. if err != nil {
  84. return err
  85. }
  86. paths, err := o.buildPaths()
  87. if err != nil {
  88. return err
  89. }
  90. o.swagger = &spec.Swagger{
  91. SwaggerProps: spec.SwaggerProps{
  92. Swagger: OpenAPIVersion,
  93. Definitions: definitions,
  94. Paths: &paths,
  95. Info: o.config.Info,
  96. },
  97. }
  98. return nil
  99. }
  100. // buildDefinitions construct OpenAPI definitions using go-restful's swagger 1.2 generated models.
  101. func (o *openAPI) buildDefinitions() (definitions spec.Definitions, err error) {
  102. definitions = spec.Definitions{}
  103. for _, decl := range swagger.NewSwaggerBuilder(*o.config.SwaggerConfig).ProduceAllDeclarations() {
  104. for _, swaggerModel := range decl.Models.List {
  105. _, ok := definitions[swaggerModel.Name]
  106. if ok {
  107. // TODO(mbohlool): decide what to do with repeated models
  108. // The best way is to make sure they have the same content and
  109. // fail otherwise.
  110. continue
  111. }
  112. definitions[swaggerModel.Name], err = buildModel(swaggerModel.Model)
  113. if err != nil {
  114. return definitions, err
  115. }
  116. }
  117. }
  118. return definitions, nil
  119. }
  120. func buildModel(swaggerModel swagger.Model) (ret spec.Schema, err error) {
  121. ret = spec.Schema{
  122. // SchemaProps.SubTypes is not used in go-restful, ignoring.
  123. SchemaProps: spec.SchemaProps{
  124. Description: swaggerModel.Description,
  125. Required: swaggerModel.Required,
  126. Properties: make(map[string]spec.Schema),
  127. },
  128. SwaggerSchemaProps: spec.SwaggerSchemaProps{
  129. Discriminator: swaggerModel.Discriminator,
  130. },
  131. }
  132. for _, swaggerProp := range swaggerModel.Properties.List {
  133. if _, ok := ret.Properties[swaggerProp.Name]; ok {
  134. return ret, fmt.Errorf("Duplicate property in swagger 1.2 spec: %v", swaggerProp.Name)
  135. }
  136. ret.Properties[swaggerProp.Name], err = buildProperty(swaggerProp)
  137. if err != nil {
  138. return ret, err
  139. }
  140. }
  141. return ret, nil
  142. }
  143. // buildProperty converts a swagger 1.2 property to an open API property.
  144. func buildProperty(swaggerProperty swagger.NamedModelProperty) (openAPIProperty spec.Schema, err error) {
  145. if swaggerProperty.Property.Ref != nil {
  146. return spec.Schema{
  147. SchemaProps: spec.SchemaProps{
  148. Ref: spec.MustCreateRef("#/definitions/" + *swaggerProperty.Property.Ref),
  149. },
  150. }, nil
  151. }
  152. openAPIProperty = spec.Schema{
  153. SchemaProps: spec.SchemaProps{
  154. Description: swaggerProperty.Property.Description,
  155. Default: getDefaultValue(swaggerProperty.Property.DefaultValue),
  156. Enum: make([]interface{}, len(swaggerProperty.Property.Enum)),
  157. },
  158. }
  159. for i, e := range swaggerProperty.Property.Enum {
  160. openAPIProperty.Enum[i] = e
  161. }
  162. openAPIProperty.Minimum, err = getFloat64OrNil(swaggerProperty.Property.Minimum)
  163. if err != nil {
  164. return spec.Schema{}, err
  165. }
  166. openAPIProperty.Maximum, err = getFloat64OrNil(swaggerProperty.Property.Maximum)
  167. if err != nil {
  168. return spec.Schema{}, err
  169. }
  170. if swaggerProperty.Property.UniqueItems != nil {
  171. openAPIProperty.UniqueItems = *swaggerProperty.Property.UniqueItems
  172. }
  173. if swaggerProperty.Property.Items != nil {
  174. if swaggerProperty.Property.Items.Ref != nil {
  175. openAPIProperty.Items = &spec.SchemaOrArray{
  176. Schema: &spec.Schema{
  177. SchemaProps: spec.SchemaProps{
  178. Ref: spec.MustCreateRef("#/definitions/" + *swaggerProperty.Property.Items.Ref),
  179. },
  180. },
  181. }
  182. } else {
  183. openAPIProperty.Items = &spec.SchemaOrArray{
  184. Schema: &spec.Schema{},
  185. }
  186. openAPIProperty.Items.Schema.Type, openAPIProperty.Items.Schema.Format, err =
  187. buildType(swaggerProperty.Property.Items.Type, swaggerProperty.Property.Items.Format)
  188. if err != nil {
  189. return spec.Schema{}, err
  190. }
  191. }
  192. }
  193. openAPIProperty.Type, openAPIProperty.Format, err =
  194. buildType(swaggerProperty.Property.Type, swaggerProperty.Property.Format)
  195. if err != nil {
  196. return spec.Schema{}, err
  197. }
  198. return openAPIProperty, nil
  199. }
  200. // buildPaths builds OpenAPI paths using go-restful's web services.
  201. func (o *openAPI) buildPaths() (spec.Paths, error) {
  202. paths := spec.Paths{
  203. Paths: make(map[string]spec.PathItem),
  204. }
  205. pathsToIgnore := createTrie(o.config.IgnorePrefixes)
  206. duplicateOpId := make(map[string]bool)
  207. // Find duplicate operation IDs.
  208. for _, service := range o.config.SwaggerConfig.WebServices {
  209. if pathsToIgnore.HasPrefix(service.RootPath()) {
  210. continue
  211. }
  212. for _, route := range service.Routes() {
  213. _, exists := duplicateOpId[route.Operation]
  214. duplicateOpId[route.Operation] = exists
  215. }
  216. }
  217. for _, w := range o.config.SwaggerConfig.WebServices {
  218. rootPath := w.RootPath()
  219. if pathsToIgnore.HasPrefix(rootPath) {
  220. continue
  221. }
  222. commonParams, err := buildParameters(w.PathParameters())
  223. if err != nil {
  224. return paths, err
  225. }
  226. for path, routes := range groupRoutesByPath(w.Routes()) {
  227. // go-swagger has special variable difinition {$NAME:*} that can only be
  228. // used at the end of the path and it is not recognized by OpenAPI.
  229. if strings.HasSuffix(path, ":*}") {
  230. path = path[:len(path)-3] + "}"
  231. }
  232. inPathCommonParamsMap, err := findCommonParameters(routes)
  233. if err != nil {
  234. return paths, err
  235. }
  236. pathItem, exists := paths.Paths[path]
  237. if exists {
  238. return paths, fmt.Errorf("Duplicate webservice route has been found for path: %v", path)
  239. }
  240. pathItem = spec.PathItem{
  241. PathItemProps: spec.PathItemProps{
  242. Parameters: make([]spec.Parameter, 0),
  243. },
  244. }
  245. // add web services's parameters as well as any parameters appears in all ops, as common parameters
  246. for _, p := range commonParams {
  247. pathItem.Parameters = append(pathItem.Parameters, p)
  248. }
  249. for _, p := range inPathCommonParamsMap {
  250. pathItem.Parameters = append(pathItem.Parameters, p)
  251. }
  252. for _, route := range routes {
  253. op, err := o.buildOperations(route, inPathCommonParamsMap)
  254. if err != nil {
  255. return paths, err
  256. }
  257. if duplicateOpId[op.ID] {
  258. // Repeated Operation IDs are not allowed in OpenAPI spec but if
  259. // an OperationID is empty, client generators will infer the ID
  260. // from the path and method of operation.
  261. op.ID = ""
  262. }
  263. switch strings.ToUpper(route.Method) {
  264. case "GET":
  265. pathItem.Get = op
  266. case "POST":
  267. pathItem.Post = op
  268. case "HEAD":
  269. pathItem.Head = op
  270. case "PUT":
  271. pathItem.Put = op
  272. case "DELETE":
  273. pathItem.Delete = op
  274. case "OPTIONS":
  275. pathItem.Options = op
  276. case "PATCH":
  277. pathItem.Patch = op
  278. }
  279. }
  280. paths.Paths[path] = pathItem
  281. }
  282. }
  283. return paths, nil
  284. }
  285. // buildProtocolList returns list of accepted protocols for this web service. If web service url has no protocol, it
  286. // will default to http.
  287. func (o *openAPI) buildProtocolList() ([]string, error) {
  288. uri, err := url.Parse(o.config.SwaggerConfig.WebServicesUrl)
  289. if err != nil {
  290. return []string{}, err
  291. }
  292. if uri.Scheme != "" {
  293. return []string{uri.Scheme}, nil
  294. } else {
  295. return []string{"http"}, nil
  296. }
  297. }
  298. // buildOperations builds operations for each webservice path
  299. func (o *openAPI) buildOperations(route restful.Route, inPathCommonParamsMap map[interface{}]spec.Parameter) (*spec.Operation, error) {
  300. ret := &spec.Operation{
  301. OperationProps: spec.OperationProps{
  302. Description: route.Doc,
  303. Consumes: route.Consumes,
  304. Produces: route.Produces,
  305. ID: route.Operation,
  306. Schemes: o.protocolList,
  307. Responses: &spec.Responses{
  308. ResponsesProps: spec.ResponsesProps{
  309. StatusCodeResponses: make(map[int]spec.Response),
  310. },
  311. },
  312. },
  313. }
  314. for _, resp := range route.ResponseErrors {
  315. ret.Responses.StatusCodeResponses[resp.Code] = spec.Response{
  316. ResponseProps: spec.ResponseProps{
  317. Description: resp.Message,
  318. Schema: &spec.Schema{
  319. SchemaProps: spec.SchemaProps{
  320. Ref: spec.MustCreateRef("#/definitions/" + reflect.TypeOf(resp.Model).String()),
  321. },
  322. },
  323. },
  324. }
  325. }
  326. if len(ret.Responses.StatusCodeResponses) == 0 {
  327. ret.Responses.Default = o.config.DefaultResponse
  328. }
  329. ret.Parameters = make([]spec.Parameter, 0)
  330. for _, param := range route.ParameterDocs {
  331. _, isCommon := inPathCommonParamsMap[mapKeyFromParam(param)]
  332. if !isCommon {
  333. openAPIParam, err := buildParameter(param.Data())
  334. if err != nil {
  335. return ret, err
  336. }
  337. ret.Parameters = append(ret.Parameters, openAPIParam)
  338. }
  339. }
  340. return ret, nil
  341. }
  342. func groupRoutesByPath(routes []restful.Route) (ret map[string][]restful.Route) {
  343. ret = make(map[string][]restful.Route)
  344. for _, r := range routes {
  345. route, exists := ret[r.Path]
  346. if !exists {
  347. route = make([]restful.Route, 0, 1)
  348. }
  349. ret[r.Path] = append(route, r)
  350. }
  351. return ret
  352. }
  353. func mapKeyFromParam(param *restful.Parameter) interface{} {
  354. return struct {
  355. Name string
  356. Kind int
  357. }{
  358. Name: param.Data().Name,
  359. Kind: param.Data().Kind,
  360. }
  361. }
  362. func findCommonParameters(routes []restful.Route) (map[interface{}]spec.Parameter, error) {
  363. commonParamsMap := make(map[interface{}]spec.Parameter, 0)
  364. paramOpsCountByName := make(map[interface{}]int, 0)
  365. paramNameKindToDataMap := make(map[interface{}]restful.ParameterData, 0)
  366. for _, route := range routes {
  367. routeParamDuplicateMap := make(map[interface{}]bool)
  368. s := ""
  369. for _, param := range route.ParameterDocs {
  370. m, _ := json.Marshal(param.Data())
  371. s += string(m) + "\n"
  372. key := mapKeyFromParam(param)
  373. if routeParamDuplicateMap[key] {
  374. msg, _ := json.Marshal(route.ParameterDocs)
  375. return commonParamsMap, fmt.Errorf("Duplicate parameter %v for route %v, %v.", param.Data().Name, string(msg), s)
  376. }
  377. routeParamDuplicateMap[key] = true
  378. paramOpsCountByName[key]++
  379. paramNameKindToDataMap[key] = param.Data()
  380. }
  381. }
  382. for key, count := range paramOpsCountByName {
  383. if count == len(routes) {
  384. openAPIParam, err := buildParameter(paramNameKindToDataMap[key])
  385. if err != nil {
  386. return commonParamsMap, err
  387. }
  388. commonParamsMap[key] = openAPIParam
  389. }
  390. }
  391. return commonParamsMap, nil
  392. }
  393. func buildParameter(restParam restful.ParameterData) (ret spec.Parameter, err error) {
  394. ret = spec.Parameter{
  395. ParamProps: spec.ParamProps{
  396. Name: restParam.Name,
  397. Description: restParam.Description,
  398. Required: restParam.Required,
  399. },
  400. }
  401. switch restParam.Kind {
  402. case restful.BodyParameterKind:
  403. ret.In = "body"
  404. ret.Schema = &spec.Schema{
  405. SchemaProps: spec.SchemaProps{
  406. Ref: spec.MustCreateRef("#/definitions/" + restParam.DataType),
  407. },
  408. }
  409. return ret, nil
  410. case restful.PathParameterKind:
  411. ret.In = "path"
  412. if !restParam.Required {
  413. return ret, fmt.Errorf("Path parameters should be marked at required for parameter %v", restParam)
  414. }
  415. case restful.QueryParameterKind:
  416. ret.In = "query"
  417. case restful.HeaderParameterKind:
  418. ret.In = "header"
  419. case restful.FormParameterKind:
  420. ret.In = "form"
  421. default:
  422. return ret, fmt.Errorf("Unknown restful operation kind : %v", restParam.Kind)
  423. }
  424. if !isSimpleDataType(restParam.DataType) {
  425. return ret, fmt.Errorf("Restful DataType should be a simple type, but got : %v", restParam.DataType)
  426. }
  427. ret.Type = restParam.DataType
  428. ret.Format = restParam.DataFormat
  429. ret.UniqueItems = !restParam.AllowMultiple
  430. // TODO(mbohlool): make sure the type of default value matches Type
  431. if restParam.DefaultValue != "" {
  432. ret.Default = restParam.DefaultValue
  433. }
  434. return ret, nil
  435. }
  436. func buildParameters(restParam []*restful.Parameter) (ret []spec.Parameter, err error) {
  437. ret = make([]spec.Parameter, len(restParam))
  438. for i, v := range restParam {
  439. ret[i], err = buildParameter(v.Data())
  440. if err != nil {
  441. return ret, err
  442. }
  443. }
  444. return ret, nil
  445. }
  446. func isSimpleDataType(typeName string) bool {
  447. switch typeName {
  448. // Note that "file" intentionally kept out of this list as it is not being used.
  449. // "file" type has more requirements.
  450. case "string", "number", "integer", "boolean", "array":
  451. return true
  452. }
  453. return false
  454. }
  455. func getFloat64OrNil(str string) (*float64, error) {
  456. if len(str) > 0 {
  457. num, err := strconv.ParseFloat(str, 64)
  458. return &num, err
  459. }
  460. return nil, nil
  461. }
  462. // TODO(mbohlool): Convert default value type to the type of parameter
  463. func getDefaultValue(str swagger.Special) interface{} {
  464. if len(str) > 0 {
  465. return str
  466. }
  467. return nil
  468. }
  469. func buildType(swaggerType *string, swaggerFormat string) ([]string, string, error) {
  470. if swaggerType == nil {
  471. return []string{}, "", nil
  472. }
  473. switch *swaggerType {
  474. case "integer", "number", "string", "boolean", "array", "object", "file":
  475. return []string{*swaggerType}, swaggerFormat, nil
  476. case "int":
  477. return []string{"integer"}, "int32", nil
  478. case "long":
  479. return []string{"integer"}, "int64", nil
  480. case "float", "double":
  481. return []string{"number"}, *swaggerType, nil
  482. case "byte", "date", "datetime", "date-time":
  483. return []string{"string"}, *swaggerType, nil
  484. default:
  485. return []string{}, "", fmt.Errorf("Unrecognized swagger 1.2 type : %v, %v", swaggerType, swaggerFormat)
  486. }
  487. }
  488. // A simple trie implementation with Add an HasPrefix methods only.
  489. type trie struct {
  490. children map[byte]*trie
  491. }
  492. func createTrie(list []string) trie {
  493. ret := trie{
  494. children: make(map[byte]*trie),
  495. }
  496. for _, v := range list {
  497. ret.Add(v)
  498. }
  499. return ret
  500. }
  501. func (t *trie) Add(v string) {
  502. root := t
  503. for _, b := range []byte(v) {
  504. child, exists := root.children[b]
  505. if !exists {
  506. child = new(trie)
  507. child.children = make(map[byte]*trie)
  508. root.children[b] = child
  509. }
  510. root = child
  511. }
  512. }
  513. func (t *trie) HasPrefix(v string) bool {
  514. root := t
  515. for _, b := range []byte(v) {
  516. child, exists := root.children[b]
  517. if !exists {
  518. return false
  519. }
  520. root = child
  521. }
  522. return true
  523. }