mirror of https://github.com/k3s-io/k3s
update APIRequestInfo for APIGroup
parent
28d71418ca
commit
dc8d0de70b
|
@ -78,6 +78,10 @@ type Mux interface {
|
|||
type APIGroupVersion struct {
|
||||
Storage map[string]rest.Storage
|
||||
|
||||
// Root is the APIPrefix under which this is being served. It can also be the APIPrefix/APIGroup that is being served
|
||||
// Since the APIGroup may not contain a '/', you can get the APIGroup by parsing from the last '/'
|
||||
// TODO Currently, an APIPrefix with a '/' is not supported in conjunction with an empty APIGroup. This struct should
|
||||
// be refactored to keep separate information separate to avoid this sort of problem in the future.
|
||||
Root string
|
||||
Version string
|
||||
|
||||
|
@ -112,6 +116,38 @@ const (
|
|||
MaxTimeoutSecs = 600
|
||||
)
|
||||
|
||||
func (g *APIGroupVersion) GetAPIPrefix() string {
|
||||
slashlessRoot := strings.Trim(g.Root, "/")
|
||||
if lastSlashIndex := strings.LastIndex(slashlessRoot, "/"); lastSlashIndex != -1 {
|
||||
return slashlessRoot[:lastSlashIndex]
|
||||
}
|
||||
|
||||
return slashlessRoot
|
||||
}
|
||||
|
||||
func (g *APIGroupVersion) GetAPIGroup() string {
|
||||
slashlessRoot := strings.Trim(g.Root, "/")
|
||||
if lastSlashIndex := strings.LastIndex(slashlessRoot, "/"); lastSlashIndex != -1 {
|
||||
return slashlessRoot[lastSlashIndex:]
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (g *APIGroupVersion) GetAPIVersion() string {
|
||||
return g.Version
|
||||
}
|
||||
|
||||
func (g *APIGroupVersion) GetAPIRequestInfoResolver() *APIRequestInfoResolver {
|
||||
apiPrefix := g.GetAPIPrefix()
|
||||
info := &APIRequestInfoResolver{sets.NewString(apiPrefix), sets.String{}, g.Mapper}
|
||||
if len(g.GetAPIGroup()) == 0 {
|
||||
info.GrouplessAPIPrefixes.Insert(apiPrefix)
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
// InstallREST registers the REST handlers (storage, watch, proxy and redirect) into a restful Container.
|
||||
// It is expected that the provided path root prefix will serve all operations. Root MUST NOT end
|
||||
// in a slash.
|
||||
|
@ -151,12 +187,10 @@ func (g *APIGroupVersion) UpdateREST(container *restful.Container) error {
|
|||
|
||||
// newInstaller is a helper to create the installer. Used by InstallREST and UpdateREST.
|
||||
func (g *APIGroupVersion) newInstaller() *APIInstaller {
|
||||
info := &APIRequestInfoResolver{sets.NewString(strings.TrimPrefix(g.Root, "/")), g.Mapper}
|
||||
|
||||
prefix := path.Join(g.Root, g.Version)
|
||||
installer := &APIInstaller{
|
||||
group: g,
|
||||
info: info,
|
||||
info: g.GetAPIRequestInfoResolver(),
|
||||
prefix: prefix,
|
||||
minRequestTimeout: g.MinRequestTimeout,
|
||||
proxyDialerFn: g.ProxyDialerFn,
|
||||
|
|
|
@ -86,3 +86,22 @@ func forbidden(w http.ResponseWriter, req *http.Request) {
|
|||
w.WriteHeader(http.StatusForbidden)
|
||||
fmt.Fprintf(w, "Forbidden: %#v", req.RequestURI)
|
||||
}
|
||||
|
||||
// errAPIPrefixNotFound indicates that a APIRequestInfo resolution failed because the request isn't under
|
||||
// any known API prefixes
|
||||
type errAPIPrefixNotFound struct {
|
||||
SpecifiedPrefix string
|
||||
}
|
||||
|
||||
func (e *errAPIPrefixNotFound) Error() string {
|
||||
return fmt.Sprintf("no valid API prefix found matching %v", e.SpecifiedPrefix)
|
||||
}
|
||||
|
||||
func IsAPIPrefixNotFound(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, ok := err.(*errAPIPrefixNotFound)
|
||||
return ok
|
||||
}
|
||||
|
|
|
@ -348,8 +348,8 @@ type requestAttributeGetter struct {
|
|||
}
|
||||
|
||||
// NewAttributeGetter returns an object which implements the RequestAttributeGetter interface.
|
||||
func NewRequestAttributeGetter(requestContextMapper api.RequestContextMapper, restMapper meta.RESTMapper, apiRoots ...string) RequestAttributeGetter {
|
||||
return &requestAttributeGetter{requestContextMapper, &APIRequestInfoResolver{sets.NewString(apiRoots...), restMapper}}
|
||||
func NewRequestAttributeGetter(requestContextMapper api.RequestContextMapper, restMapper meta.RESTMapper, apiRoots []string, grouplessAPIRoots []string) RequestAttributeGetter {
|
||||
return &requestAttributeGetter{requestContextMapper, &APIRequestInfoResolver{sets.NewString(apiRoots...), sets.NewString(grouplessAPIRoots...), restMapper}}
|
||||
}
|
||||
|
||||
func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attributes {
|
||||
|
@ -395,6 +395,8 @@ func WithAuthorizationCheck(handler http.Handler, getAttribs RequestAttributeGet
|
|||
type APIRequestInfo struct {
|
||||
// Verb is the kube verb associated with the request, not the http verb. This includes things like list and watch.
|
||||
Verb string
|
||||
APIPrefix string
|
||||
APIGroup string
|
||||
APIVersion string
|
||||
Namespace string
|
||||
// Resource is the name of the resource being requested. This is not the kind. For example: pods
|
||||
|
@ -415,66 +417,68 @@ type APIRequestInfo struct {
|
|||
}
|
||||
|
||||
type APIRequestInfoResolver struct {
|
||||
APIPrefixes sets.String
|
||||
RestMapper meta.RESTMapper
|
||||
APIPrefixes sets.String
|
||||
GrouplessAPIPrefixes sets.String
|
||||
RestMapper meta.RESTMapper
|
||||
}
|
||||
|
||||
// TODO write an integration test against the swagger doc to test the APIRequestInfo and match up behavior to responses
|
||||
// GetAPIRequestInfo returns the information from the http request. If error is not nil, APIRequestInfo holds the information as best it is known before the failure
|
||||
// Valid Inputs:
|
||||
// Storage paths
|
||||
// /namespaces
|
||||
// /namespaces/{namespace}
|
||||
// /namespaces/{namespace}/{resource}
|
||||
// /namespaces/{namespace}/{resource}/{resourceName}
|
||||
// /{resource}
|
||||
// /{resource}/{resourceName}
|
||||
// /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:
|
||||
// /proxy/{resource}/{resourceName}
|
||||
// /proxy/namespaces/{namespace}/{resource}/{resourceName}
|
||||
// /redirect/namespaces/{namespace}/{resource}/{resourceName}
|
||||
// /redirect/{resource}/{resourceName}
|
||||
// /watch/{resource}
|
||||
// /watch/namespaces/{namespace}/{resource}
|
||||
//
|
||||
// Fully qualified paths for above:
|
||||
// /api/{version}/*
|
||||
// /api/{version}/*
|
||||
// /api/{version}/proxy/{resource}/{resourceName}
|
||||
// /api/{version}/proxy/namespaces/{namespace}/{resource}/{resourceName}
|
||||
// /api/{version}/redirect/namespaces/{namespace}/{resource}/{resourceName}
|
||||
// /api/{version}/redirect/{resource}/{resourceName}
|
||||
// /api/{version}/watch/{resource}
|
||||
// /api/{version}/watch/namespaces/{namespace}/{resource}
|
||||
func (r *APIRequestInfoResolver) GetAPIRequestInfo(req *http.Request) (APIRequestInfo, error) {
|
||||
requestInfo := APIRequestInfo{
|
||||
Raw: splitPath(req.URL.Path),
|
||||
}
|
||||
|
||||
currentParts := requestInfo.Raw
|
||||
if len(currentParts) < 1 {
|
||||
return requestInfo, fmt.Errorf("Unable to determine kind and namespace from an empty URL path")
|
||||
if len(currentParts) < 3 {
|
||||
return requestInfo, fmt.Errorf("a resource request must have a url with at least three parts, not %v", req.URL)
|
||||
}
|
||||
|
||||
for _, currPrefix := range r.APIPrefixes.List() {
|
||||
// handle input of form /api/{version}/* by adjusting special paths
|
||||
if currentParts[0] == currPrefix {
|
||||
if len(currentParts) > 1 {
|
||||
requestInfo.APIVersion = currentParts[1]
|
||||
}
|
||||
if !r.APIPrefixes.Has(currentParts[0]) {
|
||||
return requestInfo, &errAPIPrefixNotFound{currentParts[0]}
|
||||
}
|
||||
requestInfo.APIPrefix = currentParts[0]
|
||||
currentParts = currentParts[1:]
|
||||
|
||||
if len(currentParts) > 2 {
|
||||
currentParts = currentParts[2:]
|
||||
} else {
|
||||
return requestInfo, fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL)
|
||||
}
|
||||
if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) {
|
||||
// one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?"
|
||||
if len(currentParts) < 3 {
|
||||
return requestInfo, fmt.Errorf("a resource request with an API group must have a url with at least four parts, not %v", req.URL)
|
||||
}
|
||||
|
||||
requestInfo.APIGroup = currentParts[0]
|
||||
currentParts = currentParts[1:]
|
||||
}
|
||||
|
||||
requestInfo.APIVersion = currentParts[0]
|
||||
currentParts = currentParts[1:]
|
||||
|
||||
// handle input of form /{specialVerb}/*
|
||||
if _, ok := specialVerbs[currentParts[0]]; ok {
|
||||
requestInfo.Verb = currentParts[0]
|
||||
|
||||
if len(currentParts) > 1 {
|
||||
currentParts = currentParts[1:]
|
||||
} else {
|
||||
return requestInfo, fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL)
|
||||
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":
|
||||
|
|
|
@ -199,6 +199,8 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
|||
method string
|
||||
url string
|
||||
expectedVerb string
|
||||
expectedAPIPrefix string
|
||||
expectedAPIGroup string
|
||||
expectedAPIVersion string
|
||||
expectedNamespace string
|
||||
expectedResource string
|
||||
|
@ -209,42 +211,38 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
|||
}{
|
||||
|
||||
// resource paths
|
||||
{"GET", "/namespaces", "list", "", "", "namespaces", "", "Namespace", "", []string{"namespaces"}},
|
||||
{"GET", "/namespaces/other", "get", "", "other", "namespaces", "", "Namespace", "other", []string{"namespaces", "other"}},
|
||||
{"GET", "/api/v1/namespaces", "list", "api", "", "v1", "", "namespaces", "", "Namespace", "", []string{"namespaces"}},
|
||||
{"GET", "/api/v1/namespaces/other", "get", "api", "", "v1", "other", "namespaces", "", "Namespace", "other", []string{"namespaces", "other"}},
|
||||
|
||||
{"GET", "/namespaces/other/pods", "list", "", "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/namespaces/other/pods/foo", "get", "", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/pods", "list", "", api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/namespaces/other/pods/foo", "get", "", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/namespaces/other/pods", "list", "", "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/api/v1/namespaces/other/pods", "list", "api", "", "v1", "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/api/v1/namespaces/other/pods/foo", "get", "api", "", "v1", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1/pods", "list", "api", "", "v1", api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/api/v1/namespaces/other/pods/foo", "get", "api", "", "v1", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1/namespaces/other/pods", "list", "api", "", "v1", "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||
|
||||
// special verbs
|
||||
{"GET", "/proxy/namespaces/other/pods/foo", "proxy", "", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/redirect/namespaces/other/pods/foo", "redirect", "", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/watch/pods", "watch", "", api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/watch/namespaces/other/pods", "watch", "", "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||
|
||||
// fully-qualified paths
|
||||
{"GET", getPath("pods", "other", ""), "list", testapi.Default.Version(), "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||
{"GET", getPath("pods", "other", "foo"), "get", testapi.Default.Version(), "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", getPath("pods", "", ""), "list", testapi.Default.Version(), api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
||||
{"POST", getPath("pods", "", ""), "create", testapi.Default.Version(), api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
||||
{"GET", getPath("pods", "", "foo"), "get", testapi.Default.Version(), api.NamespaceAll, "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", pathWithPrefix("proxy", "pods", "", "foo"), "proxy", testapi.Default.Version(), api.NamespaceAll, "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", pathWithPrefix("watch", "pods", "", ""), "watch", testapi.Default.Version(), api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
||||
{"GET", pathWithPrefix("redirect", "pods", "", ""), "redirect", testapi.Default.Version(), api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
||||
{"GET", pathWithPrefix("watch", "pods", "other", ""), "watch", testapi.Default.Version(), "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/api/v1/proxy/namespaces/other/pods/foo", "proxy", "api", "", "v1", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1/redirect/namespaces/other/pods/foo", "redirect", "api", "", "v1", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1/watch/pods", "watch", "api", "", "v1", api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/api/v1/watch/namespaces/other/pods", "watch", "api", "", "v1", "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||
|
||||
// subresource identification
|
||||
{"GET", "/namespaces/other/pods/foo/status", "get", "", "other", "pods", "status", "Pod", "foo", []string{"pods", "foo", "status"}},
|
||||
{"PUT", "/namespaces/other/finalize", "update", "", "other", "finalize", "", "", "", []string{"finalize"}},
|
||||
{"GET", "/api/v1/namespaces/other/pods/foo/status", "get", "api", "", "v1", "other", "pods", "status", "Pod", "foo", []string{"pods", "foo", "status"}},
|
||||
{"PUT", "/api/v1/namespaces/other/finalize", "update", "api", "", "v1", "other", "finalize", "", "", "", []string{"finalize"}},
|
||||
|
||||
// verb identification
|
||||
{"PATCH", "/namespaces/other/pods/foo", "patch", "", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"DELETE", "/namespaces/other/pods/foo", "delete", "", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"PATCH", "/api/v1/namespaces/other/pods/foo", "patch", "api", "", "v1", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"DELETE", "/api/v1/namespaces/other/pods/foo", "delete", "api", "", "v1", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"POST", "/api/v1/namespaces/other/pods", "create", "api", "", "v1", "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||
|
||||
// api group identification
|
||||
{"POST", "/apis/experimental/v1/namespaces/other/pods", "create", "api", "experimental", "v1", "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||
|
||||
// api version identification
|
||||
{"POST", "/apis/experimental/v1beta3/namespaces/other/pods", "create", "api", "experimental", "v1beta3", "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||
}
|
||||
|
||||
apiRequestInfoResolver := &APIRequestInfoResolver{sets.NewString("api"), testapi.Default.RESTMapper()}
|
||||
apiRequestInfoResolver := &APIRequestInfoResolver{sets.NewString("api", "apis"), sets.NewString("api"), testapi.Default.RESTMapper()}
|
||||
|
||||
for _, successCase := range successCases {
|
||||
req, _ := http.NewRequest(successCase.method, successCase.url, nil)
|
||||
|
@ -282,7 +280,10 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
|||
errorCases := map[string]string{
|
||||
"no resource path": "/",
|
||||
"just apiversion": "/api/version/",
|
||||
"just prefix, group, version": "/apis/group/version/",
|
||||
"apiversion with no resource": "/api/version/",
|
||||
"bad prefix": "/badprefix/version/resource",
|
||||
"missing api group": "/apis/version/resource",
|
||||
}
|
||||
for k, v := range errorCases {
|
||||
req, err := http.NewRequest("GET", v, nil)
|
||||
|
|
|
@ -573,7 +573,7 @@ func (m *Master) init(c *Config) {
|
|||
apiserver.InstallSupport(m.muxHelper, m.rootWebService, c.EnableProfiling, healthzChecks...)
|
||||
apiserver.AddApiWebService(m.handlerContainer, c.APIPrefix, apiVersions)
|
||||
defaultVersion := m.defaultAPIGroupVersion()
|
||||
requestInfoResolver := &apiserver.APIRequestInfoResolver{APIPrefixes: sets.NewString(strings.TrimPrefix(defaultVersion.Root, "/")), RestMapper: defaultVersion.Mapper}
|
||||
requestInfoResolver := defaultVersion.GetAPIRequestInfoResolver()
|
||||
apiserver.InstallServiceErrorHandler(m.handlerContainer, requestInfoResolver, apiVersions)
|
||||
|
||||
// allGroups records all supported groups at /apis
|
||||
|
@ -608,7 +608,7 @@ func (m *Master) init(c *Config) {
|
|||
}
|
||||
apiserver.AddGroupWebService(m.handlerContainer, c.APIGroupPrefix+"/"+latest.GroupOrDie("experimental").Group+"/", group)
|
||||
allGroups = append(allGroups, group)
|
||||
expRequestInfoResolver := &apiserver.APIRequestInfoResolver{APIPrefixes: sets.NewString(strings.TrimPrefix(expVersion.Root, "/")), RestMapper: expVersion.Mapper}
|
||||
expRequestInfoResolver := expVersion.GetAPIRequestInfoResolver()
|
||||
apiserver.InstallServiceErrorHandler(m.handlerContainer, expRequestInfoResolver, []string{expVersion.Version})
|
||||
}
|
||||
|
||||
|
@ -652,7 +652,10 @@ func (m *Master) init(c *Config) {
|
|||
|
||||
m.InsecureHandler = handler
|
||||
|
||||
attributeGetter := apiserver.NewRequestAttributeGetter(m.requestContextMapper, latest.GroupOrDie("").RESTMapper, "api")
|
||||
attributeGetter := apiserver.NewRequestAttributeGetter(m.requestContextMapper, latest.GroupOrDie("").RESTMapper,
|
||||
[]string{strings.Trim(c.APIPrefix, "/"), strings.Trim(thirdpartyprefix, "/")}, // all possible API prefixes
|
||||
[]string{strings.Trim(c.APIPrefix, "/")}, // APIPrefixes that won't have groups (legacy)
|
||||
)
|
||||
handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, m.authorizer)
|
||||
|
||||
// Install Authenticator
|
||||
|
@ -918,7 +921,7 @@ func (m *Master) InstallThirdPartyResource(rsrc *expapi.ThirdPartyResource) erro
|
|||
}
|
||||
apiserver.AddGroupWebService(m.handlerContainer, path, apiGroup)
|
||||
m.addThirdPartyResourceStorage(path, thirdparty.Storage[strings.ToLower(kind)+"s"].(*thirdpartyresourcedataetcd.REST))
|
||||
thirdPartyRequestInfoResolver := &apiserver.APIRequestInfoResolver{APIPrefixes: sets.NewString(strings.TrimPrefix(group, "/")), RestMapper: thirdparty.Mapper}
|
||||
thirdPartyRequestInfoResolver := &apiserver.APIRequestInfoResolver{APIPrefixes: sets.NewString(strings.Trim(thirdpartyprefix, "/")), RestMapper: thirdparty.Mapper}
|
||||
apiserver.InstallServiceErrorHandler(m.handlerContainer, thirdPartyRequestInfoResolver, []string{thirdparty.Version})
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue