From 92f735042e1cae38afe74364c036489fb7a81973 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Mon, 13 May 2019 11:24:20 -0400 Subject: [PATCH] Add GetResourceMapper to admission ObjectInterfaces --- .../pkg/apiserver/customresource_handler.go | 15 +- .../src/k8s.io/apimachinery/pkg/runtime/BUILD | 2 + .../apimachinery/pkg/runtime/interfaces.go | 19 +++ .../k8s.io/apimachinery/pkg/runtime/mapper.go | 98 +++++++++++++ .../apimachinery/pkg/runtime/mapper_test.go | 132 ++++++++++++++++++ .../apiserver/pkg/admission/interfaces.go | 2 + .../k8s.io/apiserver/pkg/admission/util.go | 6 +- .../apiserver/pkg/endpoints/apiserver_test.go | 8 ++ .../apiserver/pkg/endpoints/groupversion.go | 2 + .../apiserver/pkg/endpoints/handlers/rest.go | 5 + .../apiserver/pkg/endpoints/installer.go | 5 + .../src/k8s.io/apiserver/pkg/server/config.go | 42 ++++-- .../apiserver/pkg/server/genericapiserver.go | 6 + 13 files changed, 330 insertions(+), 12 deletions(-) create mode 100644 staging/src/k8s.io/apimachinery/pkg/runtime/mapper.go create mode 100644 staging/src/k8s.io/apimachinery/pkg/runtime/mapper_test.go diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go index b977de65210..4471e4cc5f9 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go @@ -493,6 +493,8 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource statusScopes := map[string]*handlers.RequestScope{} scaleScopes := map[string]*handlers.RequestScope{} + equivalentResourceRegistry := runtime.NewEquivalentResourceRegistry() + structuralSchemas := map[string]*structuralschema.Structural{} for _, v := range crd.Spec.Versions { val, err := apiextensions.GetSchemaForVersion(crd, v.Name) @@ -526,7 +528,10 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource ) parameterCodec := runtime.NewParameterCodec(parameterScheme) + resource := schema.GroupVersionResource{Group: crd.Spec.Group, Version: v.Name, Resource: crd.Status.AcceptedNames.Plural} kind := schema.GroupVersionKind{Group: crd.Spec.Group, Version: v.Name, Kind: crd.Status.AcceptedNames.Kind} + equivalentResourceRegistry.RegisterKindFor(resource, "", kind) + typer := newUnstructuredObjectTyper(parameterScheme) creator := unstructuredCreator{} @@ -554,6 +559,8 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource return nil, fmt.Errorf("the server could not properly serve the CR subresources") } if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && subresources != nil && subresources.Status != nil { + equivalentResourceRegistry.RegisterKindFor(resource, "status", kind) + statusSpec = subresources.Status // for the status subresource, validate only against the status schema if validationSchema != nil && validationSchema.OpenAPIV3Schema != nil && validationSchema.OpenAPIV3Schema.Properties != nil { @@ -569,6 +576,8 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource var scaleSpec *apiextensions.CustomResourceSubresourceScale if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceSubresources) && subresources != nil && subresources.Scale != nil { + equivalentResourceRegistry.RegisterKindFor(resource, "scale", autoscalingv1.SchemeGroupVersion.WithKind("Scale")) + scaleSpec = subresources.Scale } @@ -583,8 +592,8 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource } storages[v.Name] = customresource.NewStorage( - schema.GroupResource{Group: crd.Spec.Group, Resource: crd.Status.AcceptedNames.Plural}, - schema.GroupVersionKind{Group: crd.Spec.Group, Version: v.Name, Kind: crd.Status.AcceptedNames.Kind}, + resource.GroupResource(), + kind, schema.GroupVersionKind{Group: crd.Spec.Group, Version: v.Name, Kind: crd.Status.AcceptedNames.ListKind}, customresource.NewStrategy( typer, @@ -640,6 +649,8 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource Typer: typer, UnsafeConvertor: unsafeConverter, + EquivalentResourceMapper: equivalentResourceRegistry, + Resource: schema.GroupVersionResource{Group: crd.Spec.Group, Version: v.Name, Resource: crd.Status.AcceptedNames.Plural}, Kind: kind, diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/BUILD b/staging/src/k8s.io/apimachinery/pkg/runtime/BUILD index 1b3350b7f27..f26d903a0e5 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/BUILD @@ -15,6 +15,7 @@ go_test( "embedded_test.go", "extension_test.go", "local_scheme_test.go", + "mapper_test.go", "scheme_test.go", "swagger_doc_generator_test.go", ], @@ -48,6 +49,7 @@ go_library( "generated.pb.go", "helper.go", "interfaces.go", + "mapper.go", "register.go", "scheme.go", "scheme_builder.go", diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go b/staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go index 00bffe3081c..f263d961d65 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go @@ -210,6 +210,25 @@ type ObjectCreater interface { New(kind schema.GroupVersionKind) (out Object, err error) } +// EquivalentResourceMapper provides information about resources that address the same underlying data as a specified resource +type EquivalentResourceMapper interface { + // EquivalentResourcesFor returns a list of resources that address the same underlying data as resource. + // If subresource is specified, only equivalent resources which also have the same subresource are included. + // The specified resource can be included in the returned list. + EquivalentResourcesFor(resource schema.GroupVersionResource, subresource string) []schema.GroupVersionResource + // KindFor returns the kind expected by the specified resource[/subresource]. + // A zero value is returned if the kind is unknown. + KindFor(resource schema.GroupVersionResource, subresource string) schema.GroupVersionKind +} + +// EquivalentResourceRegistry provides an EquivalentResourceMapper interface, +// and allows registering known resource[/subresource] -> kind +type EquivalentResourceRegistry interface { + EquivalentResourceMapper + // RegisterKindFor registers the existence of the specified resource[/subresource] along with its expected kind. + RegisterKindFor(resource schema.GroupVersionResource, subresource string, kind schema.GroupVersionKind) +} + // ResourceVersioner provides methods for setting and retrieving // the resource version from an API object. type ResourceVersioner interface { diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/mapper.go b/staging/src/k8s.io/apimachinery/pkg/runtime/mapper.go new file mode 100644 index 00000000000..3ff84611ab5 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/mapper.go @@ -0,0 +1,98 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package runtime + +import ( + "sync" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type equivalentResourceRegistry struct { + // keyFunc computes a key for the specified resource (this allows honoring colocated resources across API groups). + // if null, or if "" is returned, resource.String() is used as the key + keyFunc func(resource schema.GroupResource) string + // resources maps key -> subresource -> equivalent resources (subresource is not included in the returned resources). + // main resources are stored with subresource="". + resources map[string]map[string][]schema.GroupVersionResource + // kinds maps resource -> subresource -> kind + kinds map[schema.GroupVersionResource]map[string]schema.GroupVersionKind + // keys caches the computed key for each GroupResource + keys map[schema.GroupResource]string + + mutex sync.RWMutex +} + +var _ EquivalentResourceMapper = (*equivalentResourceRegistry)(nil) +var _ EquivalentResourceRegistry = (*equivalentResourceRegistry)(nil) + +// NewEquivalentResourceRegistry creates a resource registry that considers all versions of a GroupResource to be equivalent. +func NewEquivalentResourceRegistry() EquivalentResourceRegistry { + return &equivalentResourceRegistry{} +} + +// NewEquivalentResourceRegistryWithIdentity creates a resource mapper with a custom identity function. +// If "" is returned by the function, GroupResource#String is used as the identity. +// GroupResources with the same identity string are considered equivalent. +func NewEquivalentResourceRegistryWithIdentity(keyFunc func(schema.GroupResource) string) EquivalentResourceRegistry { + return &equivalentResourceRegistry{keyFunc: keyFunc} +} + +func (r *equivalentResourceRegistry) EquivalentResourcesFor(resource schema.GroupVersionResource, subresource string) []schema.GroupVersionResource { + r.mutex.RLock() + defer r.mutex.RUnlock() + return r.resources[r.keys[resource.GroupResource()]][subresource] +} +func (r *equivalentResourceRegistry) KindFor(resource schema.GroupVersionResource, subresource string) schema.GroupVersionKind { + r.mutex.RLock() + defer r.mutex.RUnlock() + return r.kinds[resource][subresource] +} +func (r *equivalentResourceRegistry) RegisterKindFor(resource schema.GroupVersionResource, subresource string, kind schema.GroupVersionKind) { + r.mutex.Lock() + defer r.mutex.Unlock() + if r.kinds == nil { + r.kinds = map[schema.GroupVersionResource]map[string]schema.GroupVersionKind{} + } + if r.kinds[resource] == nil { + r.kinds[resource] = map[string]schema.GroupVersionKind{} + } + r.kinds[resource][subresource] = kind + + // get the shared key of the parent resource + key := "" + gr := resource.GroupResource() + if r.keyFunc != nil { + key = r.keyFunc(gr) + } + if key == "" { + key = gr.String() + } + + if r.keys == nil { + r.keys = map[schema.GroupResource]string{} + } + r.keys[gr] = key + + if r.resources == nil { + r.resources = map[string]map[string][]schema.GroupVersionResource{} + } + if r.resources[key] == nil { + r.resources[key] = map[string][]schema.GroupVersionResource{} + } + r.resources[key][subresource] = append(r.resources[key][subresource], resource) +} diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/mapper_test.go b/staging/src/k8s.io/apimachinery/pkg/runtime/mapper_test.go new file mode 100644 index 00000000000..d25f4e47b2c --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/mapper_test.go @@ -0,0 +1,132 @@ +/* +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package runtime + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/diff" +) + +func TestResourceMapper(t *testing.T) { + gvr := func(g, v, r string) schema.GroupVersionResource { return schema.GroupVersionResource{g, v, r} } + + gvk := func(g, v, k string) schema.GroupVersionKind { return schema.GroupVersionKind{g, v, k} } + + kindsToRegister := []struct { + gvr schema.GroupVersionResource + subresource string + gvk schema.GroupVersionKind + }{ + // pods + {gvr("", "v1", "pods"), "", gvk("", "v1", "Pod")}, + // pods/status + {gvr("", "v1", "pods"), "status", gvk("", "v1", "Pod")}, + // deployments + {gvr("apps", "v1", "deployments"), "", gvk("apps", "v1", "Deployment")}, + {gvr("apps", "v1beta1", "deployments"), "", gvk("apps", "v1beta1", "Deployment")}, + {gvr("apps", "v1alpha1", "deployments"), "", gvk("apps", "v1alpha1", "Deployment")}, + {gvr("extensions", "v1beta1", "deployments"), "", gvk("extensions", "v1beta1", "Deployment")}, + // deployments/scale (omitted for apps/v1alpha1) + {gvr("apps", "v1", "deployments"), "scale", gvk("", "", "Scale")}, + {gvr("apps", "v1beta1", "deployments"), "scale", gvk("", "", "Scale")}, + {gvr("extensions", "v1beta1", "deployments"), "scale", gvk("", "", "Scale")}, + // deployments/status (omitted for apps/v1alpha1) + {gvr("apps", "v1", "deployments"), "status", gvk("apps", "v1", "Deployment")}, + {gvr("apps", "v1beta1", "deployments"), "status", gvk("apps", "v1beta1", "Deployment")}, + {gvr("extensions", "v1beta1", "deployments"), "status", gvk("extensions", "v1beta1", "Deployment")}, + } + + testcases := []struct { + Name string + IdentityFunc func(schema.GroupResource) string + ResourcesForV1Deployment []schema.GroupVersionResource + ResourcesForV1DeploymentScale []schema.GroupVersionResource + ResourcesForV1DeploymentStatus []schema.GroupVersionResource + }{ + { + Name: "no identityfunc", + ResourcesForV1Deployment: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("apps", "v1alpha1", "deployments")}, + ResourcesForV1DeploymentScale: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments")}, + ResourcesForV1DeploymentStatus: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments")}, + }, + { + Name: "empty identityfunc", + IdentityFunc: func(schema.GroupResource) string { return "" }, + // same group + ResourcesForV1Deployment: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("apps", "v1alpha1", "deployments")}, + ResourcesForV1DeploymentScale: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments")}, + ResourcesForV1DeploymentStatus: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments")}, + }, + { + Name: "common identityfunc", + IdentityFunc: func(schema.GroupResource) string { return "x" }, + // all resources are seen as equivalent + ResourcesForV1Deployment: []schema.GroupVersionResource{gvr("", "v1", "pods"), gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("apps", "v1alpha1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, + // all resources with scale are seen as equivalent + ResourcesForV1DeploymentScale: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, + // all resources with status are seen as equivalent + ResourcesForV1DeploymentStatus: []schema.GroupVersionResource{gvr("", "v1", "pods"), gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, + }, + { + Name: "colocated deployments", + IdentityFunc: func(resource schema.GroupResource) string { + if resource.Resource == "deployments" { + return "deployments" + } + return "" + }, + // all deployments are seen as equivalent + ResourcesForV1Deployment: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("apps", "v1alpha1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, + // all deployments with scale are seen as equivalent + ResourcesForV1DeploymentScale: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, + // all deployments with status are seen as equivalent + ResourcesForV1DeploymentStatus: []schema.GroupVersionResource{gvr("apps", "v1", "deployments"), gvr("apps", "v1beta1", "deployments"), gvr("extensions", "v1beta1", "deployments")}, + }, + } + + for _, tc := range testcases { + t.Run(tc.Name, func(t *testing.T) { + mapper := NewEquivalentResourceRegistryWithIdentity(tc.IdentityFunc) + + // register + for _, data := range kindsToRegister { + mapper.RegisterKindFor(data.gvr, data.subresource, data.gvk) + } + // verify + for _, data := range kindsToRegister { + if kind := mapper.KindFor(data.gvr, data.subresource); kind != data.gvk { + t.Errorf("KindFor(%#v, %v) returned %#v, expected %#v", data.gvr, data.subresource, kind, data.gvk) + } + } + + // Verify equivalents to primary resource + if resources := mapper.EquivalentResourcesFor(gvr("apps", "v1", "deployments"), ""); !reflect.DeepEqual(resources, tc.ResourcesForV1Deployment) { + t.Errorf("diff:\n%s", diff.ObjectReflectDiff(tc.ResourcesForV1Deployment, resources)) + } + // Verify equivalents to subresources + if resources := mapper.EquivalentResourcesFor(gvr("apps", "v1", "deployments"), "scale"); !reflect.DeepEqual(resources, tc.ResourcesForV1DeploymentScale) { + t.Errorf("diff:\n%s", diff.ObjectReflectDiff(tc.ResourcesForV1DeploymentScale, resources)) + } + if resources := mapper.EquivalentResourcesFor(gvr("apps", "v1", "deployments"), "status"); !reflect.DeepEqual(resources, tc.ResourcesForV1DeploymentStatus) { + t.Errorf("diff:\n%s", diff.ObjectReflectDiff(tc.ResourcesForV1DeploymentStatus, resources)) + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/interfaces.go b/staging/src/k8s.io/apiserver/pkg/admission/interfaces.go index a8853fdd73e..040a6268b2f 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/interfaces.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/interfaces.go @@ -76,6 +76,8 @@ type ObjectInterfaces interface { GetObjectDefaulter() runtime.ObjectDefaulter // GetObjectConvertor is the ObjectConvertor appropriate for the requested object. GetObjectConvertor() runtime.ObjectConvertor + // GetEquivalentResourceMapper is the EquivalentResourceMapper appropriate for finding equivalent resources and expected kind for the requested object. + GetEquivalentResourceMapper() runtime.EquivalentResourceMapper } // privateAnnotationsGetter is a private interface which allows users to get annotations from Attributes. diff --git a/staging/src/k8s.io/apiserver/pkg/admission/util.go b/staging/src/k8s.io/apiserver/pkg/admission/util.go index 54f188d82b6..842932f73e7 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/util.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/util.go @@ -23,10 +23,11 @@ type RuntimeObjectInterfaces struct { runtime.ObjectTyper runtime.ObjectDefaulter runtime.ObjectConvertor + runtime.EquivalentResourceMapper } func NewObjectInterfacesFromScheme(scheme *runtime.Scheme) ObjectInterfaces { - return &RuntimeObjectInterfaces{scheme, scheme, scheme, scheme} + return &RuntimeObjectInterfaces{scheme, scheme, scheme, scheme, runtime.NewEquivalentResourceRegistry()} } func (r *RuntimeObjectInterfaces) GetObjectCreater() runtime.ObjectCreater { @@ -41,3 +42,6 @@ func (r *RuntimeObjectInterfaces) GetObjectDefaulter() runtime.ObjectDefaulter { func (r *RuntimeObjectInterfaces) GetObjectConvertor() runtime.ObjectConvertor { return r.ObjectConvertor } +func (r *RuntimeObjectInterfaces) GetEquivalentResourceMapper() runtime.EquivalentResourceMapper { + return r.EquivalentResourceMapper +} diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go index 084987092ee..91ee89abe8e 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/apiserver_test.go @@ -229,6 +229,8 @@ func handleInternal(storage map[string]rest.Storage, admissionControl admission. Linker: selfLinker, RootScopedKinds: sets.NewString("SimpleRoot"), + EquivalentResourceRegistry: runtime.NewEquivalentResourceRegistry(), + ParameterCodec: parameterCodec, Admit: admissionControl, @@ -3554,6 +3556,8 @@ func TestParentResourceIsRequired(t *testing.T) { Linker: selfLinker, RootScopedKinds: sets.NewString("SimpleRoot"), + EquivalentResourceRegistry: runtime.NewEquivalentResourceRegistry(), + Admit: admissionControl, GroupVersion: newGroupVersion, @@ -3584,6 +3588,8 @@ func TestParentResourceIsRequired(t *testing.T) { Typer: scheme, Linker: selfLinker, + EquivalentResourceRegistry: runtime.NewEquivalentResourceRegistry(), + Admit: admissionControl, GroupVersion: newGroupVersion, @@ -4301,6 +4307,8 @@ func TestXGSubresource(t *testing.T) { Typer: scheme, Linker: selfLinker, + EquivalentResourceRegistry: runtime.NewEquivalentResourceRegistry(), + ParameterCodec: parameterCodec, Admit: admissionControl, diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/groupversion.go b/staging/src/k8s.io/apiserver/pkg/endpoints/groupversion.go index 79cfefe4669..e624f0f91a9 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/groupversion.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/groupversion.go @@ -72,6 +72,8 @@ type APIGroupVersion struct { Linker runtime.SelfLinker UnsafeConvertor runtime.ObjectConvertor + EquivalentResourceRegistry runtime.EquivalentResourceRegistry + // Authorizer determines whether a user is allowed to make a certain request. The Handler does a preliminary // authorization check using the request URI but it may be necessary to make additional checks, such as in // the create-on-update case diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go index cc82a8df8d0..9347b82f9d5 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/rest.go @@ -57,6 +57,8 @@ type RequestScope struct { UnsafeConvertor runtime.ObjectConvertor Authorizer authorizer.Authorizer + EquivalentResourceMapper runtime.EquivalentResourceMapper + TableConvertor rest.TableConvertor FieldManager *fieldmanager.FieldManager @@ -108,6 +110,9 @@ func (r *RequestScope) GetObjectCreater() runtime.ObjectCreater { return r.C func (r *RequestScope) GetObjectTyper() runtime.ObjectTyper { return r.Typer } func (r *RequestScope) GetObjectDefaulter() runtime.ObjectDefaulter { return r.Defaulter } func (r *RequestScope) GetObjectConvertor() runtime.ObjectConvertor { return r.Convertor } +func (r *RequestScope) GetEquivalentResourceMapper() runtime.EquivalentResourceMapper { + return r.EquivalentResourceMapper +} // ConnectResource returns a function that handles a connect request on a rest.Storage object. func ConnectResource(connecter rest.Connecter, scope *RequestScope, admit admission.Interface, restPath string, isSubresource bool) http.HandlerFunc { diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go b/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go index 1f28f70f812..d8260e88dc5 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go @@ -532,6 +532,8 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag UnsafeConvertor: a.group.UnsafeConvertor, Authorizer: a.group.Authorizer, + EquivalentResourceMapper: a.group.EquivalentResourceRegistry, + // TODO: Check for the interface on storage TableConvertor: tableProvider, @@ -925,6 +927,9 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag apiResource.Kind = gvk.Kind } + // Record the existence of the GVR and the corresponding GVK + a.group.EquivalentResourceRegistry.RegisterKindFor(reqScope.Resource, reqScope.Subresource, fqKindToRegister) + return &apiResource, nil } diff --git a/staging/src/k8s.io/apiserver/pkg/server/config.go b/staging/src/k8s.io/apiserver/pkg/server/config.go index be642ed6d40..03cf1776e80 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/config.go +++ b/staging/src/k8s.io/apiserver/pkg/server/config.go @@ -35,6 +35,7 @@ import ( "k8s.io/klog" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/util/sets" utilwaitgroup "k8s.io/apimachinery/pkg/util/waitgroup" @@ -188,6 +189,10 @@ type Config struct { // kube-proxy, services, etc.) can reach the GenericAPIServer. // If nil or 0.0.0.0, the host's default interface will be used. PublicAddress net.IP + + // EquivalentResourceRegistry provides information about resources equivalent to a given resource, + // and the kind associated with a given resource. As resources are installed, they are registered here. + EquivalentResourceRegistry runtime.EquivalentResourceRegistry } type RecommendedConfig struct { @@ -417,6 +422,21 @@ func (c *Config) Complete(informers informers.SharedInformerFactory) CompletedCo c.RequestInfoResolver = NewRequestInfoResolver(c) } + if c.EquivalentResourceRegistry == nil { + if c.RESTOptionsGetter == nil { + c.EquivalentResourceRegistry = runtime.NewEquivalentResourceRegistry() + } else { + c.EquivalentResourceRegistry = runtime.NewEquivalentResourceRegistryWithIdentity(func(groupResource schema.GroupResource) string { + // use the storage prefix as the key if possible + if opts, err := c.RESTOptionsGetter.GetRESTOptions(groupResource); err == nil { + return opts.ResourcePrefix + } + // otherwise return "" to use the default key (parent GV name) + return "" + }) + } + } + return CompletedConfig{&completedConfig{c, informers}} } @@ -436,6 +456,9 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G if c.LoopbackClientConfig == nil { return nil, fmt.Errorf("Genericapiserver.New() called with config.LoopbackClientConfig == nil") } + if c.EquivalentResourceRegistry == nil { + return nil, fmt.Errorf("Genericapiserver.New() called with config.EquivalentResourceRegistry == nil") + } handlerChainBuilder := func(handler http.Handler) http.Handler { return c.BuildHandlerChainFunc(handler, c.Config) @@ -443,15 +466,16 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G apiServerHandler := NewAPIServerHandler(name, c.Serializer, handlerChainBuilder, delegationTarget.UnprotectedHandler()) s := &GenericAPIServer{ - discoveryAddresses: c.DiscoveryAddresses, - LoopbackClientConfig: c.LoopbackClientConfig, - legacyAPIGroupPrefixes: c.LegacyAPIGroupPrefixes, - admissionControl: c.AdmissionControl, - Serializer: c.Serializer, - AuditBackend: c.AuditBackend, - Authorizer: c.Authorization.Authorizer, - delegationTarget: delegationTarget, - HandlerChainWaitGroup: c.HandlerChainWaitGroup, + discoveryAddresses: c.DiscoveryAddresses, + LoopbackClientConfig: c.LoopbackClientConfig, + legacyAPIGroupPrefixes: c.LegacyAPIGroupPrefixes, + admissionControl: c.AdmissionControl, + Serializer: c.Serializer, + AuditBackend: c.AuditBackend, + Authorizer: c.Authorization.Authorizer, + delegationTarget: delegationTarget, + EquivalentResourceRegistry: c.EquivalentResourceRegistry, + HandlerChainWaitGroup: c.HandlerChainWaitGroup, minRequestTimeout: time.Duration(c.MinRequestTimeout) * time.Second, ShutdownTimeout: c.RequestTimeout, diff --git a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go index 8523788c13c..55532c3a92d 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go +++ b/staging/src/k8s.io/apiserver/pkg/server/genericapiserver.go @@ -157,6 +157,10 @@ type GenericAPIServer struct { // the create-on-update case Authorizer authorizer.Authorizer + // EquivalentResourceRegistry provides information about resources equivalent to a given resource, + // and the kind associated with a given resource. As resources are installed, they are registered here. + EquivalentResourceRegistry runtime.EquivalentResourceRegistry + // enableAPIResponseCompression indicates whether API Responses should support compression // if the client requests it via Accept-Encoding enableAPIResponseCompression bool @@ -467,6 +471,8 @@ func (s *GenericAPIServer) newAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupV Typer: apiGroupInfo.Scheme, Linker: runtime.SelfLinker(meta.NewAccessor()), + EquivalentResourceRegistry: s.EquivalentResourceRegistry, + Admit: s.admissionControl, MinRequestTimeout: s.minRequestTimeout, EnableAPIResponseCompression: s.enableAPIResponseCompression,