2014-09-02 16:28:24 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2014 The Kubernetes Authors .
2014-09-02 16:28:24 +00:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package apiserver
import (
2015-07-01 21:36:36 +00:00
"bufio"
"encoding/json"
2014-09-02 16:28:24 +00:00
"fmt"
2015-07-01 21:36:36 +00:00
"net"
2014-09-02 16:28:24 +00:00
"net/http"
"regexp"
"runtime/debug"
"strings"
2015-07-01 21:36:36 +00:00
"sync"
"time"
2014-09-02 16:28:24 +00:00
2015-08-05 22:05:17 +00:00
"github.com/golang/glog"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/auth/authorizer"
2016-03-28 18:33:58 +00:00
"k8s.io/kubernetes/pkg/auth/user"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/httplog"
2016-03-28 18:33:58 +00:00
"k8s.io/kubernetes/pkg/serviceaccount"
2015-09-09 17:45:01 +00:00
"k8s.io/kubernetes/pkg/util/sets"
2014-09-02 16:28:24 +00:00
)
2014-11-02 06:50:00 +00:00
// specialVerbs contains just strings which are used in REST paths for special actions that don't fall under the normal
// CRUDdy GET/POST/PUT/DELETE actions on REST objects.
// TODO: find a way to keep this up to date automatically. Maybe dynamically populate list as handlers added to
// master's Mux.
2015-10-30 04:05:50 +00:00
var specialVerbs = sets . NewString ( "proxy" , "redirect" , "watch" )
// specialVerbsNoSubresources contains root verbs which do not allow subresources
var specialVerbsNoSubresources = sets . NewString ( "proxy" , "redirect" )
2014-11-02 06:50:00 +00:00
2016-03-13 03:46:20 +00:00
// namespaceSubresources contains subresources of namespace
// this list allows the parser to distinguish between a namespace subresource, and a namespaced resource
var namespaceSubresources = sets . NewString ( "status" , "finalize" )
// NamespaceSubResourcesForTest exports namespaceSubresources for testing in pkg/master/master_test.go, so we never drift
var NamespaceSubResourcesForTest = sets . NewString ( namespaceSubresources . List ( ) ... )
2015-03-31 04:41:10 +00:00
// Constant for the retry-after interval on rate limiting.
// TODO: maybe make this dynamic? or user-adjustable?
const RetryAfter = "1"
2014-11-02 06:50:00 +00:00
// IsReadOnlyReq() is true for any (or at least many) request which has no observable
// side effects on state of apiserver (though there may be internal side effects like
// caching and logging).
func IsReadOnlyReq ( req http . Request ) bool {
if req . Method == "GET" {
// TODO: add OPTIONS and HEAD if we ever support those.
return true
}
return false
}
2014-10-20 22:23:28 +00:00
// ReadOnly passes all GET requests on to handler, and returns an error on all other requests.
func ReadOnly ( handler http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
2014-11-02 06:50:00 +00:00
if IsReadOnlyReq ( * req ) {
2014-10-20 22:23:28 +00:00
handler . ServeHTTP ( w , req )
return
}
w . WriteHeader ( http . StatusForbidden )
fmt . Fprintf ( w , "This is a read-only endpoint." )
} )
}
2016-02-18 19:56:05 +00:00
type LongRunningRequestCheck func ( r * http . Request ) bool
// BasicLongRunningRequestCheck pathRegex operates against the url path, the queryParams match is case insensitive.
// Any one match flags the request.
// TODO tighten this check to eliminate the abuse potential by malicious clients that start setting queryParameters
// to bypass the rate limitter. This could be done using a full parse and special casing the bits we need.
func BasicLongRunningRequestCheck ( pathRegex * regexp . Regexp , queryParams map [ string ] string ) LongRunningRequestCheck {
return func ( r * http . Request ) bool {
if pathRegex . MatchString ( r . URL . Path ) {
return true
}
for key , expectedValue := range queryParams {
if strings . ToLower ( expectedValue ) == strings . ToLower ( r . URL . Query ( ) . Get ( key ) ) {
return true
}
}
return false
}
}
2015-03-31 04:41:10 +00:00
// MaxInFlight limits the number of in-flight requests to buffer size of the passed in channel.
2016-02-18 19:56:05 +00:00
func MaxInFlightLimit ( c chan bool , longRunningRequestCheck LongRunningRequestCheck , handler http . Handler ) http . Handler {
2015-03-31 04:41:10 +00:00
if c == nil {
return handler
}
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2016-02-18 19:56:05 +00:00
if longRunningRequestCheck ( r ) {
2015-03-31 04:41:10 +00:00
// Skip tracking long running events.
handler . ServeHTTP ( w , r )
return
}
select {
case c <- true :
defer func ( ) { <- c } ( )
handler . ServeHTTP ( w , r )
default :
2016-05-20 08:18:05 +00:00
tooManyRequests ( r , w )
2015-03-31 04:41:10 +00:00
}
} )
}
2016-05-20 08:18:05 +00:00
func tooManyRequests ( req * http . Request , w http . ResponseWriter ) {
// "Too Many Requests" response is returned before logger is setup for the request.
// So we need to explicitly log it here.
defer httplog . NewLogged ( req , & w ) . Log ( )
2015-03-31 04:41:10 +00:00
// Return a 429 status indicating "Too Many Requests"
w . Header ( ) . Set ( "Retry-After" , RetryAfter )
2015-04-22 06:04:32 +00:00
http . Error ( w , "Too many requests, please try again later." , errors . StatusTooManyRequests )
2015-03-31 04:41:10 +00:00
}
2014-09-02 16:28:24 +00:00
// RecoverPanics wraps an http Handler to recover and log panics.
func RecoverPanics ( handler http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
defer func ( ) {
if x := recover ( ) ; x != nil {
2015-04-22 06:04:32 +00:00
http . Error ( w , "apis panic. Look in log for details." , http . StatusInternalServerError )
2015-04-24 13:09:14 +00:00
glog . Errorf ( "APIServer panic'd on %v %v: %v\n%s\n" , req . Method , req . RequestURI , x , debug . Stack ( ) )
2014-09-02 16:28:24 +00:00
}
} ( )
defer httplog . NewLogged ( req , & w ) . StacktraceWhen (
httplog . StatusIsNot (
http . StatusOK ,
2014-10-24 17:16:02 +00:00
http . StatusCreated ,
2014-09-02 16:28:24 +00:00
http . StatusAccepted ,
2015-10-07 20:04:59 +00:00
http . StatusBadRequest ,
2014-09-02 16:28:24 +00:00
http . StatusMovedPermanently ,
http . StatusTemporaryRedirect ,
http . StatusConflict ,
http . StatusNotFound ,
2015-06-30 22:19:41 +00:00
http . StatusUnauthorized ,
http . StatusForbidden ,
2015-01-24 01:17:11 +00:00
errors . StatusUnprocessableEntity ,
2015-01-08 20:41:38 +00:00
http . StatusSwitchingProtocols ,
2014-09-02 16:28:24 +00:00
) ,
) . Log ( )
// Dispatch to the internal handler
handler . ServeHTTP ( w , req )
} )
}
2015-07-01 21:36:36 +00:00
// TimeoutHandler returns an http.Handler that runs h with a timeout
// determined by timeoutFunc. The new http.Handler calls h.ServeHTTP to handle
// each request, but if a call runs for longer than its time limit, the
// handler responds with a 503 Service Unavailable error and the message
// provided. (If msg is empty, a suitable default message with be sent.) After
// the handler times out, writes by h to its http.ResponseWriter will return
// http.ErrHandlerTimeout. If timeoutFunc returns a nil timeout channel, no
// timeout will be enforced.
func TimeoutHandler ( h http . Handler , timeoutFunc func ( * http . Request ) ( timeout <- chan time . Time , msg string ) ) http . Handler {
return & timeoutHandler { h , timeoutFunc }
}
type timeoutHandler struct {
handler http . Handler
timeout func ( * http . Request ) ( <- chan time . Time , string )
}
func ( t * timeoutHandler ) ServeHTTP ( w http . ResponseWriter , r * http . Request ) {
after , msg := t . timeout ( r )
if after == nil {
t . handler . ServeHTTP ( w , r )
return
}
done := make ( chan struct { } , 1 )
tw := newTimeoutWriter ( w )
go func ( ) {
t . handler . ServeHTTP ( tw , r )
done <- struct { } { }
} ( )
select {
case <- done :
return
case <- after :
tw . timeout ( msg )
}
}
type timeoutWriter interface {
http . ResponseWriter
timeout ( string )
}
func newTimeoutWriter ( w http . ResponseWriter ) timeoutWriter {
base := & baseTimeoutWriter { w : w }
_ , notifiable := w . ( http . CloseNotifier )
_ , hijackable := w . ( http . Hijacker )
switch {
case notifiable && hijackable :
return & closeHijackTimeoutWriter { base }
case notifiable :
return & closeTimeoutWriter { base }
case hijackable :
return & hijackTimeoutWriter { base }
default :
return base
}
}
type baseTimeoutWriter struct {
w http . ResponseWriter
mu sync . Mutex
timedOut bool
wroteHeader bool
hijacked bool
}
func ( tw * baseTimeoutWriter ) Header ( ) http . Header {
return tw . w . Header ( )
}
func ( tw * baseTimeoutWriter ) Write ( p [ ] byte ) ( int , error ) {
tw . mu . Lock ( )
defer tw . mu . Unlock ( )
tw . wroteHeader = true
if tw . hijacked {
return 0 , http . ErrHijacked
}
if tw . timedOut {
return 0 , http . ErrHandlerTimeout
}
return tw . w . Write ( p )
}
2015-09-11 16:28:31 +00:00
func ( tw * baseTimeoutWriter ) Flush ( ) {
tw . mu . Lock ( )
defer tw . mu . Unlock ( )
if flusher , ok := tw . w . ( http . Flusher ) ; ok {
flusher . Flush ( )
}
}
2015-07-01 21:36:36 +00:00
func ( tw * baseTimeoutWriter ) WriteHeader ( code int ) {
tw . mu . Lock ( )
defer tw . mu . Unlock ( )
if tw . timedOut || tw . wroteHeader || tw . hijacked {
return
}
tw . wroteHeader = true
tw . w . WriteHeader ( code )
}
func ( tw * baseTimeoutWriter ) timeout ( msg string ) {
tw . mu . Lock ( )
defer tw . mu . Unlock ( )
if ! tw . wroteHeader && ! tw . hijacked {
tw . w . WriteHeader ( http . StatusGatewayTimeout )
if msg != "" {
tw . w . Write ( [ ] byte ( msg ) )
} else {
enc := json . NewEncoder ( tw . w )
2015-12-10 18:32:29 +00:00
enc . Encode ( errors . NewServerTimeout ( api . Resource ( "" ) , "" , 0 ) )
2015-07-01 21:36:36 +00:00
}
}
tw . timedOut = true
}
func ( tw * baseTimeoutWriter ) closeNotify ( ) <- chan bool {
return tw . w . ( http . CloseNotifier ) . CloseNotify ( )
}
func ( tw * baseTimeoutWriter ) hijack ( ) ( net . Conn , * bufio . ReadWriter , error ) {
tw . mu . Lock ( )
defer tw . mu . Unlock ( )
if tw . timedOut {
return nil , nil , http . ErrHandlerTimeout
}
conn , rw , err := tw . w . ( http . Hijacker ) . Hijack ( )
if err == nil {
tw . hijacked = true
}
return conn , rw , err
}
type closeTimeoutWriter struct {
* baseTimeoutWriter
}
func ( tw * closeTimeoutWriter ) CloseNotify ( ) <- chan bool {
return tw . closeNotify ( )
}
type hijackTimeoutWriter struct {
* baseTimeoutWriter
}
func ( tw * hijackTimeoutWriter ) Hijack ( ) ( net . Conn , * bufio . ReadWriter , error ) {
return tw . hijack ( )
}
type closeHijackTimeoutWriter struct {
* baseTimeoutWriter
}
func ( tw * closeHijackTimeoutWriter ) CloseNotify ( ) <- chan bool {
return tw . closeNotify ( )
}
func ( tw * closeHijackTimeoutWriter ) Hijack ( ) ( net . Conn , * bufio . ReadWriter , error ) {
return tw . hijack ( )
}
2014-11-11 07:11:45 +00:00
// TODO: use restful.CrossOriginResourceSharing
2014-09-02 16:28:24 +00:00
// Simple CORS implementation that wraps an http Handler
// For a more detailed implementation use https://github.com/martini-contrib/cors
// or implement CORS at your proxy layer
// Pass nil for allowedMethods and allowedHeaders to use the defaults
2014-09-04 17:55:30 +00:00
func CORS ( handler http . Handler , allowedOriginPatterns [ ] * regexp . Regexp , allowedMethods [ ] string , allowedHeaders [ ] string , allowCredentials string ) http . Handler {
2014-09-02 16:28:24 +00:00
return http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
origin := req . Header . Get ( "Origin" )
if origin != "" {
allowed := false
2014-09-04 17:55:30 +00:00
for _ , pattern := range allowedOriginPatterns {
2014-09-03 18:33:52 +00:00
if allowed = pattern . MatchString ( origin ) ; allowed {
break
}
2014-09-02 16:28:24 +00:00
}
if allowed {
w . Header ( ) . Set ( "Access-Control-Allow-Origin" , origin )
// Set defaults for methods and headers if nothing was passed
if allowedMethods == nil {
allowedMethods = [ ] string { "POST" , "GET" , "OPTIONS" , "PUT" , "DELETE" }
}
if allowedHeaders == nil {
2014-09-04 17:55:30 +00:00
allowedHeaders = [ ] string { "Content-Type" , "Content-Length" , "Accept-Encoding" , "X-CSRF-Token" , "Authorization" , "X-Requested-With" , "If-Modified-Since" }
2014-09-02 16:28:24 +00:00
}
w . Header ( ) . Set ( "Access-Control-Allow-Methods" , strings . Join ( allowedMethods , ", " ) )
w . Header ( ) . Set ( "Access-Control-Allow-Headers" , strings . Join ( allowedHeaders , ", " ) )
w . Header ( ) . Set ( "Access-Control-Allow-Credentials" , allowCredentials )
// Stop here if its a preflight OPTIONS request
if req . Method == "OPTIONS" {
w . WriteHeader ( http . StatusNoContent )
return
}
}
}
// Dispatch to the next handler
handler . ServeHTTP ( w , req )
} )
}
2014-10-16 21:18:16 +00:00
// RequestAttributeGetter is a function that extracts authorizer.Attributes from an http.Request
2014-11-03 15:57:08 +00:00
type RequestAttributeGetter interface {
GetAttribs ( req * http . Request ) ( attribs authorizer . Attributes )
}
type requestAttributeGetter struct {
2015-10-20 17:34:26 +00:00
requestContextMapper api . RequestContextMapper
requestInfoResolver * RequestInfoResolver
2014-11-03 15:57:08 +00:00
}
// NewAttributeGetter returns an object which implements the RequestAttributeGetter interface.
2015-10-20 17:34:26 +00:00
func NewRequestAttributeGetter ( requestContextMapper api . RequestContextMapper , requestInfoResolver * RequestInfoResolver ) RequestAttributeGetter {
return & requestAttributeGetter { requestContextMapper , requestInfoResolver }
2014-11-03 15:57:08 +00:00
}
func ( r * requestAttributeGetter ) GetAttribs ( req * http . Request ) authorizer . Attributes {
attribs := authorizer . AttributesRecord { }
2015-02-11 22:09:25 +00:00
ctx , ok := r . requestContextMapper . Get ( req )
2014-11-03 15:57:08 +00:00
if ok {
2015-02-11 22:09:25 +00:00
user , ok := api . UserFrom ( ctx )
if ok {
attribs . User = user
}
2014-11-03 15:57:08 +00:00
}
2014-10-16 21:18:16 +00:00
2015-11-20 06:14:49 +00:00
requestInfo , _ := r . requestInfoResolver . GetRequestInfo ( req )
2014-12-09 19:23:21 +00:00
2015-11-20 06:14:49 +00:00
// Start with common attributes that apply to resource and non-resource requests
attribs . ResourceRequest = requestInfo . IsResourceRequest
attribs . Path = requestInfo . Path
attribs . Verb = requestInfo . Verb
attribs . APIGroup = requestInfo . APIGroup
2016-03-28 20:32:13 +00:00
attribs . APIVersion = requestInfo . APIVersion
2015-11-20 06:14:49 +00:00
attribs . Resource = requestInfo . Resource
2016-03-28 20:32:13 +00:00
attribs . Subresource = requestInfo . Subresource
2015-11-20 06:14:49 +00:00
attribs . Namespace = requestInfo . Namespace
2016-03-28 20:32:13 +00:00
attribs . Name = requestInfo . Name
2014-11-02 06:50:00 +00:00
2014-11-03 15:57:08 +00:00
return & attribs
2014-10-16 21:18:16 +00:00
}
2016-04-26 13:31:43 +00:00
func WithImpersonation ( handler http . Handler , requestContextMapper api . RequestContextMapper , a authorizer . Authorizer ) http . Handler {
2016-03-28 18:33:58 +00:00
return http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
requestedSubject := req . Header . Get ( "Impersonate-User" )
if len ( requestedSubject ) == 0 {
handler . ServeHTTP ( w , req )
return
}
ctx , exists := requestContextMapper . Get ( req )
if ! exists {
forbidden ( w , req )
return
}
requestor , exists := api . UserFrom ( ctx )
if ! exists {
forbidden ( w , req )
return
}
actingAsAttributes := & authorizer . AttributesRecord {
2016-04-26 13:31:43 +00:00
User : requestor ,
Verb : "impersonate" ,
APIGroup : api . GroupName ,
Resource : "users" ,
Name : requestedSubject ,
2016-03-28 18:33:58 +00:00
ResourceRequest : true ,
}
2016-04-26 13:31:43 +00:00
if namespace , name , err := serviceaccount . SplitUsername ( requestedSubject ) ; err == nil {
actingAsAttributes . Resource = "serviceaccounts"
actingAsAttributes . Namespace = namespace
actingAsAttributes . Name = name
}
2016-03-28 18:33:58 +00:00
err := a . Authorize ( actingAsAttributes )
if err != nil {
forbidden ( w , req )
return
}
switch {
case strings . HasPrefix ( requestedSubject , serviceaccount . ServiceAccountUsernamePrefix ) :
namespace , name , err := serviceaccount . SplitUsername ( requestedSubject )
if err != nil {
forbidden ( w , req )
return
}
requestContextMapper . Update ( req , api . WithUser ( ctx , serviceaccount . UserInfo ( namespace , name , "" ) ) )
default :
newUser := & user . DefaultInfo {
Name : requestedSubject ,
}
requestContextMapper . Update ( req , api . WithUser ( ctx , newUser ) )
}
newCtx , _ := requestContextMapper . Get ( req )
oldUser , _ := api . UserFrom ( ctx )
newUser , _ := api . UserFrom ( newCtx )
httplog . LogOf ( req , w ) . Addf ( "%v is acting as %v" , oldUser , newUser )
handler . ServeHTTP ( w , req )
} )
}
2014-10-16 21:18:16 +00:00
// WithAuthorizationCheck passes all authorized requests on to handler, and returns a forbidden error otherwise.
func WithAuthorizationCheck ( handler http . Handler , getAttribs RequestAttributeGetter , a authorizer . Authorizer ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , req * http . Request ) {
2014-11-03 15:57:08 +00:00
err := a . Authorize ( getAttribs . GetAttribs ( req ) )
2014-10-16 21:18:16 +00:00
if err == nil {
handler . ServeHTTP ( w , req )
return
}
forbidden ( w , req )
} )
}
2014-12-09 19:23:21 +00:00
2015-10-20 17:34:26 +00:00
// RequestInfo holds information parsed from the http.Request
type RequestInfo struct {
// IsResourceRequest indicates whether or not the request is for an API resource or subresource
IsResourceRequest bool
// Path is the URL path of the request
Path string
// Verb is the kube verb associated with the request for API requests, not the http verb. This includes things like list and watch.
// for non-resource requests, this is the lowercase http verb
Verb string
2015-09-22 19:43:29 +00:00
APIPrefix string
APIGroup string
2015-01-29 19:14:36 +00:00
APIVersion string
Namespace string
// Resource is the name of the resource being requested. This is not the kind. For example: pods
Resource string
2015-04-10 17:37:13 +00:00
// 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.
// 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"
// (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".
2015-04-10 16:42:52 +00:00
Subresource string
2015-01-29 19:14:36 +00:00
// Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in.
Name string
2015-02-24 01:17:39 +00:00
// Parts are the path parts for the request, always starting with /{resource}/{name}
2015-01-29 19:14:36 +00:00
Parts [ ] string
2015-02-24 01:17:39 +00:00
}
2015-10-20 17:34:26 +00:00
type RequestInfoResolver struct {
2015-09-22 19:43:29 +00:00
APIPrefixes sets . String
GrouplessAPIPrefixes sets . String
2015-01-29 19:14:36 +00:00
}
2015-10-20 17:34:26 +00:00
// TODO write an integration test against the swagger doc to test the RequestInfo and match up behavior to responses
// 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
// It handles both resource and non-resource requests and fills in all the pertinent information for each.
2014-12-09 19:23:21 +00:00
// Valid Inputs:
2015-10-20 17:34:26 +00:00
// Resource paths
2015-09-22 19:43:29 +00:00
// /apis/{api-group}/{version}/namespaces
// /api/{version}/namespaces
// /api/{version}/namespaces/{namespace}
// /api/{version}/namespaces/{namespace}/{resource}
// /api/{version}/namespaces/{namespace}/{resource}/{resourceName}
// /api/{version}/{resource}
// /api/{version}/{resource}/{resourceName}
2014-12-09 19:23:21 +00:00
//
2015-10-30 04:05:50 +00:00
// Special verbs without subresources:
2015-09-22 19:43:29 +00:00
// /api/{version}/proxy/{resource}/{resourceName}
// /api/{version}/proxy/namespaces/{namespace}/{resource}/{resourceName}
// /api/{version}/redirect/namespaces/{namespace}/{resource}/{resourceName}
// /api/{version}/redirect/{resource}/{resourceName}
2015-10-30 04:05:50 +00:00
//
// Special verbs with subresources:
2015-09-22 19:43:29 +00:00
// /api/{version}/watch/{resource}
// /api/{version}/watch/namespaces/{namespace}/{resource}
2015-10-20 17:34:26 +00:00
//
// NonResource paths
// /apis/{api-group}/{version}
// /apis/{api-group}
// /apis
// /api/{version}
// /api
// /healthz
// /
func ( r * RequestInfoResolver ) GetRequestInfo ( req * http . Request ) ( RequestInfo , error ) {
// start with a non-resource request until proven otherwise
requestInfo := RequestInfo {
IsResourceRequest : false ,
Path : req . URL . Path ,
Verb : strings . ToLower ( req . Method ) ,
2015-02-24 01:17:39 +00:00
}
2015-01-29 19:14:36 +00:00
2015-10-20 17:34:26 +00:00
currentParts := splitPath ( req . URL . Path )
2015-09-22 19:43:29 +00:00
if len ( currentParts ) < 3 {
2015-10-20 17:34:26 +00:00
// return a non-resource request
return requestInfo , nil
2014-12-09 19:23:21 +00:00
}
2015-09-22 19:43:29 +00:00
if ! r . APIPrefixes . Has ( currentParts [ 0 ] ) {
2015-10-20 17:34:26 +00:00
// return a non-resource request
return requestInfo , nil
2015-09-22 19:43:29 +00:00
}
requestInfo . APIPrefix = currentParts [ 0 ]
currentParts = currentParts [ 1 : ]
2015-01-29 19:14:36 +00:00
2015-09-22 19:43:29 +00:00
if ! r . GrouplessAPIPrefixes . Has ( requestInfo . APIPrefix ) {
// one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?"
if len ( currentParts ) < 3 {
2015-10-20 17:34:26 +00:00
// return a non-resource request
return requestInfo , nil
2014-12-09 19:23:21 +00:00
}
2015-09-22 19:43:29 +00:00
requestInfo . APIGroup = currentParts [ 0 ]
currentParts = currentParts [ 1 : ]
2014-12-09 19:23:21 +00:00
}
2015-10-20 17:34:26 +00:00
requestInfo . IsResourceRequest = true
2015-09-22 19:43:29 +00:00
requestInfo . APIVersion = currentParts [ 0 ]
currentParts = currentParts [ 1 : ]
2014-12-09 19:23:21 +00:00
// handle input of form /{specialVerb}/*
2015-10-30 04:05:50 +00:00
if specialVerbs . Has ( currentParts [ 0 ] ) {
2015-09-22 19:43:29 +00:00
if len ( currentParts ) < 2 {
return requestInfo , fmt . Errorf ( "unable to determine kind and namespace from url, %v" , req . URL )
}
2015-01-29 19:14:36 +00:00
requestInfo . Verb = currentParts [ 0 ]
2015-09-22 19:43:29 +00:00
currentParts = currentParts [ 1 : ]
2015-01-29 19:14:36 +00:00
} else {
switch req . Method {
case "POST" :
requestInfo . Verb = "create"
2015-11-20 16:34:35 +00:00
case "GET" , "HEAD" :
2015-01-29 19:14:36 +00:00
requestInfo . Verb = "get"
case "PUT" :
requestInfo . Verb = "update"
2015-09-21 17:55:03 +00:00
case "PATCH" :
requestInfo . Verb = "patch"
2015-01-29 19:14:36 +00:00
case "DELETE" :
requestInfo . Verb = "delete"
2015-09-29 01:11:51 +00:00
default :
requestInfo . Verb = ""
2014-12-09 19:23:21 +00:00
}
}
2015-01-19 21:50:00 +00:00
// URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
if currentParts [ 0 ] == "namespaces" {
2015-04-10 16:42:52 +00:00
if len ( currentParts ) > 1 {
2015-01-19 21:50:00 +00:00
requestInfo . Namespace = currentParts [ 1 ]
2015-04-10 16:42:52 +00:00
// if there is another step after the namespace name and it is not a known namespace subresource
// move currentParts to include it as a resource in its own right
2016-03-13 03:46:20 +00:00
if len ( currentParts ) > 2 && ! namespaceSubresources . Has ( currentParts [ 2 ] ) {
2015-04-10 16:42:52 +00:00
currentParts = currentParts [ 2 : ]
}
2014-12-09 19:23:21 +00:00
}
2015-01-29 19:14:36 +00:00
} else {
2015-06-10 20:17:04 +00:00
requestInfo . Namespace = api . NamespaceNone
2014-12-09 19:23:21 +00:00
}
2015-01-29 19:14:36 +00:00
// parsing successful, so we now know the proper value for .Parts
requestInfo . Parts = currentParts
2015-04-10 16:42:52 +00:00
// parts look like: resource/resourceName/subresource/other/stuff/we/don't/interpret
switch {
2015-10-30 04:05:50 +00:00
case len ( requestInfo . Parts ) >= 3 && ! specialVerbsNoSubresources . Has ( requestInfo . Verb ) :
2015-04-10 16:42:52 +00:00
requestInfo . Subresource = requestInfo . Parts [ 2 ]
fallthrough
2015-10-30 04:05:50 +00:00
case len ( requestInfo . Parts ) >= 2 :
2015-01-29 19:14:36 +00:00
requestInfo . Name = requestInfo . Parts [ 1 ]
2015-04-10 16:42:52 +00:00
fallthrough
2015-10-30 04:05:50 +00:00
case len ( requestInfo . Parts ) >= 1 :
2015-04-10 16:42:52 +00:00
requestInfo . Resource = requestInfo . Parts [ 0 ]
2015-01-29 19:14:36 +00:00
}
// if there's no name on the request and we thought it was a get before, then the actual verb is a list
if len ( requestInfo . Name ) == 0 && requestInfo . Verb == "get" {
requestInfo . Verb = "list"
}
2016-02-10 22:44:15 +00:00
// if there's no name on the request and we thought it was a delete before, then the actual verb is deletecollection
if len ( requestInfo . Name ) == 0 && requestInfo . Verb == "delete" {
requestInfo . Verb = "deletecollection"
}
2015-01-29 19:14:36 +00:00
return requestInfo , nil
2014-12-09 19:23:21 +00:00
}