package request import ( "context" "fmt" "k8s.io/apimachinery/pkg/util/sets" "net/http" "strings" k8srequest "k8s.io/apiserver/pkg/endpoints/request" ) type RequestInfoResolver interface { NewRequestInfo(req *http.Request) (*RequestInfo, error) } // 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. // master's Mux. var specialVerbs = sets.NewString("proxy", "watch") var kubernetesAPIPrefixes = sets.NewString("api", "apis") // RequestInfo holds information parsed from the http.Request, // extended from k8s.io/apiserver/pkg/endpoints/request/requestinfo.go type RequestInfo struct { *k8srequest.RequestInfo // IsKubernetesRequest indicates whether or not the request should be handled by kubernetes or kubesphere IsKubernetesRequest bool // Workspace of requested resource, for non-workspaced resources, this may be empty Workspace string // Cluster of requested resource, this is empty in single-cluster environment Cluster string // Devops project of requested resource, this may be empty DevopsProject string } type RequestInfoFactory struct { APIPrefixes sets.String GrouplessAPIPrefixes sets.String } // 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 // It handles both resource and non-resource requests and fills in all the pertinent information for each. // Valid Inputs: // // /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} // // Special verbs without subresources: // /api/{version}/proxy/{resource}/{resourceName} // /api/{version}/proxy/namespaces/{namespace}/{resource}/{resourceName} // // Special verbs with subresources: // /api/{version}/watch/{resource} // /api/{version}/watch/namespaces/{namespace}/{resource} // // /kapis/{api-group}/{version}/workspaces/{workspace}/{resource}/{resourceName} // / // /kapis/{api-group}/{version}/namespaces/{namespace}/{resource} // /kapis/{api-group}/{version}/namespaces/{namespace}/{resource}/{resourceName} // With workspaces: // /kapis/{api-group}/{version}/clusters/{cluster}/namespaces/{namespace}/{resource} // /kapis/{api-group}/{version}/clusters/{cluster}/namespaces/{namespace}/{resource}/{resourceName} // func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, error) { requestInfo := RequestInfo{ IsKubernetesRequest: false, RequestInfo: &k8srequest.RequestInfo{ Path: req.URL.Path, Verb: req.Method, }, } defer func() { if kubernetesAPIPrefixes.Has(requestInfo.APIPrefix) { requestInfo.IsKubernetesRequest = true } }() currentParts := splitPath(req.URL.Path) if len(currentParts) < 3 { return &requestInfo, nil } if !r.APIPrefixes.Has(currentParts[0]) { // return a non-resource request return &requestInfo, nil } requestInfo.APIPrefix = currentParts[0] currentParts = currentParts[1:] if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) { if len(currentParts) < 2 { return &requestInfo, nil } if currentParts[0] == "clusters" { requestInfo.Cluster = currentParts[1] currentParts = currentParts[2:] } if len(currentParts) < 3 { return &requestInfo, nil } requestInfo.APIGroup = currentParts[0] currentParts = currentParts[1:] } requestInfo.IsResourceRequest = true requestInfo.APIVersion = currentParts[0] currentParts = currentParts[1:] if specialVerbs.Has(currentParts[0]) { if len(currentParts) < 2 { return &requestInfo, fmt.Errorf("unable to determine kind and namespace from url: %v", req.URL) } requestInfo.Verb = currentParts[0] currentParts = currentParts[1:] } else { switch req.Method { case "POST": requestInfo.Verb = "create" case "GET", "HEAD": requestInfo.Verb = "get" case "PUT": requestInfo.Verb = "update" case "PATCH": requestInfo.Verb = "patch" case "DELETE": requestInfo.Verb = "delete" default: requestInfo.Verb = "" } } 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 func WithRequestInfo(parent context.Context, info *RequestInfo) context.Context { return k8srequest.WithValue(parent, requestInfoKey, info) } func RequestInfoFrom(ctx context.Context) (*RequestInfo, bool) { info, ok := ctx.Value(requestInfoKey).(*RequestInfo) return info, ok } // 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, "/") }