handlers.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. /*
  2. Copyright 2014 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. "bufio"
  16. "encoding/json"
  17. "fmt"
  18. "net"
  19. "net/http"
  20. "regexp"
  21. "runtime/debug"
  22. "strings"
  23. "sync"
  24. "time"
  25. "github.com/golang/glog"
  26. "k8s.io/kubernetes/pkg/api"
  27. "k8s.io/kubernetes/pkg/api/errors"
  28. "k8s.io/kubernetes/pkg/auth/authorizer"
  29. "k8s.io/kubernetes/pkg/httplog"
  30. "k8s.io/kubernetes/pkg/util/runtime"
  31. "k8s.io/kubernetes/pkg/util/sets"
  32. )
  33. // specialVerbs contains just strings which are used in REST paths for special actions that don't fall under the normal
  34. // CRUDdy GET/POST/PUT/DELETE actions on REST objects.
  35. // TODO: find a way to keep this up to date automatically. Maybe dynamically populate list as handlers added to
  36. // master's Mux.
  37. var specialVerbs = sets.NewString("proxy", "redirect", "watch")
  38. // specialVerbsNoSubresources contains root verbs which do not allow subresources
  39. var specialVerbsNoSubresources = sets.NewString("proxy", "redirect")
  40. // namespaceSubresources contains subresources of namespace
  41. // this list allows the parser to distinguish between a namespace subresource, and a namespaced resource
  42. var namespaceSubresources = sets.NewString("status", "finalize")
  43. // NamespaceSubResourcesForTest exports namespaceSubresources for testing in pkg/master/master_test.go, so we never drift
  44. var NamespaceSubResourcesForTest = sets.NewString(namespaceSubresources.List()...)
  45. // Constant for the retry-after interval on rate limiting.
  46. // TODO: maybe make this dynamic? or user-adjustable?
  47. const RetryAfter = "1"
  48. // IsReadOnlyReq() is true for any (or at least many) request which has no observable
  49. // side effects on state of apiserver (though there may be internal side effects like
  50. // caching and logging).
  51. func IsReadOnlyReq(req http.Request) bool {
  52. if req.Method == "GET" {
  53. // TODO: add OPTIONS and HEAD if we ever support those.
  54. return true
  55. }
  56. return false
  57. }
  58. // ReadOnly passes all GET requests on to handler, and returns an error on all other requests.
  59. func ReadOnly(handler http.Handler) http.Handler {
  60. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  61. if IsReadOnlyReq(*req) {
  62. handler.ServeHTTP(w, req)
  63. return
  64. }
  65. w.WriteHeader(http.StatusForbidden)
  66. fmt.Fprintf(w, "This is a read-only endpoint.")
  67. })
  68. }
  69. type LongRunningRequestCheck func(r *http.Request) bool
  70. // BasicLongRunningRequestCheck pathRegex operates against the url path, the queryParams match is case insensitive.
  71. // Any one match flags the request.
  72. // TODO tighten this check to eliminate the abuse potential by malicious clients that start setting queryParameters
  73. // to bypass the rate limitter. This could be done using a full parse and special casing the bits we need.
  74. func BasicLongRunningRequestCheck(pathRegex *regexp.Regexp, queryParams map[string]string) LongRunningRequestCheck {
  75. return func(r *http.Request) bool {
  76. if pathRegex.MatchString(r.URL.Path) {
  77. return true
  78. }
  79. for key, expectedValue := range queryParams {
  80. if strings.ToLower(expectedValue) == strings.ToLower(r.URL.Query().Get(key)) {
  81. return true
  82. }
  83. }
  84. return false
  85. }
  86. }
  87. // MaxInFlight limits the number of in-flight requests to buffer size of the passed in channel.
  88. func MaxInFlightLimit(c chan bool, longRunningRequestCheck LongRunningRequestCheck, handler http.Handler) http.Handler {
  89. if c == nil {
  90. return handler
  91. }
  92. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  93. if longRunningRequestCheck(r) {
  94. // Skip tracking long running events.
  95. handler.ServeHTTP(w, r)
  96. return
  97. }
  98. select {
  99. case c <- true:
  100. defer func() { <-c }()
  101. handler.ServeHTTP(w, r)
  102. default:
  103. tooManyRequests(r, w)
  104. }
  105. })
  106. }
  107. func tooManyRequests(req *http.Request, w http.ResponseWriter) {
  108. // "Too Many Requests" response is returned before logger is setup for the request.
  109. // So we need to explicitly log it here.
  110. defer httplog.NewLogged(req, &w).Log()
  111. // Return a 429 status indicating "Too Many Requests"
  112. w.Header().Set("Retry-After", RetryAfter)
  113. http.Error(w, "Too many requests, please try again later.", errors.StatusTooManyRequests)
  114. }
  115. // RecoverPanics wraps an http Handler to recover and log panics.
  116. func RecoverPanics(handler http.Handler) http.Handler {
  117. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  118. defer runtime.HandleCrash(func(err interface{}) {
  119. http.Error(w, "This request caused apisever to panic. Look in log for details.", http.StatusInternalServerError)
  120. glog.Errorf("APIServer panic'd on %v %v: %v\n%s\n", req.Method, req.RequestURI, err, debug.Stack())
  121. })
  122. defer httplog.NewLogged(req, &w).StacktraceWhen(
  123. httplog.StatusIsNot(
  124. http.StatusOK,
  125. http.StatusCreated,
  126. http.StatusAccepted,
  127. http.StatusBadRequest,
  128. http.StatusMovedPermanently,
  129. http.StatusTemporaryRedirect,
  130. http.StatusConflict,
  131. http.StatusNotFound,
  132. http.StatusUnauthorized,
  133. http.StatusForbidden,
  134. errors.StatusUnprocessableEntity,
  135. http.StatusSwitchingProtocols,
  136. ),
  137. ).Log()
  138. // Dispatch to the internal handler
  139. handler.ServeHTTP(w, req)
  140. })
  141. }
  142. var errConnKilled = fmt.Errorf("kill connection/stream")
  143. // TimeoutHandler returns an http.Handler that runs h with a timeout
  144. // determined by timeoutFunc. The new http.Handler calls h.ServeHTTP to handle
  145. // each request, but if a call runs for longer than its time limit, the
  146. // handler responds with a 503 Service Unavailable error and the message
  147. // provided. (If msg is empty, a suitable default message will be sent.) After
  148. // the handler times out, writes by h to its http.ResponseWriter will return
  149. // http.ErrHandlerTimeout. If timeoutFunc returns a nil timeout channel, no
  150. // timeout will be enforced.
  151. func TimeoutHandler(h http.Handler, timeoutFunc func(*http.Request) (timeout <-chan time.Time, msg string)) http.Handler {
  152. return &timeoutHandler{h, timeoutFunc}
  153. }
  154. type timeoutHandler struct {
  155. handler http.Handler
  156. timeout func(*http.Request) (<-chan time.Time, string)
  157. }
  158. func (t *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  159. after, msg := t.timeout(r)
  160. if after == nil {
  161. t.handler.ServeHTTP(w, r)
  162. return
  163. }
  164. done := make(chan struct{})
  165. tw := newTimeoutWriter(w)
  166. go func() {
  167. t.handler.ServeHTTP(tw, r)
  168. close(done)
  169. }()
  170. select {
  171. case <-done:
  172. return
  173. case <-after:
  174. tw.timeout(msg)
  175. }
  176. }
  177. type timeoutWriter interface {
  178. http.ResponseWriter
  179. timeout(string)
  180. }
  181. func newTimeoutWriter(w http.ResponseWriter) timeoutWriter {
  182. base := &baseTimeoutWriter{w: w}
  183. _, notifiable := w.(http.CloseNotifier)
  184. _, hijackable := w.(http.Hijacker)
  185. switch {
  186. case notifiable && hijackable:
  187. return &closeHijackTimeoutWriter{base}
  188. case notifiable:
  189. return &closeTimeoutWriter{base}
  190. case hijackable:
  191. return &hijackTimeoutWriter{base}
  192. default:
  193. return base
  194. }
  195. }
  196. type baseTimeoutWriter struct {
  197. w http.ResponseWriter
  198. mu sync.Mutex
  199. // if the timeout handler has timedout
  200. timedOut bool
  201. // if this timeout writer has wrote header
  202. wroteHeader bool
  203. // if this timeout writer has been hijacked
  204. hijacked bool
  205. }
  206. func (tw *baseTimeoutWriter) Header() http.Header {
  207. tw.mu.Lock()
  208. defer tw.mu.Unlock()
  209. if tw.timedOut {
  210. return http.Header{}
  211. }
  212. return tw.w.Header()
  213. }
  214. func (tw *baseTimeoutWriter) Write(p []byte) (int, error) {
  215. tw.mu.Lock()
  216. defer tw.mu.Unlock()
  217. if tw.timedOut {
  218. return 0, http.ErrHandlerTimeout
  219. }
  220. if tw.hijacked {
  221. return 0, http.ErrHijacked
  222. }
  223. tw.wroteHeader = true
  224. return tw.w.Write(p)
  225. }
  226. func (tw *baseTimeoutWriter) Flush() {
  227. tw.mu.Lock()
  228. defer tw.mu.Unlock()
  229. if tw.timedOut {
  230. return
  231. }
  232. if flusher, ok := tw.w.(http.Flusher); ok {
  233. flusher.Flush()
  234. }
  235. }
  236. func (tw *baseTimeoutWriter) WriteHeader(code int) {
  237. tw.mu.Lock()
  238. defer tw.mu.Unlock()
  239. if tw.timedOut || tw.wroteHeader || tw.hijacked {
  240. return
  241. }
  242. tw.wroteHeader = true
  243. tw.w.WriteHeader(code)
  244. }
  245. func (tw *baseTimeoutWriter) timeout(msg string) {
  246. tw.mu.Lock()
  247. defer tw.mu.Unlock()
  248. tw.timedOut = true
  249. // The timeout writer has not been used by the inner handler.
  250. // We can safely timeout the HTTP request by sending by a timeout
  251. // handler
  252. if !tw.wroteHeader && !tw.hijacked {
  253. tw.w.WriteHeader(http.StatusGatewayTimeout)
  254. if msg != "" {
  255. tw.w.Write([]byte(msg))
  256. } else {
  257. enc := json.NewEncoder(tw.w)
  258. enc.Encode(errors.NewServerTimeout(api.Resource(""), "", 0))
  259. }
  260. } else {
  261. // The timeout writer has been used by the inner handler. There is
  262. // no way to timeout the HTTP request at the point. We have to shutdown
  263. // the connection for HTTP1 or reset stream for HTTP2.
  264. //
  265. // Note from: Brad Fitzpatrick
  266. // if the ServeHTTP goroutine panics, that will do the best possible thing for both
  267. // HTTP/1 and HTTP/2. In HTTP/1, assuming you're replying with at least HTTP/1.1 and
  268. // you've already flushed the headers so it's using HTTP chunking, it'll kill the TCP
  269. // connection immediately without a proper 0-byte EOF chunk, so the peer will recognize
  270. // the response as bogus. In HTTP/2 the server will just RST_STREAM the stream, leaving
  271. // the TCP connection open, but resetting the stream to the peer so it'll have an error,
  272. // like the HTTP/1 case.
  273. panic(errConnKilled)
  274. }
  275. }
  276. func (tw *baseTimeoutWriter) closeNotify() <-chan bool {
  277. tw.mu.Lock()
  278. defer tw.mu.Unlock()
  279. if tw.timedOut {
  280. done := make(chan bool)
  281. close(done)
  282. return done
  283. }
  284. return tw.w.(http.CloseNotifier).CloseNotify()
  285. }
  286. func (tw *baseTimeoutWriter) hijack() (net.Conn, *bufio.ReadWriter, error) {
  287. tw.mu.Lock()
  288. defer tw.mu.Unlock()
  289. if tw.timedOut {
  290. return nil, nil, http.ErrHandlerTimeout
  291. }
  292. conn, rw, err := tw.w.(http.Hijacker).Hijack()
  293. if err == nil {
  294. tw.hijacked = true
  295. }
  296. return conn, rw, err
  297. }
  298. type closeTimeoutWriter struct {
  299. *baseTimeoutWriter
  300. }
  301. func (tw *closeTimeoutWriter) CloseNotify() <-chan bool {
  302. return tw.closeNotify()
  303. }
  304. type hijackTimeoutWriter struct {
  305. *baseTimeoutWriter
  306. }
  307. func (tw *hijackTimeoutWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
  308. return tw.hijack()
  309. }
  310. type closeHijackTimeoutWriter struct {
  311. *baseTimeoutWriter
  312. }
  313. func (tw *closeHijackTimeoutWriter) CloseNotify() <-chan bool {
  314. return tw.closeNotify()
  315. }
  316. func (tw *closeHijackTimeoutWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
  317. return tw.hijack()
  318. }
  319. // TODO: use restful.CrossOriginResourceSharing
  320. // Simple CORS implementation that wraps an http Handler
  321. // For a more detailed implementation use https://github.com/martini-contrib/cors
  322. // or implement CORS at your proxy layer
  323. // Pass nil for allowedMethods and allowedHeaders to use the defaults
  324. func CORS(handler http.Handler, allowedOriginPatterns []*regexp.Regexp, allowedMethods []string, allowedHeaders []string, allowCredentials string) http.Handler {
  325. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  326. origin := req.Header.Get("Origin")
  327. if origin != "" {
  328. allowed := false
  329. for _, pattern := range allowedOriginPatterns {
  330. if allowed = pattern.MatchString(origin); allowed {
  331. break
  332. }
  333. }
  334. if allowed {
  335. w.Header().Set("Access-Control-Allow-Origin", origin)
  336. // Set defaults for methods and headers if nothing was passed
  337. if allowedMethods == nil {
  338. allowedMethods = []string{"POST", "GET", "OPTIONS", "PUT", "DELETE"}
  339. }
  340. if allowedHeaders == nil {
  341. allowedHeaders = []string{"Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization", "X-Requested-With", "If-Modified-Since"}
  342. }
  343. w.Header().Set("Access-Control-Allow-Methods", strings.Join(allowedMethods, ", "))
  344. w.Header().Set("Access-Control-Allow-Headers", strings.Join(allowedHeaders, ", "))
  345. w.Header().Set("Access-Control-Allow-Credentials", allowCredentials)
  346. // Stop here if its a preflight OPTIONS request
  347. if req.Method == "OPTIONS" {
  348. w.WriteHeader(http.StatusNoContent)
  349. return
  350. }
  351. }
  352. }
  353. // Dispatch to the next handler
  354. handler.ServeHTTP(w, req)
  355. })
  356. }
  357. // RequestAttributeGetter is a function that extracts authorizer.Attributes from an http.Request
  358. type RequestAttributeGetter interface {
  359. GetAttribs(req *http.Request) (attribs authorizer.Attributes)
  360. }
  361. type requestAttributeGetter struct {
  362. requestContextMapper api.RequestContextMapper
  363. requestInfoResolver *RequestInfoResolver
  364. }
  365. // NewAttributeGetter returns an object which implements the RequestAttributeGetter interface.
  366. func NewRequestAttributeGetter(requestContextMapper api.RequestContextMapper, requestInfoResolver *RequestInfoResolver) RequestAttributeGetter {
  367. return &requestAttributeGetter{requestContextMapper, requestInfoResolver}
  368. }
  369. func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attributes {
  370. attribs := authorizer.AttributesRecord{}
  371. ctx, ok := r.requestContextMapper.Get(req)
  372. if ok {
  373. user, ok := api.UserFrom(ctx)
  374. if ok {
  375. attribs.User = user
  376. }
  377. }
  378. requestInfo, _ := r.requestInfoResolver.GetRequestInfo(req)
  379. // Start with common attributes that apply to resource and non-resource requests
  380. attribs.ResourceRequest = requestInfo.IsResourceRequest
  381. attribs.Path = requestInfo.Path
  382. attribs.Verb = requestInfo.Verb
  383. attribs.APIGroup = requestInfo.APIGroup
  384. attribs.APIVersion = requestInfo.APIVersion
  385. attribs.Resource = requestInfo.Resource
  386. attribs.Subresource = requestInfo.Subresource
  387. attribs.Namespace = requestInfo.Namespace
  388. attribs.Name = requestInfo.Name
  389. return &attribs
  390. }
  391. // WithAuthorizationCheck passes all authorized requests on to handler, and returns a forbidden error otherwise.
  392. func WithAuthorizationCheck(handler http.Handler, getAttribs RequestAttributeGetter, a authorizer.Authorizer) http.Handler {
  393. return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  394. authorized, reason, err := a.Authorize(getAttribs.GetAttribs(req))
  395. if err != nil {
  396. internalError(w, req, err)
  397. return
  398. }
  399. if !authorized {
  400. glog.V(4).Infof("Forbidden: %#v, Reason: %s", req.RequestURI, reason)
  401. forbidden(w, req)
  402. return
  403. }
  404. handler.ServeHTTP(w, req)
  405. })
  406. }
  407. // RequestInfo holds information parsed from the http.Request
  408. type RequestInfo struct {
  409. // IsResourceRequest indicates whether or not the request is for an API resource or subresource
  410. IsResourceRequest bool
  411. // Path is the URL path of the request
  412. Path string
  413. // Verb is the kube verb associated with the request for API requests, not the http verb. This includes things like list and watch.
  414. // for non-resource requests, this is the lowercase http verb
  415. Verb string
  416. APIPrefix string
  417. APIGroup string
  418. APIVersion string
  419. Namespace string
  420. // Resource is the name of the resource being requested. This is not the kind. For example: pods
  421. Resource string
  422. // Subresource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind.
  423. // For instance, /pods has the resource "pods" and the kind "Pod", while /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod"
  424. // (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource "binding", and kind "Binding".
  425. Subresource string
  426. // Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in.
  427. Name string
  428. // Parts are the path parts for the request, always starting with /{resource}/{name}
  429. Parts []string
  430. }
  431. type RequestInfoResolver struct {
  432. APIPrefixes sets.String
  433. GrouplessAPIPrefixes sets.String
  434. }
  435. // TODO write an integration test against the swagger doc to test the RequestInfo and match up behavior to responses
  436. // GetRequestInfo returns the information from the http request. If error is not nil, RequestInfo holds the information as best it is known before the failure
  437. // It handles both resource and non-resource requests and fills in all the pertinent information for each.
  438. // Valid Inputs:
  439. // Resource paths
  440. // /apis/{api-group}/{version}/namespaces
  441. // /api/{version}/namespaces
  442. // /api/{version}/namespaces/{namespace}
  443. // /api/{version}/namespaces/{namespace}/{resource}
  444. // /api/{version}/namespaces/{namespace}/{resource}/{resourceName}
  445. // /api/{version}/{resource}
  446. // /api/{version}/{resource}/{resourceName}
  447. //
  448. // Special verbs without subresources:
  449. // /api/{version}/proxy/{resource}/{resourceName}
  450. // /api/{version}/proxy/namespaces/{namespace}/{resource}/{resourceName}
  451. // /api/{version}/redirect/namespaces/{namespace}/{resource}/{resourceName}
  452. // /api/{version}/redirect/{resource}/{resourceName}
  453. //
  454. // Special verbs with subresources:
  455. // /api/{version}/watch/{resource}
  456. // /api/{version}/watch/namespaces/{namespace}/{resource}
  457. //
  458. // NonResource paths
  459. // /apis/{api-group}/{version}
  460. // /apis/{api-group}
  461. // /apis
  462. // /api/{version}
  463. // /api
  464. // /healthz
  465. // /
  466. func (r *RequestInfoResolver) GetRequestInfo(req *http.Request) (RequestInfo, error) {
  467. // start with a non-resource request until proven otherwise
  468. requestInfo := RequestInfo{
  469. IsResourceRequest: false,
  470. Path: req.URL.Path,
  471. Verb: strings.ToLower(req.Method),
  472. }
  473. currentParts := splitPath(req.URL.Path)
  474. if len(currentParts) < 3 {
  475. // return a non-resource request
  476. return requestInfo, nil
  477. }
  478. if !r.APIPrefixes.Has(currentParts[0]) {
  479. // return a non-resource request
  480. return requestInfo, nil
  481. }
  482. requestInfo.APIPrefix = currentParts[0]
  483. currentParts = currentParts[1:]
  484. if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) {
  485. // one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?"
  486. if len(currentParts) < 3 {
  487. // return a non-resource request
  488. return requestInfo, nil
  489. }
  490. requestInfo.APIGroup = currentParts[0]
  491. currentParts = currentParts[1:]
  492. }
  493. requestInfo.IsResourceRequest = true
  494. requestInfo.APIVersion = currentParts[0]
  495. currentParts = currentParts[1:]
  496. // handle input of form /{specialVerb}/*
  497. if specialVerbs.Has(currentParts[0]) {
  498. if len(currentParts) < 2 {
  499. return requestInfo, fmt.Errorf("unable to determine kind and namespace from url, %v", req.URL)
  500. }
  501. requestInfo.Verb = currentParts[0]
  502. currentParts = currentParts[1:]
  503. } else {
  504. switch req.Method {
  505. case "POST":
  506. requestInfo.Verb = "create"
  507. case "GET", "HEAD":
  508. requestInfo.Verb = "get"
  509. case "PUT":
  510. requestInfo.Verb = "update"
  511. case "PATCH":
  512. requestInfo.Verb = "patch"
  513. case "DELETE":
  514. requestInfo.Verb = "delete"
  515. default:
  516. requestInfo.Verb = ""
  517. }
  518. }
  519. // URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
  520. if currentParts[0] == "namespaces" {
  521. if len(currentParts) > 1 {
  522. requestInfo.Namespace = currentParts[1]
  523. // if there is another step after the namespace name and it is not a known namespace subresource
  524. // move currentParts to include it as a resource in its own right
  525. if len(currentParts) > 2 && !namespaceSubresources.Has(currentParts[2]) {
  526. currentParts = currentParts[2:]
  527. }
  528. }
  529. } else {
  530. requestInfo.Namespace = api.NamespaceNone
  531. }
  532. // parsing successful, so we now know the proper value for .Parts
  533. requestInfo.Parts = currentParts
  534. // parts look like: resource/resourceName/subresource/other/stuff/we/don't/interpret
  535. switch {
  536. case len(requestInfo.Parts) >= 3 && !specialVerbsNoSubresources.Has(requestInfo.Verb):
  537. requestInfo.Subresource = requestInfo.Parts[2]
  538. fallthrough
  539. case len(requestInfo.Parts) >= 2:
  540. requestInfo.Name = requestInfo.Parts[1]
  541. fallthrough
  542. case len(requestInfo.Parts) >= 1:
  543. requestInfo.Resource = requestInfo.Parts[0]
  544. }
  545. // if there's no name on the request and we thought it was a get before, then the actual verb is a list
  546. if len(requestInfo.Name) == 0 && requestInfo.Verb == "get" {
  547. requestInfo.Verb = "list"
  548. }
  549. // if there's no name on the request and we thought it was a delete before, then the actual verb is deletecollection
  550. if len(requestInfo.Name) == 0 && requestInfo.Verb == "delete" {
  551. requestInfo.Verb = "deletecollection"
  552. }
  553. return requestInfo, nil
  554. }