github.com/lablabs/operator-sdk@v0.8.2/pkg/ansible/proxy/requestfactory/requestinfo.go (about) 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 // This code was retrieved from 18 // https://github.com/kubernetes/apiserver/blob/master/pkg/endpoints/request/requestinfo.go 19 // and slightly modified for use in this project 20 21 package requestfactory 22 23 import ( 24 "fmt" 25 "net/http" 26 "strings" 27 28 "k8s.io/apimachinery/pkg/api/validation/path" 29 metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/util/sets" 32 33 logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" 34 ) 35 36 var log = logf.Log.WithName("requestfactory") 37 38 // RequestInfo holds information parsed from the http.Request 39 type RequestInfo struct { 40 // IsResourceRequest indicates whether or not the request is for an API 41 // resource or subresource 42 IsResourceRequest bool 43 // Path is the URL path of the request 44 Path string 45 // Verb is the kube verb associated with the request for API requests, not 46 // the http verb. This includes things like list and watch. for 47 // 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 55 // kind. For example: pods 56 Resource string 57 // Subresource is the name of the subresource being requested. This is a 58 // different resource, scoped to the parent resource, but it may have a 59 // different kind. For instance, /pods has the resource "pods" and the kind 60 // "Pod", while /pods/foo/status has the resource "pods", the sub resource 61 // "status", and the kind "Pod" (because status operates on pods). The 62 // binding resource for a pod though may be /pods/foo/binding, which has 63 // resource "pods", subresource "binding", and kind "Binding". 64 Subresource string 65 // Name is empty for some verbs, but if the request directly indicates a name 66 // (not in body content) then this field is filled in. 67 Name string 68 // Parts are the path parts for the request, always starting with 69 // /{resource}/{name} 70 Parts []string 71 } 72 73 // specialVerbs contains just strings which are used in REST paths for special 74 // actions that don't fall under the normal CRUDdy GET/POST/PUT/DELETE actions 75 // on REST objects. TODO: find a way to keep this up to date automatically. 76 // Maybe dynamically populate list as handlers added to master's Mux. 77 var specialVerbs = sets.NewString("proxy", "watch") 78 79 // specialVerbsNoSubresources contains root verbs which do not allow 80 // subresources 81 var specialVerbsNoSubresources = sets.NewString("proxy") 82 83 // namespaceSubresources contains subresources of namespace this list allows 84 // the parser to distinguish between a namespace subresource, and a namespaced 85 // resource 86 var namespaceSubresources = sets.NewString("status", "finalize") 87 88 // NamespaceSubResourcesForTest exports namespaceSubresources for testing in 89 // pkg/master/master_test.go, so we never drift 90 var NamespaceSubResourcesForTest = sets.NewString(namespaceSubresources.List()...) 91 92 type RequestInfoFactory struct { 93 APIPrefixes sets.String // without leading and trailing slashes 94 GrouplessAPIPrefixes sets.String // without leading and trailing slashes 95 } 96 97 // TODO write an integration test against the swagger doc to test the 98 // RequestInfo and match up behavior to responses NewRequestInfo returns the 99 // information from the http request. If error is not nil, RequestInfo holds 100 // the information as best it is known before the failure It handles both 101 // resource and non-resource requests and fills in all the pertinent 102 // information for each. 103 // Valid Inputs: 104 // Resource paths 105 // /apis/{api-group}/{version}/namespaces 106 // /api/{version}/namespaces 107 // /api/{version}/namespaces/{namespace} 108 // /api/{version}/namespaces/{namespace}/{resource} 109 // /api/{version}/namespaces/{namespace}/{resource}/{resourceName} 110 // /api/{version}/{resource} 111 // /api/{version}/{resource}/{resourceName} 112 // 113 // Special verbs without subresources: 114 // /api/{version}/proxy/{resource}/{resourceName} 115 // /api/{version}/proxy/namespaces/{namespace}/{resource}/{resourceName} 116 // 117 // Special verbs with subresources: 118 // /api/{version}/watch/{resource} 119 // /api/{version}/watch/namespaces/{namespace}/{resource} 120 // 121 // NonResource paths 122 // /apis/{api-group}/{version} 123 // /apis/{api-group} 124 // /apis 125 // /api/{version} 126 // /api 127 // /healthz 128 // / 129 func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, error) { 130 // start with a non-resource request until proven otherwise 131 requestInfo := RequestInfo{ 132 IsResourceRequest: false, 133 Path: req.URL.Path, 134 Verb: strings.ToLower(req.Method), 135 } 136 137 currentParts := splitPath(req.URL.Path) 138 if len(currentParts) < 3 { 139 // return a non-resource request 140 return &requestInfo, nil 141 } 142 143 if !r.APIPrefixes.Has(currentParts[0]) { 144 // return a non-resource request 145 return &requestInfo, nil 146 } 147 requestInfo.APIPrefix = currentParts[0] 148 currentParts = currentParts[1:] 149 150 if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) { 151 // one part (APIPrefix) has already been consumed, so this is actually "do 152 // we have four parts?" 153 if len(currentParts) < 3 { 154 // return a non-resource request 155 return &requestInfo, nil 156 } 157 158 requestInfo.APIGroup = currentParts[0] 159 currentParts = currentParts[1:] 160 } 161 162 requestInfo.IsResourceRequest = true 163 requestInfo.APIVersion = currentParts[0] 164 currentParts = currentParts[1:] 165 166 // handle input of form /{specialVerb}/* 167 if specialVerbs.Has(currentParts[0]) { 168 if len(currentParts) < 2 { 169 return &requestInfo, fmt.Errorf("unable to determine kind and namespace from url, %v", req.URL) 170 } 171 172 requestInfo.Verb = currentParts[0] 173 currentParts = currentParts[1:] 174 175 } else { 176 switch req.Method { 177 case "POST": 178 requestInfo.Verb = "create" 179 case "GET", "HEAD": 180 requestInfo.Verb = "get" 181 case "PUT": 182 requestInfo.Verb = "update" 183 case "PATCH": 184 requestInfo.Verb = "patch" 185 case "DELETE": 186 requestInfo.Verb = "delete" 187 default: 188 requestInfo.Verb = "" 189 } 190 } 191 192 // URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to 193 // be relative to kind 194 if currentParts[0] == "namespaces" { 195 if len(currentParts) > 1 { 196 requestInfo.Namespace = currentParts[1] 197 198 // if there is another step after the namespace name and it is not a 199 // known namespace subresource move currentParts to include it as a 200 // resource in its own right 201 if len(currentParts) > 2 && !namespaceSubresources.Has(currentParts[2]) { 202 currentParts = currentParts[2:] 203 } 204 } 205 } else { 206 requestInfo.Namespace = metav1.NamespaceNone 207 } 208 209 // parsing successful, so we now know the proper value for .Parts 210 requestInfo.Parts = currentParts 211 212 // parts look like: 213 // resource/resourceName/subresource/other/stuff/we/don't/interpret 214 switch { 215 case len(requestInfo.Parts) >= 3 && !specialVerbsNoSubresources.Has(requestInfo.Verb): 216 requestInfo.Subresource = requestInfo.Parts[2] 217 fallthrough 218 case len(requestInfo.Parts) >= 2: 219 requestInfo.Name = requestInfo.Parts[1] 220 fallthrough 221 case len(requestInfo.Parts) >= 1: 222 requestInfo.Resource = requestInfo.Parts[0] 223 } 224 225 // if there's no name on the request and we thought it was a get before, then 226 // the actual verb is a list or a watch 227 if len(requestInfo.Name) == 0 && requestInfo.Verb == "get" { 228 opts := metainternalversion.ListOptions{} 229 if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), metav1.SchemeGroupVersion, &opts); err != nil { 230 // An error in parsing request will result in default to "list" and not 231 // setting "name" field. 232 log.Error(err, "Could not parse request") 233 // Reset opts to not rely on partial results from parsing. 234 // However, if watch is set, let's report it. 235 opts = metainternalversion.ListOptions{} 236 if values := req.URL.Query()["watch"]; len(values) > 0 { 237 switch strings.ToLower(values[0]) { 238 case "false", "0": 239 default: 240 opts.Watch = true 241 } 242 } 243 } 244 245 if opts.Watch { 246 requestInfo.Verb = "watch" 247 } else { 248 requestInfo.Verb = "list" 249 } 250 251 if opts.FieldSelector != nil { 252 if name, ok := opts.FieldSelector.RequiresExactMatch("metadata.name"); ok { 253 if len(path.IsValidPathSegmentName(name)) == 0 { 254 requestInfo.Name = name 255 } 256 } 257 } 258 } 259 // if there's no name on the request and we thought it was a delete before, 260 // then the actual verb is deletecollection 261 if len(requestInfo.Name) == 0 && requestInfo.Verb == "delete" { 262 requestInfo.Verb = "deletecollection" 263 } 264 265 return &requestInfo, nil 266 } 267 268 // splitPath returns the segments for a URL path. 269 func splitPath(path string) []string { 270 path = strings.Trim(path, "/") 271 if path == "" { 272 return []string{} 273 } 274 return strings.Split(path, "/") 275 }