api_installer.go 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998
  1. /*
  2. Copyright 2015 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 apiserver
  14. import (
  15. "fmt"
  16. "net/http"
  17. gpath "path"
  18. "reflect"
  19. "sort"
  20. "strings"
  21. "time"
  22. "k8s.io/kubernetes/pkg/api"
  23. "k8s.io/kubernetes/pkg/api/errors"
  24. "k8s.io/kubernetes/pkg/api/meta"
  25. "k8s.io/kubernetes/pkg/api/rest"
  26. "k8s.io/kubernetes/pkg/api/unversioned"
  27. "k8s.io/kubernetes/pkg/apis/extensions"
  28. "k8s.io/kubernetes/pkg/apiserver/metrics"
  29. "k8s.io/kubernetes/pkg/conversion"
  30. "k8s.io/kubernetes/pkg/runtime"
  31. "github.com/emicklei/go-restful"
  32. )
  33. type APIInstaller struct {
  34. group *APIGroupVersion
  35. info *RequestInfoResolver
  36. prefix string // Path prefix where API resources are to be registered.
  37. minRequestTimeout time.Duration
  38. }
  39. // Struct capturing information about an action ("GET", "POST", "WATCH", PROXY", etc).
  40. type action struct {
  41. Verb string // Verb identifying the action ("GET", "POST", "WATCH", PROXY", etc).
  42. Path string // The path of the action
  43. Params []*restful.Parameter // List of parameters associated with the action.
  44. Namer ScopeNamer
  45. }
  46. // An interface to see if an object supports swagger documentation as a method
  47. type documentable interface {
  48. SwaggerDoc() map[string]string
  49. }
  50. // errEmptyName is returned when API requests do not fill the name section of the path.
  51. var errEmptyName = errors.NewBadRequest("name must be provided")
  52. // Installs handlers for API resources.
  53. func (a *APIInstaller) Install(ws *restful.WebService) (apiResources []unversioned.APIResource, errors []error) {
  54. errors = make([]error, 0)
  55. proxyHandler := (&ProxyHandler{
  56. prefix: a.prefix + "/proxy/",
  57. storage: a.group.Storage,
  58. serializer: a.group.Serializer,
  59. context: a.group.Context,
  60. requestInfoResolver: a.info,
  61. })
  62. // Register the paths in a deterministic (sorted) order to get a deterministic swagger spec.
  63. paths := make([]string, len(a.group.Storage))
  64. var i int = 0
  65. for path := range a.group.Storage {
  66. paths[i] = path
  67. i++
  68. }
  69. sort.Strings(paths)
  70. for _, path := range paths {
  71. apiResource, err := a.registerResourceHandlers(path, a.group.Storage[path], ws, proxyHandler)
  72. if err != nil {
  73. errors = append(errors, fmt.Errorf("error in registering resource: %s, %v", path, err))
  74. }
  75. if apiResource != nil {
  76. apiResources = append(apiResources, *apiResource)
  77. }
  78. }
  79. return apiResources, errors
  80. }
  81. // NewWebService creates a new restful webservice with the api installer's prefix and version.
  82. func (a *APIInstaller) NewWebService() *restful.WebService {
  83. ws := new(restful.WebService)
  84. ws.Path(a.prefix)
  85. // a.prefix contains "prefix/group/version"
  86. ws.Doc("API at " + a.prefix)
  87. // Backwards compatibility, we accepted objects with empty content-type at V1.
  88. // If we stop using go-restful, we can default empty content-type to application/json on an
  89. // endpoint by endpoint basis
  90. ws.Consumes("*/*")
  91. ws.Produces(a.group.Serializer.SupportedMediaTypes()...)
  92. ws.ApiVersion(a.group.GroupVersion.String())
  93. return ws
  94. }
  95. // getResourceKind returns the external group version kind registered for the given storage
  96. // object. If the storage object is a subresource and has an override supplied for it, it returns
  97. // the group version kind supplied in the override.
  98. func (a *APIInstaller) getResourceKind(path string, storage rest.Storage) (unversioned.GroupVersionKind, error) {
  99. if fqKindToRegister, ok := a.group.SubresourceGroupVersionKind[path]; ok {
  100. return fqKindToRegister, nil
  101. }
  102. object := storage.New()
  103. fqKinds, _, err := a.group.Typer.ObjectKinds(object)
  104. if err != nil {
  105. return unversioned.GroupVersionKind{}, err
  106. }
  107. // a given go type can have multiple potential fully qualified kinds. Find the one that corresponds with the group
  108. // we're trying to register here
  109. fqKindToRegister := unversioned.GroupVersionKind{}
  110. for _, fqKind := range fqKinds {
  111. if fqKind.Group == a.group.GroupVersion.Group {
  112. fqKindToRegister = a.group.GroupVersion.WithKind(fqKind.Kind)
  113. break
  114. }
  115. // TODO This keeps it doing what it was doing before, but it doesn't feel right.
  116. if fqKind.Group == extensions.GroupName && fqKind.Kind == "ThirdPartyResourceData" {
  117. fqKindToRegister = a.group.GroupVersion.WithKind(fqKind.Kind)
  118. }
  119. }
  120. if fqKindToRegister.Empty() {
  121. return unversioned.GroupVersionKind{}, fmt.Errorf("unable to locate fully qualified kind for %v: found %v when registering for %v", reflect.TypeOf(object), fqKinds, a.group.GroupVersion)
  122. }
  123. return fqKindToRegister, nil
  124. }
  125. // restMapping returns rest mapper for the resource.
  126. // Example REST paths that this mapper maps.
  127. // 1. Resource only, no subresource:
  128. // Resource Type: batch/v1.Job (input args: resource = "jobs")
  129. // REST path: /apis/batch/v1/namespaces/{namespace}/job/{name}
  130. // 2. Subresource and its parent belong to different API groups and/or versions:
  131. // Resource Type: extensions/v1beta1.ReplicaSet (input args: resource = "replicasets")
  132. // Subresource Type: autoscaling/v1.Scale
  133. // REST path: /apis/extensions/v1beta1/namespaces/{namespace}/replicaset/{name}/scale
  134. func (a *APIInstaller) restMapping(resource string) (*meta.RESTMapping, error) {
  135. // subresources must have parent resources, and follow the namespacing rules of their parent.
  136. // So get the storage of the resource (which is the parent resource in case of subresources)
  137. storage, ok := a.group.Storage[resource]
  138. if !ok {
  139. return nil, fmt.Errorf("unable to locate the storage object for resource: %s", resource)
  140. }
  141. fqKindToRegister, err := a.getResourceKind(resource, storage)
  142. if err != nil {
  143. return nil, fmt.Errorf("unable to locate fully qualified kind for mapper resource %s: %v", resource, err)
  144. }
  145. return a.group.Mapper.RESTMapping(fqKindToRegister.GroupKind(), fqKindToRegister.Version)
  146. }
  147. func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService, proxyHandler http.Handler) (*unversioned.APIResource, error) {
  148. admit := a.group.Admit
  149. context := a.group.Context
  150. optionsExternalVersion := a.group.GroupVersion
  151. if a.group.OptionsExternalVersion != nil {
  152. optionsExternalVersion = *a.group.OptionsExternalVersion
  153. }
  154. resource, subresource, err := splitSubresource(path)
  155. if err != nil {
  156. return nil, err
  157. }
  158. mapping, err := a.restMapping(resource)
  159. if err != nil {
  160. return nil, err
  161. }
  162. fqKindToRegister, err := a.getResourceKind(path, storage)
  163. if err != nil {
  164. return nil, err
  165. }
  166. versionedPtr, err := a.group.Creater.New(fqKindToRegister)
  167. if err != nil {
  168. return nil, err
  169. }
  170. versionedObject := indirectArbitraryPointer(versionedPtr)
  171. kind := fqKindToRegister.Kind
  172. hasSubresource := len(subresource) > 0
  173. // what verbs are supported by the storage, used to know what verbs we support per path
  174. creater, isCreater := storage.(rest.Creater)
  175. namedCreater, isNamedCreater := storage.(rest.NamedCreater)
  176. lister, isLister := storage.(rest.Lister)
  177. getter, isGetter := storage.(rest.Getter)
  178. getterWithOptions, isGetterWithOptions := storage.(rest.GetterWithOptions)
  179. deleter, isDeleter := storage.(rest.Deleter)
  180. gracefulDeleter, isGracefulDeleter := storage.(rest.GracefulDeleter)
  181. collectionDeleter, isCollectionDeleter := storage.(rest.CollectionDeleter)
  182. updater, isUpdater := storage.(rest.Updater)
  183. patcher, isPatcher := storage.(rest.Patcher)
  184. watcher, isWatcher := storage.(rest.Watcher)
  185. _, isRedirector := storage.(rest.Redirector)
  186. connecter, isConnecter := storage.(rest.Connecter)
  187. storageMeta, isMetadata := storage.(rest.StorageMetadata)
  188. if !isMetadata {
  189. storageMeta = defaultStorageMetadata{}
  190. }
  191. exporter, isExporter := storage.(rest.Exporter)
  192. if !isExporter {
  193. exporter = nil
  194. }
  195. versionedExportOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("ExportOptions"))
  196. if err != nil {
  197. return nil, err
  198. }
  199. if isNamedCreater {
  200. isCreater = true
  201. }
  202. var versionedList interface{}
  203. if isLister {
  204. list := lister.NewList()
  205. listGVKs, _, err := a.group.Typer.ObjectKinds(list)
  206. if err != nil {
  207. return nil, err
  208. }
  209. versionedListPtr, err := a.group.Creater.New(a.group.GroupVersion.WithKind(listGVKs[0].Kind))
  210. if err != nil {
  211. return nil, err
  212. }
  213. versionedList = indirectArbitraryPointer(versionedListPtr)
  214. }
  215. versionedListOptions, err := a.group.Creater.New(optionsExternalVersion.WithKind("ListOptions"))
  216. if err != nil {
  217. return nil, err
  218. }
  219. var versionedDeleterObject interface{}
  220. switch {
  221. case isGracefulDeleter:
  222. objectPtr, err := a.group.Creater.New(optionsExternalVersion.WithKind("DeleteOptions"))
  223. if err != nil {
  224. return nil, err
  225. }
  226. versionedDeleterObject = indirectArbitraryPointer(objectPtr)
  227. isDeleter = true
  228. case isDeleter:
  229. gracefulDeleter = rest.GracefulDeleteAdapter{Deleter: deleter}
  230. }
  231. versionedStatusPtr, err := a.group.Creater.New(optionsExternalVersion.WithKind("Status"))
  232. if err != nil {
  233. return nil, err
  234. }
  235. versionedStatus := indirectArbitraryPointer(versionedStatusPtr)
  236. var (
  237. getOptions runtime.Object
  238. versionedGetOptions runtime.Object
  239. getOptionsInternalKind unversioned.GroupVersionKind
  240. getSubpath bool
  241. )
  242. if isGetterWithOptions {
  243. getOptions, getSubpath, _ = getterWithOptions.NewGetOptions()
  244. getOptionsInternalKinds, _, err := a.group.Typer.ObjectKinds(getOptions)
  245. if err != nil {
  246. return nil, err
  247. }
  248. getOptionsInternalKind = getOptionsInternalKinds[0]
  249. versionedGetOptions, err = a.group.Creater.New(optionsExternalVersion.WithKind(getOptionsInternalKind.Kind))
  250. if err != nil {
  251. return nil, err
  252. }
  253. isGetter = true
  254. }
  255. var versionedWatchEvent runtime.Object
  256. if isWatcher {
  257. versionedWatchEvent, err = a.group.Creater.New(a.group.GroupVersion.WithKind("WatchEvent"))
  258. if err != nil {
  259. return nil, err
  260. }
  261. }
  262. var (
  263. connectOptions runtime.Object
  264. versionedConnectOptions runtime.Object
  265. connectOptionsInternalKind unversioned.GroupVersionKind
  266. connectSubpath bool
  267. )
  268. if isConnecter {
  269. connectOptions, connectSubpath, _ = connecter.NewConnectOptions()
  270. if connectOptions != nil {
  271. connectOptionsInternalKinds, _, err := a.group.Typer.ObjectKinds(connectOptions)
  272. if err != nil {
  273. return nil, err
  274. }
  275. connectOptionsInternalKind = connectOptionsInternalKinds[0]
  276. versionedConnectOptions, err = a.group.Creater.New(optionsExternalVersion.WithKind(connectOptionsInternalKind.Kind))
  277. if err != nil {
  278. return nil, err
  279. }
  280. }
  281. }
  282. var ctxFn ContextFunc
  283. ctxFn = func(req *restful.Request) api.Context {
  284. if context == nil {
  285. return api.NewContext()
  286. }
  287. if ctx, ok := context.Get(req.Request); ok {
  288. return ctx
  289. }
  290. return api.NewContext()
  291. }
  292. allowWatchList := isWatcher && isLister // watching on lists is allowed only for kinds that support both watch and list.
  293. scope := mapping.Scope
  294. nameParam := ws.PathParameter("name", "name of the "+kind).DataType("string")
  295. pathParam := ws.PathParameter("path", "path to the resource").DataType("string")
  296. params := []*restful.Parameter{}
  297. actions := []action{}
  298. var resourceKind string
  299. kindProvider, ok := storage.(rest.KindProvider)
  300. if ok {
  301. resourceKind = kindProvider.Kind()
  302. } else {
  303. resourceKind = kind
  304. }
  305. var apiResource unversioned.APIResource
  306. // Get the list of actions for the given scope.
  307. switch scope.Name() {
  308. case meta.RESTScopeNameRoot:
  309. // Handle non-namespace scoped resources like nodes.
  310. resourcePath := resource
  311. resourceParams := params
  312. itemPath := resourcePath + "/{name}"
  313. nameParams := append(params, nameParam)
  314. proxyParams := append(nameParams, pathParam)
  315. if hasSubresource {
  316. itemPath = itemPath + "/" + subresource
  317. resourcePath = itemPath
  318. resourceParams = nameParams
  319. }
  320. apiResource.Name = path
  321. apiResource.Namespaced = false
  322. apiResource.Kind = resourceKind
  323. namer := rootScopeNaming{scope, a.group.Linker, gpath.Join(a.prefix, itemPath)}
  324. // Handler for standard REST verbs (GET, PUT, POST and DELETE).
  325. // Add actions at the resource path: /api/apiVersion/resource
  326. actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer}, isLister)
  327. actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer}, isCreater)
  328. actions = appendIf(actions, action{"DELETECOLLECTION", resourcePath, resourceParams, namer}, isCollectionDeleter)
  329. // DEPRECATED
  330. actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, resourceParams, namer}, allowWatchList)
  331. // Add actions at the item path: /api/apiVersion/resource/{name}
  332. actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter)
  333. if getSubpath {
  334. actions = appendIf(actions, action{"GET", itemPath + "/{path:*}", proxyParams, namer}, isGetter)
  335. }
  336. actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater)
  337. actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer}, isPatcher)
  338. actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter)
  339. actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer}, isWatcher)
  340. // We add "proxy" subresource to remove the need for the generic top level prefix proxy.
  341. // The generic top level prefix proxy is deprecated in v1.2, and will be removed in 1.3, or 1.4 at the latest.
  342. // TODO: DEPRECATED in v1.2.
  343. actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath + "/{path:*}", proxyParams, namer}, isRedirector)
  344. // TODO: DEPRECATED in v1.2.
  345. actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath, nameParams, namer}, isRedirector)
  346. actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer}, isConnecter)
  347. actions = appendIf(actions, action{"CONNECT", itemPath + "/{path:*}", proxyParams, namer}, isConnecter && connectSubpath)
  348. break
  349. case meta.RESTScopeNameNamespace:
  350. // Handler for standard REST verbs (GET, PUT, POST and DELETE).
  351. namespaceParam := ws.PathParameter(scope.ArgumentName(), scope.ParamDescription()).DataType("string")
  352. namespacedPath := scope.ParamName() + "/{" + scope.ArgumentName() + "}/" + resource
  353. namespaceParams := []*restful.Parameter{namespaceParam}
  354. resourcePath := namespacedPath
  355. resourceParams := namespaceParams
  356. itemPathPrefix := gpath.Join(a.prefix, scope.ParamName()) + "/"
  357. itemPath := namespacedPath + "/{name}"
  358. itemPathMiddle := "/" + resource + "/"
  359. nameParams := append(namespaceParams, nameParam)
  360. proxyParams := append(nameParams, pathParam)
  361. itemPathSuffix := ""
  362. if hasSubresource {
  363. itemPathSuffix = "/" + subresource
  364. itemPath = itemPath + itemPathSuffix
  365. resourcePath = itemPath
  366. resourceParams = nameParams
  367. }
  368. apiResource.Name = path
  369. apiResource.Namespaced = true
  370. apiResource.Kind = resourceKind
  371. itemPathFn := func(name, namespace string) string {
  372. return itemPathPrefix + namespace + itemPathMiddle + name + itemPathSuffix
  373. }
  374. namer := scopeNaming{scope, a.group.Linker, itemPathFn, false}
  375. actions = appendIf(actions, action{"LIST", resourcePath, resourceParams, namer}, isLister)
  376. actions = appendIf(actions, action{"POST", resourcePath, resourceParams, namer}, isCreater)
  377. actions = appendIf(actions, action{"DELETECOLLECTION", resourcePath, resourceParams, namer}, isCollectionDeleter)
  378. // DEPRECATED
  379. actions = appendIf(actions, action{"WATCHLIST", "watch/" + resourcePath, resourceParams, namer}, allowWatchList)
  380. actions = appendIf(actions, action{"GET", itemPath, nameParams, namer}, isGetter)
  381. if getSubpath {
  382. actions = appendIf(actions, action{"GET", itemPath + "/{path:*}", proxyParams, namer}, isGetter)
  383. }
  384. actions = appendIf(actions, action{"PUT", itemPath, nameParams, namer}, isUpdater)
  385. actions = appendIf(actions, action{"PATCH", itemPath, nameParams, namer}, isPatcher)
  386. actions = appendIf(actions, action{"DELETE", itemPath, nameParams, namer}, isDeleter)
  387. actions = appendIf(actions, action{"WATCH", "watch/" + itemPath, nameParams, namer}, isWatcher)
  388. // We add "proxy" subresource to remove the need for the generic top level prefix proxy.
  389. // The generic top level prefix proxy is deprecated in v1.2, and will be removed in 1.3, or 1.4 at the latest.
  390. // TODO: DEPRECATED in v1.2.
  391. actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath + "/{path:*}", proxyParams, namer}, isRedirector)
  392. // TODO: DEPRECATED in v1.2.
  393. actions = appendIf(actions, action{"PROXY", "proxy/" + itemPath, nameParams, namer}, isRedirector)
  394. actions = appendIf(actions, action{"CONNECT", itemPath, nameParams, namer}, isConnecter)
  395. actions = appendIf(actions, action{"CONNECT", itemPath + "/{path:*}", proxyParams, namer}, isConnecter && connectSubpath)
  396. // list or post across namespace.
  397. // For ex: LIST all pods in all namespaces by sending a LIST request at /api/apiVersion/pods.
  398. // TODO: more strongly type whether a resource allows these actions on "all namespaces" (bulk delete)
  399. if !hasSubresource {
  400. namer = scopeNaming{scope, a.group.Linker, itemPathFn, true}
  401. actions = appendIf(actions, action{"LIST", resource, params, namer}, isLister)
  402. actions = appendIf(actions, action{"WATCHLIST", "watch/" + resource, params, namer}, allowWatchList)
  403. }
  404. break
  405. default:
  406. return nil, fmt.Errorf("unsupported restscope: %s", scope.Name())
  407. }
  408. // Create Routes for the actions.
  409. // TODO: Add status documentation using Returns()
  410. // Errors (see api/errors/errors.go as well as go-restful router):
  411. // http.StatusNotFound, http.StatusMethodNotAllowed,
  412. // http.StatusUnsupportedMediaType, http.StatusNotAcceptable,
  413. // http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden,
  414. // http.StatusRequestTimeout, http.StatusConflict, http.StatusPreconditionFailed,
  415. // 422 (StatusUnprocessableEntity), http.StatusInternalServerError,
  416. // http.StatusServiceUnavailable
  417. // and api error codes
  418. // Note that if we specify a versioned Status object here, we may need to
  419. // create one for the tests, also
  420. // Success:
  421. // http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent
  422. //
  423. // test/integration/auth_test.go is currently the most comprehensive status code test
  424. reqScope := RequestScope{
  425. ContextFunc: ctxFn,
  426. Serializer: a.group.Serializer,
  427. ParameterCodec: a.group.ParameterCodec,
  428. Creater: a.group.Creater,
  429. Convertor: a.group.Convertor,
  430. Copier: a.group.Copier,
  431. // TODO: This seems wrong for cross-group subresources. It makes an assumption that a subresource and its parent are in the same group version. Revisit this.
  432. Resource: a.group.GroupVersion.WithResource(resource),
  433. Subresource: subresource,
  434. Kind: fqKindToRegister,
  435. }
  436. for _, action := range actions {
  437. reqScope.Namer = action.Namer
  438. namespaced := ""
  439. if apiResource.Namespaced {
  440. namespaced = "Namespaced"
  441. }
  442. switch action.Verb {
  443. case "GET": // Get a resource.
  444. var handler restful.RouteFunction
  445. if isGetterWithOptions {
  446. handler = GetResourceWithOptions(getterWithOptions, reqScope)
  447. } else {
  448. handler = GetResource(getter, exporter, reqScope)
  449. }
  450. handler = metrics.InstrumentRouteFunc(action.Verb, resource, handler)
  451. doc := "read the specified " + kind
  452. if hasSubresource {
  453. doc = "read " + subresource + " of the specified " + kind
  454. }
  455. route := ws.GET(action.Path).To(handler).
  456. Doc(doc).
  457. Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
  458. Operation("read"+namespaced+kind+strings.Title(subresource)).
  459. Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
  460. Returns(http.StatusOK, "OK", versionedObject).
  461. Writes(versionedObject)
  462. if isGetterWithOptions {
  463. if err := addObjectParams(ws, route, versionedGetOptions); err != nil {
  464. return nil, err
  465. }
  466. }
  467. if isExporter {
  468. if err := addObjectParams(ws, route, versionedExportOptions); err != nil {
  469. return nil, err
  470. }
  471. }
  472. addParams(route, action.Params)
  473. ws.Route(route)
  474. case "LIST": // List all resources of a kind.
  475. doc := "list objects of kind " + kind
  476. if hasSubresource {
  477. doc = "list " + subresource + " of objects of kind " + kind
  478. }
  479. handler := metrics.InstrumentRouteFunc(action.Verb, resource, ListResource(lister, watcher, reqScope, false, a.minRequestTimeout))
  480. route := ws.GET(action.Path).To(handler).
  481. Doc(doc).
  482. Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
  483. Operation("list"+namespaced+kind+strings.Title(subresource)).
  484. Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
  485. Returns(http.StatusOK, "OK", versionedList).
  486. Writes(versionedList)
  487. if err := addObjectParams(ws, route, versionedListOptions); err != nil {
  488. return nil, err
  489. }
  490. switch {
  491. case isLister && isWatcher:
  492. doc := "list or watch objects of kind " + kind
  493. if hasSubresource {
  494. doc = "list or watch " + subresource + " of objects of kind " + kind
  495. }
  496. route.Doc(doc)
  497. case isWatcher:
  498. doc := "watch objects of kind " + kind
  499. if hasSubresource {
  500. doc = "watch " + subresource + "of objects of kind " + kind
  501. }
  502. route.Doc(doc)
  503. }
  504. addParams(route, action.Params)
  505. ws.Route(route)
  506. case "PUT": // Update a resource.
  507. doc := "replace the specified " + kind
  508. if hasSubresource {
  509. doc = "replace " + subresource + " of the specified " + kind
  510. }
  511. handler := metrics.InstrumentRouteFunc(action.Verb, resource, UpdateResource(updater, reqScope, a.group.Typer, admit))
  512. route := ws.PUT(action.Path).To(handler).
  513. Doc(doc).
  514. Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
  515. Operation("replace"+namespaced+kind+strings.Title(subresource)).
  516. Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
  517. Returns(http.StatusOK, "OK", versionedObject).
  518. Reads(versionedObject).
  519. Writes(versionedObject)
  520. addParams(route, action.Params)
  521. ws.Route(route)
  522. case "PATCH": // Partially update a resource
  523. doc := "partially update the specified " + kind
  524. if hasSubresource {
  525. doc = "partially update " + subresource + " of the specified " + kind
  526. }
  527. handler := metrics.InstrumentRouteFunc(action.Verb, resource, PatchResource(patcher, reqScope, a.group.Typer, admit, mapping.ObjectConvertor))
  528. route := ws.PATCH(action.Path).To(handler).
  529. Doc(doc).
  530. Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
  531. Consumes(string(api.JSONPatchType), string(api.MergePatchType), string(api.StrategicMergePatchType)).
  532. Operation("patch"+namespaced+kind+strings.Title(subresource)).
  533. Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
  534. Returns(http.StatusOK, "OK", versionedObject).
  535. Reads(unversioned.Patch{}).
  536. Writes(versionedObject)
  537. addParams(route, action.Params)
  538. ws.Route(route)
  539. case "POST": // Create a resource.
  540. var handler restful.RouteFunction
  541. if isNamedCreater {
  542. handler = CreateNamedResource(namedCreater, reqScope, a.group.Typer, admit)
  543. } else {
  544. handler = CreateResource(creater, reqScope, a.group.Typer, admit)
  545. }
  546. handler = metrics.InstrumentRouteFunc(action.Verb, resource, handler)
  547. doc := "create a " + kind
  548. if hasSubresource {
  549. doc = "create " + subresource + " of a " + kind
  550. }
  551. route := ws.POST(action.Path).To(handler).
  552. Doc(doc).
  553. Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
  554. Operation("create"+namespaced+kind+strings.Title(subresource)).
  555. Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
  556. Returns(http.StatusOK, "OK", versionedObject).
  557. Reads(versionedObject).
  558. Writes(versionedObject)
  559. addParams(route, action.Params)
  560. ws.Route(route)
  561. case "DELETE": // Delete a resource.
  562. doc := "delete a " + kind
  563. if hasSubresource {
  564. doc = "delete " + subresource + " of a " + kind
  565. }
  566. handler := metrics.InstrumentRouteFunc(action.Verb, resource, DeleteResource(gracefulDeleter, isGracefulDeleter, reqScope, admit))
  567. route := ws.DELETE(action.Path).To(handler).
  568. Doc(doc).
  569. Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
  570. Operation("delete"+namespaced+kind+strings.Title(subresource)).
  571. Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
  572. Writes(versionedStatus).
  573. Returns(http.StatusOK, "OK", versionedStatus)
  574. if isGracefulDeleter {
  575. route.Reads(versionedDeleterObject)
  576. }
  577. addParams(route, action.Params)
  578. ws.Route(route)
  579. case "DELETECOLLECTION":
  580. doc := "delete collection of " + kind
  581. if hasSubresource {
  582. doc = "delete collection of " + subresource + " of a " + kind
  583. }
  584. handler := metrics.InstrumentRouteFunc(action.Verb, resource, DeleteCollection(collectionDeleter, isCollectionDeleter, reqScope, admit))
  585. route := ws.DELETE(action.Path).To(handler).
  586. Doc(doc).
  587. Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
  588. Operation("deletecollection"+namespaced+kind+strings.Title(subresource)).
  589. Produces(append(storageMeta.ProducesMIMETypes(action.Verb), a.group.Serializer.SupportedMediaTypes()...)...).
  590. Writes(versionedStatus).
  591. Returns(http.StatusOK, "OK", versionedStatus)
  592. if err := addObjectParams(ws, route, versionedListOptions); err != nil {
  593. return nil, err
  594. }
  595. addParams(route, action.Params)
  596. ws.Route(route)
  597. // TODO: deprecated
  598. case "WATCH": // Watch a resource.
  599. doc := "watch changes to an object of kind " + kind
  600. if hasSubresource {
  601. doc = "watch changes to " + subresource + " of an object of kind " + kind
  602. }
  603. handler := metrics.InstrumentRouteFunc(action.Verb, resource, ListResource(lister, watcher, reqScope, true, a.minRequestTimeout))
  604. route := ws.GET(action.Path).To(handler).
  605. Doc(doc).
  606. Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
  607. Operation("watch"+namespaced+kind+strings.Title(subresource)).
  608. Produces(a.group.Serializer.SupportedStreamingMediaTypes()...).
  609. Returns(http.StatusOK, "OK", versionedWatchEvent).
  610. Writes(versionedWatchEvent)
  611. if err := addObjectParams(ws, route, versionedListOptions); err != nil {
  612. return nil, err
  613. }
  614. addParams(route, action.Params)
  615. ws.Route(route)
  616. // TODO: deprecated
  617. case "WATCHLIST": // Watch all resources of a kind.
  618. doc := "watch individual changes to a list of " + kind
  619. if hasSubresource {
  620. doc = "watch individual changes to a list of " + subresource + " of " + kind
  621. }
  622. handler := metrics.InstrumentRouteFunc(action.Verb, resource, ListResource(lister, watcher, reqScope, true, a.minRequestTimeout))
  623. route := ws.GET(action.Path).To(handler).
  624. Doc(doc).
  625. Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
  626. Operation("watch"+namespaced+kind+strings.Title(subresource)+"List").
  627. Produces(a.group.Serializer.SupportedStreamingMediaTypes()...).
  628. Returns(http.StatusOK, "OK", versionedWatchEvent).
  629. Writes(versionedWatchEvent)
  630. if err := addObjectParams(ws, route, versionedListOptions); err != nil {
  631. return nil, err
  632. }
  633. addParams(route, action.Params)
  634. ws.Route(route)
  635. // We add "proxy" subresource to remove the need for the generic top level prefix proxy.
  636. // The generic top level prefix proxy is deprecated in v1.2, and will be removed in 1.3, or 1.4 at the latest.
  637. // TODO: DEPRECATED in v1.2.
  638. case "PROXY": // Proxy requests to a resource.
  639. // Accept all methods as per http://issue.k8s.io/3996
  640. addProxyRoute(ws, "GET", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params)
  641. addProxyRoute(ws, "PUT", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params)
  642. addProxyRoute(ws, "POST", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params)
  643. addProxyRoute(ws, "DELETE", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params)
  644. addProxyRoute(ws, "HEAD", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params)
  645. addProxyRoute(ws, "OPTIONS", a.prefix, action.Path, proxyHandler, namespaced, kind, resource, subresource, hasSubresource, action.Params)
  646. case "CONNECT":
  647. for _, method := range connecter.ConnectMethods() {
  648. doc := "connect " + method + " requests to " + kind
  649. if hasSubresource {
  650. doc = "connect " + method + " requests to " + subresource + " of " + kind
  651. }
  652. handler := metrics.InstrumentRouteFunc(action.Verb, resource, ConnectResource(connecter, reqScope, admit, path))
  653. route := ws.Method(method).Path(action.Path).
  654. To(handler).
  655. Doc(doc).
  656. Operation("connect" + strings.Title(strings.ToLower(method)) + namespaced + kind + strings.Title(subresource)).
  657. Produces("*/*").
  658. Consumes("*/*").
  659. Writes("string")
  660. if versionedConnectOptions != nil {
  661. if err := addObjectParams(ws, route, versionedConnectOptions); err != nil {
  662. return nil, err
  663. }
  664. }
  665. addParams(route, action.Params)
  666. ws.Route(route)
  667. }
  668. default:
  669. return nil, fmt.Errorf("unrecognized action verb: %s", action.Verb)
  670. }
  671. // Note: update GetAttribs() when adding a custom handler.
  672. }
  673. return &apiResource, nil
  674. }
  675. // rootScopeNaming reads only names from a request and ignores namespaces. It implements ScopeNamer
  676. // for root scoped resources.
  677. type rootScopeNaming struct {
  678. scope meta.RESTScope
  679. runtime.SelfLinker
  680. itemPath string
  681. }
  682. // rootScopeNaming implements ScopeNamer
  683. var _ ScopeNamer = rootScopeNaming{}
  684. // Namespace returns an empty string because root scoped objects have no namespace.
  685. func (n rootScopeNaming) Namespace(req *restful.Request) (namespace string, err error) {
  686. return "", nil
  687. }
  688. // Name returns the name from the path and an empty string for namespace, or an error if the
  689. // name is empty.
  690. func (n rootScopeNaming) Name(req *restful.Request) (namespace, name string, err error) {
  691. name = req.PathParameter("name")
  692. if len(name) == 0 {
  693. return "", "", errEmptyName
  694. }
  695. return "", name, nil
  696. }
  697. // GenerateLink returns the appropriate path and query to locate an object by its canonical path.
  698. func (n rootScopeNaming) GenerateLink(req *restful.Request, obj runtime.Object) (path, query string, err error) {
  699. _, name, err := n.ObjectName(obj)
  700. if err != nil {
  701. return "", "", err
  702. }
  703. if len(name) == 0 {
  704. _, name, err = n.Name(req)
  705. if err != nil {
  706. return "", "", err
  707. }
  708. }
  709. path = strings.Replace(n.itemPath, "{name}", name, 1)
  710. return path, "", nil
  711. }
  712. // GenerateListLink returns the appropriate path and query to locate a list by its canonical path.
  713. func (n rootScopeNaming) GenerateListLink(req *restful.Request) (path, query string, err error) {
  714. path = req.Request.URL.Path
  715. return path, "", nil
  716. }
  717. // ObjectName returns the name set on the object, or an error if the
  718. // name cannot be returned. Namespace is empty
  719. // TODO: distinguish between objects with name/namespace and without via a specific error.
  720. func (n rootScopeNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) {
  721. name, err = n.SelfLinker.Name(obj)
  722. if err != nil {
  723. return "", "", err
  724. }
  725. if len(name) == 0 {
  726. return "", "", errEmptyName
  727. }
  728. return "", name, nil
  729. }
  730. // scopeNaming returns naming information from a request. It implements ScopeNamer for
  731. // namespace scoped resources.
  732. type scopeNaming struct {
  733. scope meta.RESTScope
  734. runtime.SelfLinker
  735. itemPathFn func(name, namespace string) string
  736. allNamespaces bool
  737. }
  738. // scopeNaming implements ScopeNamer
  739. var _ ScopeNamer = scopeNaming{}
  740. // Namespace returns the namespace from the path or the default.
  741. func (n scopeNaming) Namespace(req *restful.Request) (namespace string, err error) {
  742. if n.allNamespaces {
  743. return "", nil
  744. }
  745. namespace = req.PathParameter(n.scope.ArgumentName())
  746. if len(namespace) == 0 {
  747. // a URL was constructed without the namespace, or this method was invoked
  748. // on an object without a namespace path parameter.
  749. return "", fmt.Errorf("no namespace parameter found on request")
  750. }
  751. return namespace, nil
  752. }
  753. // Name returns the name from the path, the namespace (or default), or an error if the
  754. // name is empty.
  755. func (n scopeNaming) Name(req *restful.Request) (namespace, name string, err error) {
  756. namespace, _ = n.Namespace(req)
  757. name = req.PathParameter("name")
  758. if len(name) == 0 {
  759. return "", "", errEmptyName
  760. }
  761. return
  762. }
  763. // GenerateLink returns the appropriate path and query to locate an object by its canonical path.
  764. func (n scopeNaming) GenerateLink(req *restful.Request, obj runtime.Object) (path, query string, err error) {
  765. namespace, name, err := n.ObjectName(obj)
  766. if err != nil {
  767. return "", "", err
  768. }
  769. if len(namespace) == 0 && len(name) == 0 {
  770. namespace, name, err = n.Name(req)
  771. if err != nil {
  772. return "", "", err
  773. }
  774. }
  775. if len(name) == 0 {
  776. return "", "", errEmptyName
  777. }
  778. return n.itemPathFn(name, namespace), "", nil
  779. }
  780. // GenerateListLink returns the appropriate path and query to locate a list by its canonical path.
  781. func (n scopeNaming) GenerateListLink(req *restful.Request) (path, query string, err error) {
  782. path = req.Request.URL.Path
  783. return path, "", nil
  784. }
  785. // ObjectName returns the name and namespace set on the object, or an error if the
  786. // name cannot be returned.
  787. // TODO: distinguish between objects with name/namespace and without via a specific error.
  788. func (n scopeNaming) ObjectName(obj runtime.Object) (namespace, name string, err error) {
  789. name, err = n.SelfLinker.Name(obj)
  790. if err != nil {
  791. return "", "", err
  792. }
  793. namespace, err = n.SelfLinker.Namespace(obj)
  794. if err != nil {
  795. return "", "", err
  796. }
  797. return namespace, name, err
  798. }
  799. // This magic incantation returns *ptrToObject for an arbitrary pointer
  800. func indirectArbitraryPointer(ptrToObject interface{}) interface{} {
  801. return reflect.Indirect(reflect.ValueOf(ptrToObject)).Interface()
  802. }
  803. func appendIf(actions []action, a action, shouldAppend bool) []action {
  804. if shouldAppend {
  805. actions = append(actions, a)
  806. }
  807. return actions
  808. }
  809. // Wraps a http.Handler function inside a restful.RouteFunction
  810. func routeFunction(handler http.Handler) restful.RouteFunction {
  811. return func(restReq *restful.Request, restResp *restful.Response) {
  812. handler.ServeHTTP(restResp.ResponseWriter, restReq.Request)
  813. }
  814. }
  815. func addProxyRoute(ws *restful.WebService, method string, prefix string, path string, proxyHandler http.Handler, namespaced, kind, resource, subresource string, hasSubresource bool, params []*restful.Parameter) {
  816. doc := "proxy " + method + " requests to " + kind
  817. if hasSubresource {
  818. doc = "proxy " + method + " requests to " + subresource + " of " + kind
  819. }
  820. handler := metrics.InstrumentRouteFunc("PROXY", resource, routeFunction(proxyHandler))
  821. proxyRoute := ws.Method(method).Path(path).To(handler).
  822. Doc(doc).
  823. Operation("proxy" + strings.Title(method) + namespaced + kind + strings.Title(subresource)).
  824. Produces("*/*").
  825. Consumes("*/*").
  826. Writes("string")
  827. addParams(proxyRoute, params)
  828. ws.Route(proxyRoute)
  829. }
  830. func addParams(route *restful.RouteBuilder, params []*restful.Parameter) {
  831. for _, param := range params {
  832. route.Param(param)
  833. }
  834. }
  835. // addObjectParams converts a runtime.Object into a set of go-restful Param() definitions on the route.
  836. // The object must be a pointer to a struct; only fields at the top level of the struct that are not
  837. // themselves interfaces or structs are used; only fields with a json tag that is non empty (the standard
  838. // Go JSON behavior for omitting a field) become query parameters. The name of the query parameter is
  839. // the JSON field name. If a description struct tag is set on the field, that description is used on the
  840. // query parameter. In essence, it converts a standard JSON top level object into a query param schema.
  841. func addObjectParams(ws *restful.WebService, route *restful.RouteBuilder, obj interface{}) error {
  842. sv, err := conversion.EnforcePtr(obj)
  843. if err != nil {
  844. return err
  845. }
  846. st := sv.Type()
  847. switch st.Kind() {
  848. case reflect.Struct:
  849. for i := 0; i < st.NumField(); i++ {
  850. name := st.Field(i).Name
  851. sf, ok := st.FieldByName(name)
  852. if !ok {
  853. continue
  854. }
  855. switch sf.Type.Kind() {
  856. case reflect.Interface, reflect.Struct:
  857. default:
  858. jsonTag := sf.Tag.Get("json")
  859. if len(jsonTag) == 0 {
  860. continue
  861. }
  862. jsonName := strings.SplitN(jsonTag, ",", 2)[0]
  863. if len(jsonName) == 0 {
  864. continue
  865. }
  866. var desc string
  867. if docable, ok := obj.(documentable); ok {
  868. desc = docable.SwaggerDoc()[jsonName]
  869. }
  870. route.Param(ws.QueryParameter(jsonName, desc).DataType(typeToJSON(sf.Type.String())))
  871. }
  872. }
  873. }
  874. return nil
  875. }
  876. // TODO: this is incomplete, expand as needed.
  877. // Convert the name of a golang type to the name of a JSON type
  878. func typeToJSON(typeName string) string {
  879. switch typeName {
  880. case "bool", "*bool":
  881. return "boolean"
  882. case "uint8", "*uint8", "int", "*int", "int32", "*int32", "int64", "*int64", "uint32", "*uint32", "uint64", "*uint64":
  883. return "integer"
  884. case "float64", "*float64", "float32", "*float32":
  885. return "number"
  886. case "unversioned.Time", "*unversioned.Time":
  887. return "string"
  888. case "byte", "*byte":
  889. return "string"
  890. case "[]string", "[]*string":
  891. // TODO: Fix this when go-restful supports a way to specify an array query param:
  892. // https://github.com/emicklei/go-restful/issues/225
  893. return "string"
  894. default:
  895. return typeName
  896. }
  897. }
  898. // defaultStorageMetadata provides default answers to rest.StorageMetadata.
  899. type defaultStorageMetadata struct{}
  900. // defaultStorageMetadata implements rest.StorageMetadata
  901. var _ rest.StorageMetadata = defaultStorageMetadata{}
  902. func (defaultStorageMetadata) ProducesMIMETypes(verb string) []string {
  903. return nil
  904. }
  905. // splitSubresource checks if the given storage path is the path of a subresource and returns
  906. // the resource and subresource components.
  907. func splitSubresource(path string) (string, string, error) {
  908. var resource, subresource string
  909. switch parts := strings.Split(path, "/"); len(parts) {
  910. case 2:
  911. resource, subresource = parts[0], parts[1]
  912. case 1:
  913. resource = parts[0]
  914. default:
  915. // TODO: support deeper paths
  916. return "", "", fmt.Errorf("api_installer allows only one or two segment paths (resource or resource/subresource)")
  917. }
  918. return resource, subresource, nil
  919. }