Matthias Andreas Benkard | 832a54e | 2019-01-29 09:27:38 +0100 | [diff] [blame] | 1 | /* |
| 2 | Copyright 2016 The Kubernetes Authors. |
| 3 | |
| 4 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | you may not use this file except in compliance with the License. |
| 6 | You may obtain a copy of the License at |
| 7 | |
| 8 | http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | |
| 10 | Unless required by applicable law or agreed to in writing, software |
| 11 | distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | See the License for the specific language governing permissions and |
| 14 | limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package request |
| 18 | |
| 19 | import ( |
| 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. |
| 34 | type LongRunningRequestCheck func(r *http.Request, requestInfo *RequestInfo) bool |
| 35 | |
| 36 | type RequestInfoResolver interface { |
| 37 | NewRequestInfo(req *http.Request) (*RequestInfo, error) |
| 38 | } |
| 39 | |
| 40 | // RequestInfo holds information parsed from the http.Request |
| 41 | type 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. |
| 70 | var specialVerbs = sets.NewString("proxy", "watch") |
| 71 | |
| 72 | // specialVerbsNoSubresources contains root verbs which do not allow subresources |
| 73 | var 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 |
| 77 | var namespaceSubresources = sets.NewString("status", "finalize") |
| 78 | |
| 79 | // NamespaceSubResourcesForTest exports namespaceSubresources for testing in pkg/master/master_test.go, so we never drift |
| 80 | var NamespaceSubResourcesForTest = sets.NewString(namespaceSubresources.List()...) |
| 81 | |
| 82 | type 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 | // / |
| 116 | func (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 | |
| 248 | type 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. |
| 253 | const requestInfoKey requestInfoKeyType = iota |
| 254 | |
| 255 | // WithRequestInfo returns a copy of parent in which the request info value is set |
| 256 | func 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 |
| 261 | func 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. |
| 267 | func splitPath(path string) []string { |
| 268 | path = strings.Trim(path, "/") |
| 269 | if path == "" { |
| 270 | return []string{} |
| 271 | } |
| 272 | return strings.Split(path, "/") |
| 273 | } |