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  }