remove Kind from APIRequestInfo

This commit is contained in:
deads2k 2015-09-25 14:57:10 -04:00
parent 8db054651c
commit df870cf36a
5 changed files with 67 additions and 102 deletions

View File

@ -42,7 +42,6 @@ import (
"k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util/errors" "k8s.io/kubernetes/pkg/util/errors"
"k8s.io/kubernetes/pkg/util/flushwriter" "k8s.io/kubernetes/pkg/util/flushwriter"
"k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/version" "k8s.io/kubernetes/pkg/version"
"github.com/emicklei/go-restful" "github.com/emicklei/go-restful"
@ -78,13 +77,13 @@ type Mux interface {
type APIGroupVersion struct { type APIGroupVersion struct {
Storage map[string]rest.Storage 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 Root string
Version string Version string
// APIRequestInfoResolver is used to parse URLs for the legacy proxy handler. Don't use this for anything else
// TODO: refactor proxy handler to use sub resources
APIRequestInfoResolver *APIRequestInfoResolver
// ServerVersion controls the Kubernetes APIVersion used for common objects in the apiserver // ServerVersion controls the Kubernetes APIVersion used for common objects in the apiserver
// schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may // schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may
// define a version "v1beta1" but want to use the Kubernetes "v1" internal objects. If // define a version "v1beta1" but want to use the Kubernetes "v1" internal objects. If
@ -116,38 +115,6 @@ const (
MaxTimeoutSecs = 600 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. // 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 // It is expected that the provided path root prefix will serve all operations. Root MUST NOT end
// in a slash. // in a slash.
@ -190,7 +157,7 @@ func (g *APIGroupVersion) newInstaller() *APIInstaller {
prefix := path.Join(g.Root, g.Version) prefix := path.Join(g.Root, g.Version)
installer := &APIInstaller{ installer := &APIInstaller{
group: g, group: g,
info: g.GetAPIRequestInfoResolver(), info: g.APIRequestInfoResolver,
prefix: prefix, prefix: prefix,
minRequestTimeout: g.MinRequestTimeout, minRequestTimeout: g.MinRequestTimeout,
proxyDialerFn: g.ProxyDialerFn, proxyDialerFn: g.ProxyDialerFn,

View File

@ -42,6 +42,7 @@ import (
"k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util" "k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/version" "k8s.io/kubernetes/pkg/version"
"k8s.io/kubernetes/pkg/watch" "k8s.io/kubernetes/pkg/watch"
"k8s.io/kubernetes/plugin/pkg/admission/admit" "k8s.io/kubernetes/plugin/pkg/admission/admit"
@ -190,11 +191,16 @@ func handleLinker(storage map[string]rest.Storage, selfLinker runtime.SelfLinker
return handleInternal(true, storage, admissionControl, selfLinker) return handleInternal(true, storage, admissionControl, selfLinker)
} }
func newTestAPIRequestInfoResolver() *APIRequestInfoResolver {
return &APIRequestInfoResolver{sets.NewString("api", "apis"), sets.NewString("api")}
}
func handleInternal(legacy bool, storage map[string]rest.Storage, admissionControl admission.Interface, selfLinker runtime.SelfLinker) http.Handler { func handleInternal(legacy bool, storage map[string]rest.Storage, admissionControl admission.Interface, selfLinker runtime.SelfLinker) http.Handler {
group := &APIGroupVersion{ group := &APIGroupVersion{
Storage: storage, Storage: storage,
Root: "/api", Root: "/api",
APIRequestInfoResolver: newTestAPIRequestInfoResolver(),
Creater: api.Scheme, Creater: api.Scheme,
Convertor: api.Scheme, Convertor: api.Scheme,
@ -2016,6 +2022,7 @@ func TestUpdateREST(t *testing.T) {
return &APIGroupVersion{ return &APIGroupVersion{
Storage: storage, Storage: storage,
Root: "/api", Root: "/api",
APIRequestInfoResolver: newTestAPIRequestInfoResolver(),
Creater: api.Scheme, Creater: api.Scheme,
Convertor: api.Scheme, Convertor: api.Scheme,
Typer: api.Scheme, Typer: api.Scheme,
@ -2097,6 +2104,7 @@ func TestParentResourceIsRequired(t *testing.T) {
"simple/sub": storage, "simple/sub": storage,
}, },
Root: "/api", Root: "/api",
APIRequestInfoResolver: newTestAPIRequestInfoResolver(),
Creater: api.Scheme, Creater: api.Scheme,
Convertor: api.Scheme, Convertor: api.Scheme,
Typer: api.Scheme, Typer: api.Scheme,
@ -2125,6 +2133,7 @@ func TestParentResourceIsRequired(t *testing.T) {
"simple/sub": storage, "simple/sub": storage,
}, },
Root: "/api", Root: "/api",
APIRequestInfoResolver: newTestAPIRequestInfoResolver(),
Creater: api.Scheme, Creater: api.Scheme,
Convertor: api.Scheme, Convertor: api.Scheme,
Typer: api.Scheme, Typer: api.Scheme,

View File

@ -31,7 +31,6 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/auth/authorizer" "k8s.io/kubernetes/pkg/auth/authorizer"
"k8s.io/kubernetes/pkg/httplog" "k8s.io/kubernetes/pkg/httplog"
"k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/sets"
@ -348,8 +347,8 @@ type requestAttributeGetter struct {
} }
// NewAttributeGetter returns an object which implements the RequestAttributeGetter interface. // NewAttributeGetter returns an object which implements the RequestAttributeGetter interface.
func NewRequestAttributeGetter(requestContextMapper api.RequestContextMapper, restMapper meta.RESTMapper, apiRoots []string, grouplessAPIRoots []string) RequestAttributeGetter { func NewRequestAttributeGetter(requestContextMapper api.RequestContextMapper, apiRequestInfoResolver *APIRequestInfoResolver) RequestAttributeGetter {
return &requestAttributeGetter{requestContextMapper, &APIRequestInfoResolver{sets.NewString(apiRoots...), sets.NewString(grouplessAPIRoots...), restMapper}} return &requestAttributeGetter{requestContextMapper, apiRequestInfoResolver}
} }
func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attributes { func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attributes {
@ -407,8 +406,6 @@ type APIRequestInfo struct {
// 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" // 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"
// (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". // (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".
Subresource string Subresource string
// Kind is the type of object being manipulated. For example: Pod
Kind string
// Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in. // Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in.
Name string Name string
// Parts are the path parts for the request, always starting with /{resource}/{name} // Parts are the path parts for the request, always starting with /{resource}/{name}
@ -421,7 +418,6 @@ type APIRequestInfo struct {
type APIRequestInfoResolver struct { type APIRequestInfoResolver struct {
APIPrefixes sets.String APIPrefixes sets.String
GrouplessAPIPrefixes 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 // TODO write an integration test against the swagger doc to test the APIRequestInfo and match up behavior to responses
@ -534,10 +530,5 @@ func (r *APIRequestInfoResolver) GetAPIRequestInfo(req *http.Request) (APIReques
requestInfo.Verb = "list" requestInfo.Verb = "list"
} }
// if we have a resource, we have a good shot at being able to determine kind
if len(requestInfo.Resource) > 0 {
_, requestInfo.Kind, _ = r.RestMapper.VersionAndKindForResource(requestInfo.Resource)
}
return requestInfo, nil return requestInfo, nil
} }

View File

@ -30,7 +30,6 @@ import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/util/sets"
) )
type fakeRL bool type fakeRL bool
@ -205,44 +204,43 @@ func TestGetAPIRequestInfo(t *testing.T) {
expectedNamespace string expectedNamespace string
expectedResource string expectedResource string
expectedSubresource string expectedSubresource string
expectedKind string
expectedName string expectedName string
expectedParts []string expectedParts []string
}{ }{
// resource paths // resource paths
{"GET", "/api/v1/namespaces", "list", "api", "", "v1", "", "namespaces", "", "Namespace", "", []string{"namespaces"}}, {"GET", "/api/v1/namespaces", "list", "api", "", "v1", "", "namespaces", "", "", []string{"namespaces"}},
{"GET", "/api/v1/namespaces/other", "get", "api", "", "v1", "other", "namespaces", "", "Namespace", "other", []string{"namespaces", "other"}}, {"GET", "/api/v1/namespaces/other", "get", "api", "", "v1", "other", "namespaces", "", "other", []string{"namespaces", "other"}},
{"GET", "/api/v1/namespaces/other/pods", "list", "api", "", "v1", "other", "pods", "", "Pod", "", []string{"pods"}}, {"GET", "/api/v1/namespaces/other/pods", "list", "api", "", "v1", "other", "pods", "", "", []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/foo", "get", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
{"GET", "/api/v1/pods", "list", "api", "", "v1", api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}}, {"GET", "/api/v1/pods", "list", "api", "", "v1", api.NamespaceAll, "pods", "", "", []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/foo", "get", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
{"GET", "/api/v1/namespaces/other/pods", "list", "api", "", "v1", "other", "pods", "", "Pod", "", []string{"pods"}}, {"GET", "/api/v1/namespaces/other/pods", "list", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
// special verbs // special verbs
{"GET", "/api/v1/proxy/namespaces/other/pods/foo", "proxy", "api", "", "v1", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}}, {"GET", "/api/v1/proxy/namespaces/other/pods/foo", "proxy", "api", "", "v1", "other", "pods", "", "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/redirect/namespaces/other/pods/foo", "redirect", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
{"GET", "/api/v1/watch/pods", "watch", "api", "", "v1", api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}}, {"GET", "/api/v1/watch/pods", "watch", "api", "", "v1", api.NamespaceAll, "pods", "", "", []string{"pods"}},
{"GET", "/api/v1/watch/namespaces/other/pods", "watch", "api", "", "v1", "other", "pods", "", "Pod", "", []string{"pods"}}, {"GET", "/api/v1/watch/namespaces/other/pods", "watch", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
// subresource identification // subresource identification
{"GET", "/api/v1/namespaces/other/pods/foo/status", "get", "api", "", "v1", "other", "pods", "status", "Pod", "foo", []string{"pods", "foo", "status"}}, {"GET", "/api/v1/namespaces/other/pods/foo/status", "get", "api", "", "v1", "other", "pods", "status", "foo", []string{"pods", "foo", "status"}},
{"PUT", "/api/v1/namespaces/other/finalize", "update", "api", "", "v1", "other", "finalize", "", "", "", []string{"finalize"}}, {"PUT", "/api/v1/namespaces/other/finalize", "update", "api", "", "v1", "other", "finalize", "", "", []string{"finalize"}},
// verb identification // verb identification
{"PATCH", "/api/v1/namespaces/other/pods/foo", "patch", "api", "", "v1", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}}, {"PATCH", "/api/v1/namespaces/other/pods/foo", "patch", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
{"DELETE", "/api/v1/namespaces/other/pods/foo", "delete", "api", "", "v1", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}}, {"DELETE", "/api/v1/namespaces/other/pods/foo", "delete", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
{"POST", "/api/v1/namespaces/other/pods", "create", "api", "", "v1", "other", "pods", "", "Pod", "", []string{"pods"}}, {"POST", "/api/v1/namespaces/other/pods", "create", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
// api group identification // api group identification
{"POST", "/apis/experimental/v1/namespaces/other/pods", "create", "api", "experimental", "v1", "other", "pods", "", "Pod", "", []string{"pods"}}, {"POST", "/apis/experimental/v1/namespaces/other/pods", "create", "api", "experimental", "v1", "other", "pods", "", "", []string{"pods"}},
// api version identification // api version identification
{"POST", "/apis/experimental/v1beta3/namespaces/other/pods", "create", "api", "experimental", "v1beta3", "other", "pods", "", "Pod", "", []string{"pods"}}, {"POST", "/apis/experimental/v1beta3/namespaces/other/pods", "create", "api", "experimental", "v1beta3", "other", "pods", "", "", []string{"pods"}},
} }
apiRequestInfoResolver := &APIRequestInfoResolver{sets.NewString("api", "apis"), sets.NewString("api"), testapi.Default.RESTMapper()} apiRequestInfoResolver := newTestAPIRequestInfoResolver()
for _, successCase := range successCases { for _, successCase := range successCases {
req, _ := http.NewRequest(successCase.method, successCase.url, nil) req, _ := http.NewRequest(successCase.method, successCase.url, nil)
@ -260,9 +258,6 @@ func TestGetAPIRequestInfo(t *testing.T) {
if successCase.expectedNamespace != apiRequestInfo.Namespace { if successCase.expectedNamespace != apiRequestInfo.Namespace {
t.Errorf("Unexpected namespace for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedNamespace, apiRequestInfo.Namespace) t.Errorf("Unexpected namespace for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedNamespace, apiRequestInfo.Namespace)
} }
if successCase.expectedKind != apiRequestInfo.Kind {
t.Errorf("Unexpected kind for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedKind, apiRequestInfo.Kind)
}
if successCase.expectedResource != apiRequestInfo.Resource { if successCase.expectedResource != apiRequestInfo.Resource {
t.Errorf("Unexpected resource for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedResource, apiRequestInfo.Resource) t.Errorf("Unexpected resource for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedResource, apiRequestInfo.Resource)
} }

View File

@ -572,9 +572,7 @@ func (m *Master) init(c *Config) {
apiserver.InstallSupport(m.muxHelper, m.rootWebService, c.EnableProfiling, healthzChecks...) apiserver.InstallSupport(m.muxHelper, m.rootWebService, c.EnableProfiling, healthzChecks...)
apiserver.AddApiWebService(m.handlerContainer, c.APIPrefix, apiVersions) apiserver.AddApiWebService(m.handlerContainer, c.APIPrefix, apiVersions)
defaultVersion := m.defaultAPIGroupVersion() apiserver.InstallServiceErrorHandler(m.handlerContainer, m.newAPIRequestInfoResolver(), apiVersions)
requestInfoResolver := defaultVersion.GetAPIRequestInfoResolver()
apiserver.InstallServiceErrorHandler(m.handlerContainer, requestInfoResolver, apiVersions)
// allGroups records all supported groups at /apis // allGroups records all supported groups at /apis
allGroups := []api.APIGroup{} allGroups := []api.APIGroup{}
@ -608,8 +606,7 @@ func (m *Master) init(c *Config) {
} }
apiserver.AddGroupWebService(m.handlerContainer, c.APIGroupPrefix+"/"+latest.GroupOrDie("experimental").Group+"/", group) apiserver.AddGroupWebService(m.handlerContainer, c.APIGroupPrefix+"/"+latest.GroupOrDie("experimental").Group+"/", group)
allGroups = append(allGroups, group) allGroups = append(allGroups, group)
expRequestInfoResolver := expVersion.GetAPIRequestInfoResolver() apiserver.InstallServiceErrorHandler(m.handlerContainer, m.newAPIRequestInfoResolver(), []string{expVersion.Version})
apiserver.InstallServiceErrorHandler(m.handlerContainer, expRequestInfoResolver, []string{expVersion.Version})
} }
// This should be done after all groups are registered // This should be done after all groups are registered
@ -652,10 +649,7 @@ func (m *Master) init(c *Config) {
m.InsecureHandler = handler m.InsecureHandler = handler
attributeGetter := apiserver.NewRequestAttributeGetter(m.requestContextMapper, latest.GroupOrDie("").RESTMapper, attributeGetter := apiserver.NewRequestAttributeGetter(m.requestContextMapper, m.newAPIRequestInfoResolver())
[]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) handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, m.authorizer)
// Install Authenticator // Install Authenticator
@ -781,9 +775,17 @@ func (m *Master) getServersToValidate(c *Config) map[string]apiserver.Server {
return serversToValidate return serversToValidate
} }
func (m *Master) newAPIRequestInfoResolver() *apiserver.APIRequestInfoResolver {
return &apiserver.APIRequestInfoResolver{
sets.NewString(strings.Trim(m.apiPrefix, "/"), strings.Trim(thirdpartyprefix, "/")), // all possible API prefixes
sets.NewString(strings.Trim(m.apiPrefix, "/")), // APIPrefixes that won't have groups (legacy)
}
}
func (m *Master) defaultAPIGroupVersion() *apiserver.APIGroupVersion { func (m *Master) defaultAPIGroupVersion() *apiserver.APIGroupVersion {
return &apiserver.APIGroupVersion{ return &apiserver.APIGroupVersion{
Root: m.apiPrefix, Root: m.apiPrefix,
APIRequestInfoResolver: m.newAPIRequestInfoResolver(),
Mapper: latest.GroupOrDie("").RESTMapper, Mapper: latest.GroupOrDie("").RESTMapper,
@ -921,8 +923,7 @@ func (m *Master) InstallThirdPartyResource(rsrc *expapi.ThirdPartyResource) erro
} }
apiserver.AddGroupWebService(m.handlerContainer, path, apiGroup) apiserver.AddGroupWebService(m.handlerContainer, path, apiGroup)
m.addThirdPartyResourceStorage(path, thirdparty.Storage[strings.ToLower(kind)+"s"].(*thirdpartyresourcedataetcd.REST)) m.addThirdPartyResourceStorage(path, thirdparty.Storage[strings.ToLower(kind)+"s"].(*thirdpartyresourcedataetcd.REST))
thirdPartyRequestInfoResolver := &apiserver.APIRequestInfoResolver{APIPrefixes: sets.NewString(strings.Trim(thirdpartyprefix, "/")), RestMapper: thirdparty.Mapper} apiserver.InstallServiceErrorHandler(m.handlerContainer, m.newAPIRequestInfoResolver(), []string{thirdparty.Version})
apiserver.InstallServiceErrorHandler(m.handlerContainer, thirdPartyRequestInfoResolver, []string{thirdparty.Version})
return nil return nil
} }
@ -937,6 +938,8 @@ func (m *Master) thirdpartyapi(group, kind, version string) *apiserver.APIGroupV
return &apiserver.APIGroupVersion{ return &apiserver.APIGroupVersion{
Root: apiRoot, Root: apiRoot,
Version: version,
APIRequestInfoResolver: m.newAPIRequestInfoResolver(),
Creater: thirdpartyresourcedata.NewObjectCreator(version, api.Scheme), Creater: thirdpartyresourcedata.NewObjectCreator(version, api.Scheme),
Convertor: api.Scheme, Convertor: api.Scheme,
@ -946,7 +949,6 @@ func (m *Master) thirdpartyapi(group, kind, version string) *apiserver.APIGroupV
Codec: thirdpartyresourcedata.NewCodec(latest.GroupOrDie("experimental").Codec, kind), Codec: thirdpartyresourcedata.NewCodec(latest.GroupOrDie("experimental").Codec, kind),
Linker: latest.GroupOrDie("experimental").SelfLinker, Linker: latest.GroupOrDie("experimental").SelfLinker,
Storage: storage, Storage: storage,
Version: version,
ServerVersion: latest.GroupOrDie("").GroupVersion, ServerVersion: latest.GroupOrDie("").GroupVersion,
Context: m.requestContextMapper, Context: m.requestContextMapper,
@ -993,6 +995,7 @@ func (m *Master) experimental(c *Config) *apiserver.APIGroupVersion {
return &apiserver.APIGroupVersion{ return &apiserver.APIGroupVersion{
Root: m.apiGroupPrefix, Root: m.apiGroupPrefix,
APIRequestInfoResolver: m.newAPIRequestInfoResolver(),
Creater: api.Scheme, Creater: api.Scheme,
Convertor: api.Scheme, Convertor: api.Scheme,