2014-09-02 16:28:24 +00:00
/ *
2016-09-26 15:18:19 +00:00
Copyright 2016 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 .
* /
2016-09-26 15:18:19 +00:00
package request
2014-09-02 16:28:24 +00:00
import (
"fmt"
"net/http"
"strings"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/api"
2015-09-09 17:45:01 +00:00
"k8s.io/kubernetes/pkg/util/sets"
2014-09-02 16:28:24 +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
}
2016-09-26 11:07:35 +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.
var specialVerbs = sets . NewString ( "proxy" , "redirect" , "watch" )
// specialVerbsNoSubresources contains root verbs which do not allow subresources
var specialVerbsNoSubresources = sets . NewString ( "proxy" , "redirect" )
// 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 ( ) ... )
2016-09-29 08:10:22 +00:00
type RequestInfoFactory struct {
2016-09-26 15:18:19 +00:00
APIPrefixes sets . String // without leading and trailing slashes
GrouplessAPIPrefixes sets . String // without leading and trailing slashes
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
2016-09-29 08:10:22 +00:00
// NewRequestInfo returns the information from the http request. If error is not nil, RequestInfo holds the information as best it is known before the failure
2015-10-20 17:34:26 +00:00
// 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
// /
2016-09-29 08:10:22 +00:00
func ( r * RequestInfoFactory ) NewRequestInfo ( req * http . Request ) ( * RequestInfo , error ) {
2015-10-20 17:34:26 +00:00
// 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
2016-09-26 15:18:19 +00:00
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
2016-09-26 15:18:19 +00:00
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
2016-09-26 15:18:19 +00:00
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 {
2016-09-26 15:18:19 +00:00
return & requestInfo , fmt . Errorf ( "unable to determine kind and namespace from url, %v" , req . URL )
2015-09-22 19:43:29 +00:00
}
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
}
2016-11-02 16:00:42 +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 or a watch
2015-01-29 19:14:36 +00:00
if len ( requestInfo . Name ) == 0 && requestInfo . Verb == "get" {
2016-11-02 16:00:42 +00:00
// Assumes v1.ListOptions
// Duplicates logic of Convert_Slice_string_To_bool
switch strings . ToLower ( req . URL . Query ( ) . Get ( "watch" ) ) {
case "false" , "0" , "" :
requestInfo . Verb = "list"
default :
requestInfo . Verb = "watch"
}
2015-01-29 19:14:36 +00:00
}
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
2016-09-26 15:18:19 +00:00
return & requestInfo , nil
}
type requestInfoKeyType int
// requestInfoKey is the RequestInfo key for the context. It's of private type here. Because
// keys are interfaces and interfaces are equal when the type and the value is equal, this
// does not conflict with the keys defined in pkg/api.
const requestInfoKey requestInfoKeyType = iota
// WithRequestInfo returns a copy of parent in which the request info value is set
func WithRequestInfo ( parent api . Context , info * RequestInfo ) api . Context {
return api . WithValue ( parent , requestInfoKey , info )
}
// RequestInfoFrom returns the value of the RequestInfo key on the ctx
func RequestInfoFrom ( ctx api . Context ) ( * RequestInfo , bool ) {
info , ok := ctx . Value ( requestInfoKey ) . ( * RequestInfo )
return info , ok
2014-12-09 19:23:21 +00:00
}
2016-09-26 11:07:35 +00:00
// splitPath returns the segments for a URL path.
func splitPath ( path string ) [ ] string {
path = strings . Trim ( path , "/" )
if path == "" {
return [ ] string { }
}
return strings . Split ( path , "/" )
}