blob: 1520bb3c9e5156544c84facebf5cffcf4eddd65d [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package request
18
19import (
20 "context"
21 "fmt"
22 "net/http"
23 "strings"
24
25 "k8s.io/apimachinery/pkg/api/validation/path"
26 metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/util/sets"
29
30 "github.com/golang/glog"
31)
32
33// LongRunningRequestCheck is a predicate which is true for long-running http requests.
34type LongRunningRequestCheck func(r *http.Request, requestInfo *RequestInfo) bool
35
36type RequestInfoResolver interface {
37 NewRequestInfo(req *http.Request) (*RequestInfo, error)
38}
39
40// RequestInfo holds information parsed from the http.Request
41type RequestInfo struct {
42 // IsResourceRequest indicates whether or not the request is for an API resource or subresource
43 IsResourceRequest bool
44 // Path is the URL path of the request
45 Path string
46 // Verb is the kube verb associated with the request for API requests, not the http verb. This includes things like list and watch.
47 // for non-resource requests, this is the lowercase http verb
48 Verb string
49
50 APIPrefix string
51 APIGroup string
52 APIVersion string
53 Namespace string
54 // Resource is the name of the resource being requested. This is not the kind. For example: pods
55 Resource string
56 // 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.
57 // 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"
58 // (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".
59 Subresource string
60 // Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in.
61 Name string
62 // Parts are the path parts for the request, always starting with /{resource}/{name}
63 Parts []string
64}
65
66// specialVerbs contains just strings which are used in REST paths for special actions that don't fall under the normal
67// CRUDdy GET/POST/PUT/DELETE actions on REST objects.
68// TODO: find a way to keep this up to date automatically. Maybe dynamically populate list as handlers added to
69// master's Mux.
70var specialVerbs = sets.NewString("proxy", "watch")
71
72// specialVerbsNoSubresources contains root verbs which do not allow subresources
73var specialVerbsNoSubresources = sets.NewString("proxy")
74
75// namespaceSubresources contains subresources of namespace
76// this list allows the parser to distinguish between a namespace subresource, and a namespaced resource
77var namespaceSubresources = sets.NewString("status", "finalize")
78
79// NamespaceSubResourcesForTest exports namespaceSubresources for testing in pkg/master/master_test.go, so we never drift
80var NamespaceSubResourcesForTest = sets.NewString(namespaceSubresources.List()...)
81
82type RequestInfoFactory struct {
83 APIPrefixes sets.String // without leading and trailing slashes
84 GrouplessAPIPrefixes sets.String // without leading and trailing slashes
85}
86
87// TODO write an integration test against the swagger doc to test the RequestInfo and match up behavior to responses
88// 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
89// It handles both resource and non-resource requests and fills in all the pertinent information for each.
90// Valid Inputs:
91// Resource paths
92// /apis/{api-group}/{version}/namespaces
93// /api/{version}/namespaces
94// /api/{version}/namespaces/{namespace}
95// /api/{version}/namespaces/{namespace}/{resource}
96// /api/{version}/namespaces/{namespace}/{resource}/{resourceName}
97// /api/{version}/{resource}
98// /api/{version}/{resource}/{resourceName}
99//
100// Special verbs without subresources:
101// /api/{version}/proxy/{resource}/{resourceName}
102// /api/{version}/proxy/namespaces/{namespace}/{resource}/{resourceName}
103//
104// Special verbs with subresources:
105// /api/{version}/watch/{resource}
106// /api/{version}/watch/namespaces/{namespace}/{resource}
107//
108// NonResource paths
109// /apis/{api-group}/{version}
110// /apis/{api-group}
111// /apis
112// /api/{version}
113// /api
114// /healthz
115// /
116func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, error) {
117 // start with a non-resource request until proven otherwise
118 requestInfo := RequestInfo{
119 IsResourceRequest: false,
120 Path: req.URL.Path,
121 Verb: strings.ToLower(req.Method),
122 }
123
124 currentParts := splitPath(req.URL.Path)
125 if len(currentParts) < 3 {
126 // return a non-resource request
127 return &requestInfo, nil
128 }
129
130 if !r.APIPrefixes.Has(currentParts[0]) {
131 // return a non-resource request
132 return &requestInfo, nil
133 }
134 requestInfo.APIPrefix = currentParts[0]
135 currentParts = currentParts[1:]
136
137 if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) {
138 // one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?"
139 if len(currentParts) < 3 {
140 // return a non-resource request
141 return &requestInfo, nil
142 }
143
144 requestInfo.APIGroup = currentParts[0]
145 currentParts = currentParts[1:]
146 }
147
148 requestInfo.IsResourceRequest = true
149 requestInfo.APIVersion = currentParts[0]
150 currentParts = currentParts[1:]
151
152 // handle input of form /{specialVerb}/*
153 if specialVerbs.Has(currentParts[0]) {
154 if len(currentParts) < 2 {
155 return &requestInfo, fmt.Errorf("unable to determine kind and namespace from url, %v", req.URL)
156 }
157
158 requestInfo.Verb = currentParts[0]
159 currentParts = currentParts[1:]
160
161 } else {
162 switch req.Method {
163 case "POST":
164 requestInfo.Verb = "create"
165 case "GET", "HEAD":
166 requestInfo.Verb = "get"
167 case "PUT":
168 requestInfo.Verb = "update"
169 case "PATCH":
170 requestInfo.Verb = "patch"
171 case "DELETE":
172 requestInfo.Verb = "delete"
173 default:
174 requestInfo.Verb = ""
175 }
176 }
177
178 // URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
179 if currentParts[0] == "namespaces" {
180 if len(currentParts) > 1 {
181 requestInfo.Namespace = currentParts[1]
182
183 // if there is another step after the namespace name and it is not a known namespace subresource
184 // move currentParts to include it as a resource in its own right
185 if len(currentParts) > 2 && !namespaceSubresources.Has(currentParts[2]) {
186 currentParts = currentParts[2:]
187 }
188 }
189 } else {
190 requestInfo.Namespace = metav1.NamespaceNone
191 }
192
193 // parsing successful, so we now know the proper value for .Parts
194 requestInfo.Parts = currentParts
195
196 // parts look like: resource/resourceName/subresource/other/stuff/we/don't/interpret
197 switch {
198 case len(requestInfo.Parts) >= 3 && !specialVerbsNoSubresources.Has(requestInfo.Verb):
199 requestInfo.Subresource = requestInfo.Parts[2]
200 fallthrough
201 case len(requestInfo.Parts) >= 2:
202 requestInfo.Name = requestInfo.Parts[1]
203 fallthrough
204 case len(requestInfo.Parts) >= 1:
205 requestInfo.Resource = requestInfo.Parts[0]
206 }
207
208 // 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
209 if len(requestInfo.Name) == 0 && requestInfo.Verb == "get" {
210 opts := metainternalversion.ListOptions{}
211 if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), metav1.SchemeGroupVersion, &opts); err != nil {
212 // An error in parsing request will result in default to "list" and not setting "name" field.
213 glog.Errorf("Couldn't parse request %#v: %v", req.URL.Query(), err)
214 // Reset opts to not rely on partial results from parsing.
215 // However, if watch is set, let's report it.
216 opts = metainternalversion.ListOptions{}
217 if values := req.URL.Query()["watch"]; len(values) > 0 {
218 switch strings.ToLower(values[0]) {
219 case "false", "0":
220 default:
221 opts.Watch = true
222 }
223 }
224 }
225
226 if opts.Watch {
227 requestInfo.Verb = "watch"
228 } else {
229 requestInfo.Verb = "list"
230 }
231
232 if opts.FieldSelector != nil {
233 if name, ok := opts.FieldSelector.RequiresExactMatch("metadata.name"); ok {
234 if len(path.IsValidPathSegmentName(name)) == 0 {
235 requestInfo.Name = name
236 }
237 }
238 }
239 }
240 // if there's no name on the request and we thought it was a delete before, then the actual verb is deletecollection
241 if len(requestInfo.Name) == 0 && requestInfo.Verb == "delete" {
242 requestInfo.Verb = "deletecollection"
243 }
244
245 return &requestInfo, nil
246}
247
248type requestInfoKeyType int
249
250// requestInfoKey is the RequestInfo key for the context. It's of private type here. Because
251// keys are interfaces and interfaces are equal when the type and the value is equal, this
252// does not conflict with the keys defined in pkg/api.
253const requestInfoKey requestInfoKeyType = iota
254
255// WithRequestInfo returns a copy of parent in which the request info value is set
256func WithRequestInfo(parent context.Context, info *RequestInfo) context.Context {
257 return WithValue(parent, requestInfoKey, info)
258}
259
260// RequestInfoFrom returns the value of the RequestInfo key on the ctx
261func RequestInfoFrom(ctx context.Context) (*RequestInfo, bool) {
262 info, ok := ctx.Value(requestInfoKey).(*RequestInfo)
263 return info, ok
264}
265
266// splitPath returns the segments for a URL path.
267func splitPath(path string) []string {
268 path = strings.Trim(path, "/")
269 if path == "" {
270 return []string{}
271 }
272 return strings.Split(path, "/")
273}