mirror of https://github.com/k3s-io/k3s
bulk delete of tpr packages
parent
c10cc3decd
commit
254e71bfc6
|
@ -238,8 +238,6 @@ pkg/registry/core/service/ipallocator/controller
|
||||||
pkg/registry/core/service/ipallocator/storage
|
pkg/registry/core/service/ipallocator/storage
|
||||||
pkg/registry/core/serviceaccount
|
pkg/registry/core/serviceaccount
|
||||||
pkg/registry/extensions/podsecuritypolicy/storage
|
pkg/registry/extensions/podsecuritypolicy/storage
|
||||||
pkg/registry/extensions/thirdpartyresource
|
|
||||||
pkg/registry/extensions/thirdpartyresource/storage
|
|
||||||
pkg/registry/rbac/clusterrole/storage
|
pkg/registry/rbac/clusterrole/storage
|
||||||
pkg/registry/rbac/clusterrolebinding/storage
|
pkg/registry/rbac/clusterrolebinding/storage
|
||||||
pkg/registry/rbac/role/storage
|
pkg/registry/rbac/role/storage
|
||||||
|
|
|
@ -43,7 +43,6 @@ go_library(
|
||||||
"//pkg/client/clientset_generated/clientset/typed/core/v1:go_default_library",
|
"//pkg/client/clientset_generated/clientset/typed/core/v1:go_default_library",
|
||||||
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
|
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
|
||||||
"//pkg/kubelet/client:go_default_library",
|
"//pkg/kubelet/client:go_default_library",
|
||||||
"//pkg/master/thirdparty:go_default_library",
|
|
||||||
"//pkg/master/tunneler:go_default_library",
|
"//pkg/master/tunneler:go_default_library",
|
||||||
"//pkg/registry/admissionregistration/rest:go_default_library",
|
"//pkg/registry/admissionregistration/rest:go_default_library",
|
||||||
"//pkg/registry/apps/rest:go_default_library",
|
"//pkg/registry/apps/rest:go_default_library",
|
||||||
|
|
|
@ -54,7 +54,6 @@ import (
|
||||||
corev1client "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1"
|
corev1client "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1"
|
||||||
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
||||||
kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
|
kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
|
||||||
"k8s.io/kubernetes/pkg/master/thirdparty"
|
|
||||||
"k8s.io/kubernetes/pkg/master/tunneler"
|
"k8s.io/kubernetes/pkg/master/tunneler"
|
||||||
"k8s.io/kubernetes/pkg/routes"
|
"k8s.io/kubernetes/pkg/routes"
|
||||||
nodeutil "k8s.io/kubernetes/pkg/util/node"
|
nodeutil "k8s.io/kubernetes/pkg/util/node"
|
||||||
|
@ -259,7 +258,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget,
|
||||||
batchrest.RESTStorageProvider{},
|
batchrest.RESTStorageProvider{},
|
||||||
certificatesrest.RESTStorageProvider{},
|
certificatesrest.RESTStorageProvider{},
|
||||||
// TODO(enisoc): Remove crdRESTOptionsGetter input argument when TPR code is removed.
|
// TODO(enisoc): Remove crdRESTOptionsGetter input argument when TPR code is removed.
|
||||||
extensionsrest.RESTStorageProvider{ResourceInterface: thirdparty.NewThirdPartyResourceServer(s, s.DiscoveryGroupManager, c.StorageFactory, crdRESTOptionsGetter)},
|
extensionsrest.RESTStorageProvider{},
|
||||||
networkingrest.RESTStorageProvider{},
|
networkingrest.RESTStorageProvider{},
|
||||||
policyrest.RESTStorageProvider{},
|
policyrest.RESTStorageProvider{},
|
||||||
rbacrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorizer},
|
rbacrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorizer},
|
||||||
|
|
|
@ -10,44 +10,19 @@ load(
|
||||||
|
|
||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = ["tprregistration_controller.go"],
|
||||||
"thirdparty.go",
|
|
||||||
"tprregistration_controller.go",
|
|
||||||
],
|
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/api:go_default_library",
|
|
||||||
"//pkg/apis/extensions:go_default_library",
|
|
||||||
"//pkg/controller:go_default_library",
|
"//pkg/controller:go_default_library",
|
||||||
"//pkg/registry/extensions/rest:go_default_library",
|
|
||||||
"//pkg/registry/extensions/thirdpartyresourcedata:go_default_library",
|
|
||||||
"//pkg/registry/extensions/thirdpartyresourcedata/storage:go_default_library",
|
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
|
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
|
||||||
"//vendor/k8s.io/apiextensions-apiserver/pkg/apiserver:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion:go_default_library",
|
"//vendor/k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion:go_default_library",
|
||||||
"//vendor/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion:go_default_library",
|
"//vendor/k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion:go_default_library",
|
||||||
"//vendor/k8s.io/apiextensions-apiserver/pkg/registry/customresource:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/discovery:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/server/storage:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/storage/storagebackend:go_default_library",
|
|
||||||
"//vendor/k8s.io/client-go/discovery:go_default_library",
|
|
||||||
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
|
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
|
||||||
"//vendor/k8s.io/client-go/util/workqueue:go_default_library",
|
"//vendor/k8s.io/client-go/util/workqueue:go_default_library",
|
||||||
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration:go_default_library",
|
"//vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration:go_default_library",
|
||||||
|
|
|
@ -1,462 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 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 thirdparty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
|
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
|
||||||
apiextensionsserver "k8s.io/apiextensions-apiserver/pkg/apiserver"
|
|
||||||
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset/typed/apiextensions/internalversion"
|
|
||||||
"k8s.io/apiextensions-apiserver/pkg/registry/customresource"
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
|
||||||
"k8s.io/apimachinery/pkg/util/json"
|
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
|
||||||
genericapi "k8s.io/apiserver/pkg/endpoints"
|
|
||||||
"k8s.io/apiserver/pkg/endpoints/discovery"
|
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
|
||||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
|
||||||
"k8s.io/apiserver/pkg/registry/generic"
|
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
|
||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
|
||||||
serverstorgage "k8s.io/apiserver/pkg/server/storage"
|
|
||||||
"k8s.io/apiserver/pkg/storage/storagebackend"
|
|
||||||
discoveryclient "k8s.io/client-go/discovery"
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
extensionsrest "k8s.io/kubernetes/pkg/registry/extensions/rest"
|
|
||||||
"k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata"
|
|
||||||
thirdpartyresourcedatastore "k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata/storage"
|
|
||||||
)
|
|
||||||
|
|
||||||
// dynamicLister is used to list resources for dynamic third party
|
|
||||||
// apis. It implements the genericapihandlers.APIResourceLister interface
|
|
||||||
type dynamicLister struct {
|
|
||||||
m *ThirdPartyResourceServer
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d dynamicLister) ListAPIResources() []metav1.APIResource {
|
|
||||||
return d.m.getExistingThirdPartyResources(d.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ discovery.APIResourceLister = &dynamicLister{}
|
|
||||||
|
|
||||||
type ThirdPartyResourceServer struct {
|
|
||||||
genericAPIServer *genericapiserver.GenericAPIServer
|
|
||||||
|
|
||||||
availableGroupManager discovery.GroupManager
|
|
||||||
|
|
||||||
deleteCollectionWorkers int
|
|
||||||
|
|
||||||
// storage for third party objects
|
|
||||||
thirdPartyStorageConfig *storagebackend.Config
|
|
||||||
// map from api path to a tuple of (storage for the objects, APIGroup)
|
|
||||||
thirdPartyResources map[string]*thirdPartyEntry
|
|
||||||
// protects the map
|
|
||||||
thirdPartyResourcesLock sync.RWMutex
|
|
||||||
|
|
||||||
// Useful for reliable testing. Shouldn't be used otherwise.
|
|
||||||
disableThirdPartyControllerForTesting bool
|
|
||||||
|
|
||||||
crdRESTOptionsGetter generic.RESTOptionsGetter
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewThirdPartyResourceServer(genericAPIServer *genericapiserver.GenericAPIServer, availableGroupManager discovery.GroupManager, storageFactory serverstorgage.StorageFactory, crdRESTOptionsGetter generic.RESTOptionsGetter) *ThirdPartyResourceServer {
|
|
||||||
ret := &ThirdPartyResourceServer{
|
|
||||||
genericAPIServer: genericAPIServer,
|
|
||||||
thirdPartyResources: map[string]*thirdPartyEntry{},
|
|
||||||
availableGroupManager: availableGroupManager,
|
|
||||||
crdRESTOptionsGetter: crdRESTOptionsGetter,
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
ret.thirdPartyStorageConfig, err = storageFactory.NewConfig(extensions.Resource("thirdpartyresources"))
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("Error building third party storage: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// thirdPartyEntry combines objects storage and API group into one struct
|
|
||||||
// for easy lookup.
|
|
||||||
type thirdPartyEntry struct {
|
|
||||||
// Map from plural resource name to entry
|
|
||||||
storage map[string]*thirdpartyresourcedatastore.REST
|
|
||||||
group metav1.APIGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasThirdPartyResource returns true if a particular third party resource currently installed.
|
|
||||||
func (m *ThirdPartyResourceServer) HasThirdPartyResource(rsrc *extensions.ThirdPartyResource) (bool, error) {
|
|
||||||
kind, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
path := extensionsrest.MakeThirdPartyPath(group)
|
|
||||||
m.thirdPartyResourcesLock.Lock()
|
|
||||||
defer m.thirdPartyResourcesLock.Unlock()
|
|
||||||
entry := m.thirdPartyResources[path]
|
|
||||||
if entry == nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
plural, _ := meta.UnsafeGuessKindToResource(schema.GroupVersionKind{
|
|
||||||
Group: group,
|
|
||||||
Version: rsrc.Versions[0].Name,
|
|
||||||
Kind: kind,
|
|
||||||
})
|
|
||||||
_, found := entry.storage[plural.Resource]
|
|
||||||
return found, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ThirdPartyResourceServer) removeThirdPartyStorage(path, resource string) error {
|
|
||||||
m.thirdPartyResourcesLock.Lock()
|
|
||||||
defer m.thirdPartyResourcesLock.Unlock()
|
|
||||||
entry, found := m.thirdPartyResources[path]
|
|
||||||
if !found {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
storage, found := entry.storage[resource]
|
|
||||||
if !found {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := m.removeThirdPartyResourceData(&entry.group, resource, storage); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
delete(entry.storage, resource)
|
|
||||||
if len(entry.storage) == 0 {
|
|
||||||
delete(m.thirdPartyResources, path)
|
|
||||||
m.availableGroupManager.RemoveGroup(extensionsrest.GetThirdPartyGroupName(path))
|
|
||||||
} else {
|
|
||||||
m.thirdPartyResources[path] = entry
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveThirdPartyResource removes all resources matching `path`. Also deletes any stored data
|
|
||||||
func (m *ThirdPartyResourceServer) RemoveThirdPartyResource(path string) error {
|
|
||||||
ix := strings.LastIndex(path, "/")
|
|
||||||
if ix == -1 {
|
|
||||||
return fmt.Errorf("expected <api-group>/<resource-plural-name>, saw: %s", path)
|
|
||||||
}
|
|
||||||
resource := path[ix+1:]
|
|
||||||
path = path[0:ix]
|
|
||||||
|
|
||||||
if err := m.removeThirdPartyStorage(path, resource); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
services := m.genericAPIServer.Handler.GoRestfulContainer.RegisteredWebServices()
|
|
||||||
for ix := range services {
|
|
||||||
root := services[ix].RootPath()
|
|
||||||
if root == path || strings.HasPrefix(root, path+"/") {
|
|
||||||
m.genericAPIServer.Handler.GoRestfulContainer.Remove(services[ix])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ThirdPartyResourceServer) removeThirdPartyResourceData(group *metav1.APIGroup, resource string, registry *thirdpartyresourcedatastore.REST) error {
|
|
||||||
// Freeze TPR data to prevent new writes via this apiserver process.
|
|
||||||
// Other apiservers can still write. This is best-effort because there
|
|
||||||
// are worse problems with TPR data than the possibility of going back
|
|
||||||
// in time when migrating to CRD [citation needed].
|
|
||||||
registry.Freeze()
|
|
||||||
|
|
||||||
ctx := genericapirequest.NewContext()
|
|
||||||
existingData, err := registry.List(ctx, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
list, ok := existingData.(*extensions.ThirdPartyResourceDataList)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("expected a *ThirdPartyResourceDataList, got %T", existingData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate TPR data to CRD if requested.
|
|
||||||
gvk := schema.GroupVersionKind{Group: group.Name, Version: group.PreferredVersion.Version, Kind: registry.Kind()}
|
|
||||||
migrationRequested, err := m.migrateThirdPartyResourceData(gvk, resource, list)
|
|
||||||
if err != nil {
|
|
||||||
// Migration is best-effort. Log and continue.
|
|
||||||
utilruntime.HandleError(fmt.Errorf("failed to migrate TPR data: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip deletion of TPR data if migration was requested (whether or not it succeeded).
|
|
||||||
// This leaves the etcd data around for rollback, and to avoid sending DELETE watch events.
|
|
||||||
if migrationRequested {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range list.Items {
|
|
||||||
item := &list.Items[i]
|
|
||||||
|
|
||||||
// Use registry.Store.Delete() to bypass the frozen registry.Delete().
|
|
||||||
if _, _, err := registry.Store.Delete(genericapirequest.WithNamespace(ctx, item.Namespace), item.Name, nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ThirdPartyResourceServer) findMatchingCRD(gvk schema.GroupVersionKind, resource string) (*apiextensions.CustomResourceDefinition, error) {
|
|
||||||
// CustomResourceDefinitionList does not implement the protobuf marshalling interface.
|
|
||||||
config := *m.genericAPIServer.LoopbackClientConfig
|
|
||||||
config.ContentType = "application/json"
|
|
||||||
crdClient, err := apiextensionsclient.NewForConfig(&config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can't create apiextensions client: %v", err)
|
|
||||||
}
|
|
||||||
crdList, err := crdClient.CustomResourceDefinitions().List(metav1.ListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("can't list CustomResourceDefinitions: %v", err)
|
|
||||||
}
|
|
||||||
for i := range crdList.Items {
|
|
||||||
item := &crdList.Items[i]
|
|
||||||
if item.Spec.Scope == apiextensions.NamespaceScoped &&
|
|
||||||
item.Spec.Group == gvk.Group && item.Spec.Version == gvk.Version &&
|
|
||||||
item.Status.AcceptedNames.Kind == gvk.Kind && item.Status.AcceptedNames.Plural == resource {
|
|
||||||
return item, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ThirdPartyResourceServer) migrateThirdPartyResourceData(gvk schema.GroupVersionKind, resource string, dataList *extensions.ThirdPartyResourceDataList) (bool, error) {
|
|
||||||
// A matching CustomResourceDefinition implies migration is requested.
|
|
||||||
crd, err := m.findMatchingCRD(gvk, resource)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("can't determine if TPR should migrate: %v", err)
|
|
||||||
}
|
|
||||||
if crd == nil {
|
|
||||||
// No migration requested.
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Talk directly to CustomResource storage.
|
|
||||||
// We have to bypass the API server because TPR is shadowing CRD at this point.
|
|
||||||
storage := customresource.NewREST(
|
|
||||||
schema.GroupResource{Group: crd.Spec.Group, Resource: crd.Spec.Names.Plural},
|
|
||||||
schema.GroupVersionKind{Group: crd.Spec.Group, Version: crd.Spec.Version, Kind: crd.Spec.Names.ListKind},
|
|
||||||
apiextensionsserver.UnstructuredCopier{},
|
|
||||||
customresource.NewStrategy(discoveryclient.NewUnstructuredObjectTyper(nil), true, gvk),
|
|
||||||
m.crdRESTOptionsGetter,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Copy TPR data to CustomResource.
|
|
||||||
var errs []error
|
|
||||||
ctx := request.NewContext()
|
|
||||||
for i := range dataList.Items {
|
|
||||||
item := &dataList.Items[i]
|
|
||||||
|
|
||||||
// Convert TPR data to Unstructured.
|
|
||||||
objMap := make(map[string]interface{})
|
|
||||||
if err := json.Unmarshal(item.Data, &objMap); err != nil {
|
|
||||||
errs = append(errs, fmt.Errorf("can't unmarshal TPR data %q: %v", item.Name, err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert metadata to Unstructured and merge with data.
|
|
||||||
// cf. thirdpartyresourcedata.encodeToJSON()
|
|
||||||
metaMap := make(map[string]interface{})
|
|
||||||
buf, err := json.Marshal(&item.ObjectMeta)
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, fmt.Errorf("can't marshal metadata for TPR data %q: %v", item.Name, err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(buf, &metaMap); err != nil {
|
|
||||||
errs = append(errs, fmt.Errorf("can't unmarshal TPR data %q: %v", item.Name, err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// resourceVersion cannot be set when creating objects.
|
|
||||||
delete(metaMap, "resourceVersion")
|
|
||||||
objMap["metadata"] = metaMap
|
|
||||||
|
|
||||||
// Store CustomResource.
|
|
||||||
obj := &unstructured.Unstructured{Object: objMap}
|
|
||||||
createCtx := request.WithNamespace(ctx, obj.GetNamespace())
|
|
||||||
if _, err := storage.Create(createCtx, obj, false); err != nil {
|
|
||||||
errs = append(errs, fmt.Errorf("can't create CustomResource for TPR data %q: %v", item.Name, err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, utilerrors.NewAggregate(errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListThirdPartyResources lists all currently installed third party resources
|
|
||||||
// The format is <path>/<resource-plural-name>
|
|
||||||
func (m *ThirdPartyResourceServer) ListThirdPartyResources() []string {
|
|
||||||
m.thirdPartyResourcesLock.RLock()
|
|
||||||
defer m.thirdPartyResourcesLock.RUnlock()
|
|
||||||
result := []string{}
|
|
||||||
for key := range m.thirdPartyResources {
|
|
||||||
for rsrc := range m.thirdPartyResources[key].storage {
|
|
||||||
result = append(result, key+"/"+rsrc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ThirdPartyResourceServer) getExistingThirdPartyResources(path string) []metav1.APIResource {
|
|
||||||
result := []metav1.APIResource{}
|
|
||||||
m.thirdPartyResourcesLock.Lock()
|
|
||||||
defer m.thirdPartyResourcesLock.Unlock()
|
|
||||||
entry := m.thirdPartyResources[path]
|
|
||||||
if entry != nil {
|
|
||||||
for key, obj := range entry.storage {
|
|
||||||
result = append(result, metav1.APIResource{
|
|
||||||
Name: key,
|
|
||||||
Namespaced: true,
|
|
||||||
Kind: obj.Kind(),
|
|
||||||
Verbs: metav1.Verbs([]string{
|
|
||||||
"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch",
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ThirdPartyResourceServer) hasThirdPartyGroupStorage(path string) bool {
|
|
||||||
m.thirdPartyResourcesLock.Lock()
|
|
||||||
defer m.thirdPartyResourcesLock.Unlock()
|
|
||||||
_, found := m.thirdPartyResources[path]
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ThirdPartyResourceServer) addThirdPartyResourceStorage(path, resource string, storage *thirdpartyresourcedatastore.REST, apiGroup metav1.APIGroup) {
|
|
||||||
m.thirdPartyResourcesLock.Lock()
|
|
||||||
defer m.thirdPartyResourcesLock.Unlock()
|
|
||||||
entry, found := m.thirdPartyResources[path]
|
|
||||||
if entry == nil {
|
|
||||||
entry = &thirdPartyEntry{
|
|
||||||
group: apiGroup,
|
|
||||||
storage: map[string]*thirdpartyresourcedatastore.REST{},
|
|
||||||
}
|
|
||||||
m.thirdPartyResources[path] = entry
|
|
||||||
}
|
|
||||||
entry.storage[resource] = storage
|
|
||||||
if !found {
|
|
||||||
m.availableGroupManager.AddGroup(apiGroup)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstallThirdPartyResource installs a third party resource specified by 'rsrc'. When a resource is
|
|
||||||
// installed a corresponding RESTful resource is added as a valid path in the web service provided by
|
|
||||||
// the master.
|
|
||||||
//
|
|
||||||
// For example, if you install a resource ThirdPartyResource{ Name: "foo.company.com", Versions: {"v1"} }
|
|
||||||
// then the following RESTful resource is created on the server:
|
|
||||||
// http://<host>/apis/company.com/v1/foos/...
|
|
||||||
func (m *ThirdPartyResourceServer) InstallThirdPartyResource(rsrc *extensions.ThirdPartyResource) error {
|
|
||||||
kind, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(rsrc.Versions) == 0 {
|
|
||||||
return fmt.Errorf("ThirdPartyResource %s has no defined versions", rsrc.Name)
|
|
||||||
}
|
|
||||||
plural, _ := meta.UnsafeGuessKindToResource(schema.GroupVersionKind{
|
|
||||||
Group: group,
|
|
||||||
Version: rsrc.Versions[0].Name,
|
|
||||||
Kind: kind,
|
|
||||||
})
|
|
||||||
path := extensionsrest.MakeThirdPartyPath(group)
|
|
||||||
|
|
||||||
groupVersion := metav1.GroupVersionForDiscovery{
|
|
||||||
GroupVersion: group + "/" + rsrc.Versions[0].Name,
|
|
||||||
Version: rsrc.Versions[0].Name,
|
|
||||||
}
|
|
||||||
apiGroup := metav1.APIGroup{
|
|
||||||
Name: group,
|
|
||||||
Versions: []metav1.GroupVersionForDiscovery{groupVersion},
|
|
||||||
PreferredVersion: groupVersion,
|
|
||||||
}
|
|
||||||
|
|
||||||
thirdparty := m.thirdpartyapi(group, kind, rsrc.Versions[0].Name, plural.Resource)
|
|
||||||
|
|
||||||
// If storage exists, this group has already been added, just update
|
|
||||||
// the group with the new API
|
|
||||||
if m.hasThirdPartyGroupStorage(path) {
|
|
||||||
m.addThirdPartyResourceStorage(path, plural.Resource, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedatastore.REST), apiGroup)
|
|
||||||
return thirdparty.UpdateREST(m.genericAPIServer.Handler.GoRestfulContainer)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := thirdparty.InstallREST(m.genericAPIServer.Handler.GoRestfulContainer); err != nil {
|
|
||||||
glog.Errorf("Unable to setup thirdparty api: %v", err)
|
|
||||||
}
|
|
||||||
m.genericAPIServer.Handler.GoRestfulContainer.Add(discovery.NewAPIGroupHandler(api.Codecs, apiGroup, m.genericAPIServer.RequestContextMapper()).WebService())
|
|
||||||
|
|
||||||
m.addThirdPartyResourceStorage(path, plural.Resource, thirdparty.Storage[plural.Resource].(*thirdpartyresourcedatastore.REST), apiGroup)
|
|
||||||
api.Registry.AddThirdPartyAPIGroupVersions(schema.GroupVersion{Group: group, Version: rsrc.Versions[0].Name})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ThirdPartyResourceServer) thirdpartyapi(group, kind, version, pluralResource string) *genericapi.APIGroupVersion {
|
|
||||||
resourceStorage := thirdpartyresourcedatastore.NewREST(
|
|
||||||
generic.RESTOptions{
|
|
||||||
StorageConfig: m.thirdPartyStorageConfig,
|
|
||||||
Decorator: generic.UndecoratedStorage,
|
|
||||||
DeleteCollectionWorkers: m.deleteCollectionWorkers,
|
|
||||||
},
|
|
||||||
group,
|
|
||||||
kind,
|
|
||||||
)
|
|
||||||
|
|
||||||
storage := map[string]rest.Storage{
|
|
||||||
pluralResource: resourceStorage,
|
|
||||||
}
|
|
||||||
|
|
||||||
optionsExternalVersion := api.Registry.GroupOrDie(api.GroupName).GroupVersion
|
|
||||||
internalVersion := schema.GroupVersion{Group: group, Version: runtime.APIVersionInternal}
|
|
||||||
externalVersion := schema.GroupVersion{Group: group, Version: version}
|
|
||||||
|
|
||||||
apiRoot := extensionsrest.MakeThirdPartyPath("")
|
|
||||||
return &genericapi.APIGroupVersion{
|
|
||||||
Root: apiRoot,
|
|
||||||
GroupVersion: externalVersion,
|
|
||||||
|
|
||||||
Creater: thirdpartyresourcedata.NewObjectCreator(group, version, api.Scheme),
|
|
||||||
Convertor: api.Scheme,
|
|
||||||
Copier: api.Scheme,
|
|
||||||
Defaulter: api.Scheme,
|
|
||||||
Typer: api.Scheme,
|
|
||||||
UnsafeConvertor: api.Scheme,
|
|
||||||
|
|
||||||
Mapper: thirdpartyresourcedata.NewMapper(api.Registry.GroupOrDie(extensions.GroupName).RESTMapper, kind, version, group),
|
|
||||||
Linker: api.Registry.GroupOrDie(extensions.GroupName).SelfLinker,
|
|
||||||
Storage: storage,
|
|
||||||
OptionsExternalVersion: &optionsExternalVersion,
|
|
||||||
|
|
||||||
Serializer: thirdpartyresourcedata.NewNegotiatedSerializer(api.Codecs, kind, externalVersion, internalVersion),
|
|
||||||
ParameterCodec: thirdpartyresourcedata.NewThirdPartyParameterCodec(api.ParameterCodec),
|
|
||||||
|
|
||||||
Context: m.genericAPIServer.RequestContextMapper(),
|
|
||||||
|
|
||||||
MinRequestTimeout: m.genericAPIServer.MinRequestTimeout(),
|
|
||||||
|
|
||||||
ResourceLister: dynamicLister{m, extensionsrest.MakeThirdPartyPath(group)},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -71,8 +71,6 @@ filegroup(
|
||||||
"//pkg/registry/extensions/podsecuritypolicy:all-srcs",
|
"//pkg/registry/extensions/podsecuritypolicy:all-srcs",
|
||||||
"//pkg/registry/extensions/replicaset:all-srcs",
|
"//pkg/registry/extensions/replicaset:all-srcs",
|
||||||
"//pkg/registry/extensions/rest:all-srcs",
|
"//pkg/registry/extensions/rest:all-srcs",
|
||||||
"//pkg/registry/extensions/thirdpartyresource:all-srcs",
|
|
||||||
"//pkg/registry/extensions/thirdpartyresourcedata:all-srcs",
|
|
||||||
"//pkg/registry/networking/networkpolicy:all-srcs",
|
"//pkg/registry/networking/networkpolicy:all-srcs",
|
||||||
"//pkg/registry/networking/rest:all-srcs",
|
"//pkg/registry/networking/rest:all-srcs",
|
||||||
"//pkg/registry/policy/poddisruptionbudget:all-srcs",
|
"//pkg/registry/policy/poddisruptionbudget:all-srcs",
|
||||||
|
|
|
@ -5,33 +5,15 @@ licenses(["notice"])
|
||||||
load(
|
load(
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
"@io_bazel_rules_go//go:def.bzl",
|
||||||
"go_library",
|
"go_library",
|
||||||
"go_test",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = ["thirdparty_controller_test.go"],
|
|
||||||
library = ":go_default_library",
|
|
||||||
tags = ["automanaged"],
|
|
||||||
deps = [
|
|
||||||
"//pkg/apis/extensions:go_default_library",
|
|
||||||
"//pkg/registry/extensions/thirdpartyresourcedata:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = ["storage_extensions.go"],
|
||||||
"storage_extensions.go",
|
|
||||||
"thirdparty_controller.go",
|
|
||||||
],
|
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/api:go_default_library",
|
"//pkg/api:go_default_library",
|
||||||
"//pkg/apis/extensions:go_default_library",
|
"//pkg/apis/extensions:go_default_library",
|
||||||
"//pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion:go_default_library",
|
|
||||||
"//pkg/registry/extensions/controller/storage:go_default_library",
|
"//pkg/registry/extensions/controller/storage:go_default_library",
|
||||||
"//pkg/registry/extensions/daemonset/storage:go_default_library",
|
"//pkg/registry/extensions/daemonset/storage:go_default_library",
|
||||||
"//pkg/registry/extensions/deployment/storage:go_default_library",
|
"//pkg/registry/extensions/deployment/storage:go_default_library",
|
||||||
|
@ -39,11 +21,7 @@ go_library(
|
||||||
"//pkg/registry/extensions/networkpolicy/storage:go_default_library",
|
"//pkg/registry/extensions/networkpolicy/storage:go_default_library",
|
||||||
"//pkg/registry/extensions/podsecuritypolicy/storage:go_default_library",
|
"//pkg/registry/extensions/podsecuritypolicy/storage:go_default_library",
|
||||||
"//pkg/registry/extensions/replicaset/storage:go_default_library",
|
"//pkg/registry/extensions/replicaset/storage:go_default_library",
|
||||||
"//pkg/registry/extensions/thirdpartyresourcedata:go_default_library",
|
|
||||||
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
|
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||||
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
|
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
|
||||||
|
|
|
@ -34,7 +34,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type RESTStorageProvider struct {
|
type RESTStorageProvider struct {
|
||||||
ResourceInterface ResourceInterface
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) {
|
func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) {
|
||||||
|
|
|
@ -1,131 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 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 rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion"
|
|
||||||
"k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ResourceInterface is the interface for the parts of the master that know how to add/remove
|
|
||||||
// third party resources. Extracted into an interface for injection for testing.
|
|
||||||
type ResourceInterface interface {
|
|
||||||
// Remove a third party resource based on the RESTful path for that resource, the path is <api-group-path>/<resource-plural-name>
|
|
||||||
RemoveThirdPartyResource(path string) error
|
|
||||||
// Install a third party resource described by 'rsrc'
|
|
||||||
InstallThirdPartyResource(rsrc *extensions.ThirdPartyResource) error
|
|
||||||
// Is a particular third party resource currently installed?
|
|
||||||
HasThirdPartyResource(rsrc *extensions.ThirdPartyResource) (bool, error)
|
|
||||||
// List all currently installed third party resources, the returned
|
|
||||||
// names are of the form <api-group-path>/<resource-plural-name>
|
|
||||||
ListThirdPartyResources() []string
|
|
||||||
}
|
|
||||||
|
|
||||||
const thirdpartyprefix = "/apis"
|
|
||||||
|
|
||||||
// ThirdPartyController is a control loop that knows how to synchronize ThirdPartyResource objects with
|
|
||||||
// RESTful resources which are present in the API server.
|
|
||||||
type ThirdPartyController struct {
|
|
||||||
master ResourceInterface
|
|
||||||
client extensionsclient.ThirdPartyResourcesGetter
|
|
||||||
}
|
|
||||||
|
|
||||||
// SyncOneResource synchronizes a single resource with RESTful resources on the master
|
|
||||||
func (t *ThirdPartyController) SyncOneResource(rsrc *extensions.ThirdPartyResource) error {
|
|
||||||
// TODO: we also need to test if the existing installed resource matches the resource we are sync-ing.
|
|
||||||
// Currently, if there is an older, incompatible resource installed, we won't remove it. We should detect
|
|
||||||
// older, incompatible resources and remove them before testing if the resource exists.
|
|
||||||
hasResource, err := t.master.HasThirdPartyResource(rsrc)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !hasResource {
|
|
||||||
return t.master.InstallThirdPartyResource(rsrc)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Synchronize all resources with RESTful resources on the master
|
|
||||||
func (t *ThirdPartyController) SyncResources() error {
|
|
||||||
list, err := t.client.ThirdPartyResources().List(metav1.ListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return t.syncResourceList(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *ThirdPartyController) syncResourceList(list runtime.Object) error {
|
|
||||||
existing := sets.String{}
|
|
||||||
switch list := list.(type) {
|
|
||||||
case *extensions.ThirdPartyResourceList:
|
|
||||||
// Loop across all schema objects for third party resources
|
|
||||||
for ix := range list.Items {
|
|
||||||
item := &list.Items[ix]
|
|
||||||
// extract the api group and resource kind from the schema
|
|
||||||
_, group, err := thirdpartyresourcedata.ExtractApiGroupAndKind(item)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// place it in the set of resources that we expect, so that we don't delete it in the delete pass
|
|
||||||
existing.Insert(MakeThirdPartyPath(group))
|
|
||||||
// ensure a RESTful resource for this schema exists on the master
|
|
||||||
if err := t.SyncOneResource(item); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("expected a *ThirdPartyResourceList, got %#v", list)
|
|
||||||
}
|
|
||||||
// deletion phase, get all installed RESTful resources
|
|
||||||
installed := t.master.ListThirdPartyResources()
|
|
||||||
for _, installedAPI := range installed {
|
|
||||||
found := false
|
|
||||||
// search across the expected restful resources to see if this resource belongs to one of the expected ones
|
|
||||||
for _, apiPath := range existing.List() {
|
|
||||||
if installedAPI == apiPath || strings.HasPrefix(installedAPI, apiPath+"/") {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// not expected, delete the resource
|
|
||||||
if !found {
|
|
||||||
if err := t.master.RemoveThirdPartyResource(installedAPI); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeThirdPartyPath(group string) string {
|
|
||||||
if len(group) == 0 {
|
|
||||||
return thirdpartyprefix
|
|
||||||
}
|
|
||||||
return thirdpartyprefix + "/" + group
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetThirdPartyGroupName(path string) string {
|
|
||||||
return strings.TrimPrefix(strings.TrimPrefix(path, thirdpartyprefix), "/")
|
|
||||||
}
|
|
|
@ -1,177 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2014 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 rest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
|
||||||
expapi "k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
"k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FakeAPIInterface struct {
|
|
||||||
removed []string
|
|
||||||
installed []*expapi.ThirdPartyResource
|
|
||||||
apis []string
|
|
||||||
t *testing.T
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeAPIInterface) RemoveThirdPartyResource(path string) error {
|
|
||||||
f.removed = append(f.removed, path)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeAPIInterface) InstallThirdPartyResource(rsrc *expapi.ThirdPartyResource) error {
|
|
||||||
f.installed = append(f.installed, rsrc)
|
|
||||||
_, group, _ := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc)
|
|
||||||
f.apis = append(f.apis, MakeThirdPartyPath(group))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeAPIInterface) HasThirdPartyResource(rsrc *expapi.ThirdPartyResource) (bool, error) {
|
|
||||||
if f.apis == nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
_, group, _ := thirdpartyresourcedata.ExtractApiGroupAndKind(rsrc)
|
|
||||||
path := MakeThirdPartyPath(group)
|
|
||||||
for _, api := range f.apis {
|
|
||||||
if api == path {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *FakeAPIInterface) ListThirdPartyResources() []string {
|
|
||||||
return f.apis
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSyncAPIs(t *testing.T) {
|
|
||||||
resourcesNamed := func(names ...string) []expapi.ThirdPartyResource {
|
|
||||||
result := []expapi.ThirdPartyResource{}
|
|
||||||
for _, name := range names {
|
|
||||||
result = append(result, expapi.ThirdPartyResource{ObjectMeta: metav1.ObjectMeta{Name: name}})
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
list *expapi.ThirdPartyResourceList
|
|
||||||
apis []string
|
|
||||||
expectedInstalled []string
|
|
||||||
expectedRemoved []string
|
|
||||||
name string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
list: &expapi.ThirdPartyResourceList{
|
|
||||||
Items: resourcesNamed("foo.example.com"),
|
|
||||||
},
|
|
||||||
expectedInstalled: []string{"foo.example.com"},
|
|
||||||
name: "simple add",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
list: &expapi.ThirdPartyResourceList{
|
|
||||||
Items: resourcesNamed("foo.example.com"),
|
|
||||||
},
|
|
||||||
apis: []string{
|
|
||||||
"/apis/example.com",
|
|
||||||
"/apis/example.com/v1",
|
|
||||||
},
|
|
||||||
name: "does nothing",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
list: &expapi.ThirdPartyResourceList{
|
|
||||||
Items: resourcesNamed("foo.example.com"),
|
|
||||||
},
|
|
||||||
apis: []string{
|
|
||||||
"/apis/example.com",
|
|
||||||
"/apis/example.com/v1",
|
|
||||||
"/apis/example.co",
|
|
||||||
"/apis/example.co/v1",
|
|
||||||
},
|
|
||||||
name: "deletes substring API",
|
|
||||||
expectedRemoved: []string{
|
|
||||||
"/apis/example.co",
|
|
||||||
"/apis/example.co/v1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
list: &expapi.ThirdPartyResourceList{
|
|
||||||
Items: resourcesNamed("foo.example.com", "foo.company.com"),
|
|
||||||
},
|
|
||||||
apis: []string{
|
|
||||||
"/apis/company.com",
|
|
||||||
"/apis/company.com/v1",
|
|
||||||
},
|
|
||||||
expectedInstalled: []string{"foo.example.com"},
|
|
||||||
name: "adds with existing",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
list: &expapi.ThirdPartyResourceList{
|
|
||||||
Items: resourcesNamed("foo.example.com"),
|
|
||||||
},
|
|
||||||
apis: []string{
|
|
||||||
"/apis/company.com",
|
|
||||||
"/apis/company.com/v1",
|
|
||||||
},
|
|
||||||
expectedInstalled: []string{"foo.example.com"},
|
|
||||||
expectedRemoved: []string{"/apis/company.com", "/apis/company.com/v1"},
|
|
||||||
name: "removes with existing",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
fake := FakeAPIInterface{
|
|
||||||
apis: test.apis,
|
|
||||||
t: t,
|
|
||||||
}
|
|
||||||
|
|
||||||
cntrl := ThirdPartyController{master: &fake}
|
|
||||||
|
|
||||||
if err := cntrl.syncResourceList(test.list); err != nil {
|
|
||||||
t.Errorf("[%s] unexpected error: %v", test.name, err)
|
|
||||||
}
|
|
||||||
if len(test.expectedInstalled) != len(fake.installed) {
|
|
||||||
t.Errorf("[%s] unexpected installed APIs: %d, expected %d (%#v)", test.name, len(fake.installed), len(test.expectedInstalled), fake.installed[0])
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
names := sets.String{}
|
|
||||||
for ix := range fake.installed {
|
|
||||||
names.Insert(fake.installed[ix].Name)
|
|
||||||
}
|
|
||||||
for _, name := range test.expectedInstalled {
|
|
||||||
if !names.Has(name) {
|
|
||||||
t.Errorf("[%s] missing installed API: %s", test.name, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(test.expectedRemoved) != len(fake.removed) {
|
|
||||||
t.Errorf("[%s] unexpected installed APIs: %d, expected %d", test.name, len(fake.removed), len(test.expectedRemoved))
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
names := sets.String{}
|
|
||||||
names.Insert(fake.removed...)
|
|
||||||
for _, name := range test.expectedRemoved {
|
|
||||||
if !names.Has(name) {
|
|
||||||
t.Errorf("[%s] missing removed API: %s (%s)", test.name, name, names)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
licenses(["notice"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
"go_test",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = [
|
|
||||||
"doc.go",
|
|
||||||
"strategy.go",
|
|
||||||
],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
deps = [
|
|
||||||
"//pkg/api:go_default_library",
|
|
||||||
"//pkg/apis/extensions:go_default_library",
|
|
||||||
"//pkg/apis/extensions/validation:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/storage:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = ["strategy_test.go"],
|
|
||||||
library = ":go_default_library",
|
|
||||||
tags = ["automanaged"],
|
|
||||||
deps = [
|
|
||||||
"//pkg/api:go_default_library",
|
|
||||||
"//pkg/api/testapi:go_default_library",
|
|
||||||
"//pkg/api/testing:go_default_library",
|
|
||||||
"//pkg/apis/extensions:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [
|
|
||||||
":package-srcs",
|
|
||||||
"//pkg/registry/extensions/thirdpartyresource/storage:all-srcs",
|
|
||||||
],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
|
@ -1,19 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 thirdpartyresource provides Registry interface and its REST
|
|
||||||
// implementation for storing ThirdPartyResource api objects.
|
|
||||||
package thirdpartyresource // import "k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresource"
|
|
|
@ -1,55 +0,0 @@
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
licenses(["notice"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
"go_test",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = ["storage_test.go"],
|
|
||||||
library = ":go_default_library",
|
|
||||||
tags = ["automanaged"],
|
|
||||||
deps = [
|
|
||||||
"//pkg/apis/extensions:go_default_library",
|
|
||||||
"//pkg/registry/registrytest:go_default_library",
|
|
||||||
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["storage.go"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
deps = [
|
|
||||||
"//pkg/api:go_default_library",
|
|
||||||
"//pkg/apis/extensions:go_default_library",
|
|
||||||
"//pkg/registry/cachesize:go_default_library",
|
|
||||||
"//pkg/registry/extensions/thirdpartyresource:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apiserver/pkg/registry/generic"
|
|
||||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
"k8s.io/kubernetes/pkg/registry/cachesize"
|
|
||||||
"k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresource"
|
|
||||||
)
|
|
||||||
|
|
||||||
// REST implements a RESTStorage for ThirdPartyResources
|
|
||||||
type REST struct {
|
|
||||||
*genericregistry.Store
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewREST returns a registry which will store ThirdPartyResource in the given helper
|
|
||||||
func NewREST(optsGetter generic.RESTOptionsGetter) *REST {
|
|
||||||
resource := extensions.Resource("thirdpartyresources")
|
|
||||||
opts, err := optsGetter.GetRESTOptions(resource)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // TODO: Propagate error up
|
|
||||||
}
|
|
||||||
|
|
||||||
// We explicitly do NOT do any decoration here yet. // TODO determine why we do not want to cache here
|
|
||||||
opts.Decorator = generic.UndecoratedStorage
|
|
||||||
|
|
||||||
store := &genericregistry.Store{
|
|
||||||
Copier: api.Scheme,
|
|
||||||
NewFunc: func() runtime.Object { return &extensions.ThirdPartyResource{} },
|
|
||||||
NewListFunc: func() runtime.Object { return &extensions.ThirdPartyResourceList{} },
|
|
||||||
PredicateFunc: thirdpartyresource.Matcher,
|
|
||||||
QualifiedResource: resource,
|
|
||||||
WatchCacheSize: cachesize.GetWatchCacheSizeByResource(resource.Resource),
|
|
||||||
|
|
||||||
CreateStrategy: thirdpartyresource.Strategy,
|
|
||||||
UpdateStrategy: thirdpartyresource.Strategy,
|
|
||||||
DeleteStrategy: thirdpartyresource.Strategy,
|
|
||||||
}
|
|
||||||
options := &generic.StoreOptions{RESTOptions: opts, AttrFunc: thirdpartyresource.GetAttrs} // Pass in opts to use UndecoratedStorage
|
|
||||||
if err := store.CompleteWithOptions(options); err != nil {
|
|
||||||
panic(err) // TODO: Propagate error up
|
|
||||||
}
|
|
||||||
return &REST{store}
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
// Ensure that extensions/v1beta1 package is initialized.
|
|
||||||
_ "k8s.io/api/extensions/v1beta1"
|
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apiserver/pkg/registry/generic"
|
|
||||||
etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing"
|
|
||||||
"k8s.io/kubernetes/pkg/registry/registrytest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) {
|
|
||||||
etcdStorage, server := registrytest.NewEtcdStorage(t, extensions.GroupName)
|
|
||||||
restOptions := generic.RESTOptions{
|
|
||||||
StorageConfig: etcdStorage,
|
|
||||||
Decorator: generic.UndecoratedStorage,
|
|
||||||
DeleteCollectionWorkers: 1,
|
|
||||||
ResourcePrefix: "thirdpartyresources",
|
|
||||||
}
|
|
||||||
return NewREST(restOptions), server
|
|
||||||
}
|
|
||||||
|
|
||||||
func validNewThirdPartyResource(name string) *extensions.ThirdPartyResource {
|
|
||||||
return &extensions.ThirdPartyResource{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: name,
|
|
||||||
},
|
|
||||||
Versions: []extensions.APIVersion{
|
|
||||||
{
|
|
||||||
Name: "v1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func namer(i int) string {
|
|
||||||
return fmt.Sprintf("kind%d.example.com", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreate(t *testing.T) {
|
|
||||||
storage, server := newStorage(t)
|
|
||||||
defer server.Terminate(t)
|
|
||||||
defer storage.Store.DestroyFunc()
|
|
||||||
test := registrytest.New(t, storage.Store).ClusterScope().Namer(namer).GeneratesName()
|
|
||||||
rsrc := validNewThirdPartyResource("kind.domain.tld")
|
|
||||||
test.TestCreate(
|
|
||||||
// valid
|
|
||||||
rsrc,
|
|
||||||
// invalid
|
|
||||||
&extensions.ThirdPartyResource{},
|
|
||||||
&extensions.ThirdPartyResource{ObjectMeta: metav1.ObjectMeta{Name: "kind"}, Versions: []extensions.APIVersion{{Name: "v1"}}},
|
|
||||||
&extensions.ThirdPartyResource{ObjectMeta: metav1.ObjectMeta{Name: "kind.tld"}, Versions: []extensions.APIVersion{{Name: "v1"}}},
|
|
||||||
&extensions.ThirdPartyResource{ObjectMeta: metav1.ObjectMeta{Name: "kind.domain.tld"}, Versions: []extensions.APIVersion{{Name: "v.1"}}},
|
|
||||||
&extensions.ThirdPartyResource{ObjectMeta: metav1.ObjectMeta{Name: "kind.domain.tld"}, Versions: []extensions.APIVersion{{Name: "stable/v1"}}},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdate(t *testing.T) {
|
|
||||||
storage, server := newStorage(t)
|
|
||||||
defer server.Terminate(t)
|
|
||||||
defer storage.Store.DestroyFunc()
|
|
||||||
test := registrytest.New(t, storage.Store).ClusterScope().Namer(namer)
|
|
||||||
test.TestUpdate(
|
|
||||||
// valid
|
|
||||||
validNewThirdPartyResource("kind.domain.tld"),
|
|
||||||
// updateFunc
|
|
||||||
func(obj runtime.Object) runtime.Object {
|
|
||||||
object := obj.(*extensions.ThirdPartyResource)
|
|
||||||
object.Description = "new description"
|
|
||||||
return object
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDelete(t *testing.T) {
|
|
||||||
storage, server := newStorage(t)
|
|
||||||
defer server.Terminate(t)
|
|
||||||
defer storage.Store.DestroyFunc()
|
|
||||||
test := registrytest.New(t, storage.Store).ClusterScope().Namer(namer)
|
|
||||||
test.TestDelete(validNewThirdPartyResource("kind.domain.tld"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
|
||||||
storage, server := newStorage(t)
|
|
||||||
defer server.Terminate(t)
|
|
||||||
defer storage.Store.DestroyFunc()
|
|
||||||
test := registrytest.New(t, storage.Store).ClusterScope().Namer(namer)
|
|
||||||
test.TestGet(validNewThirdPartyResource("kind.domain.tld"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestList(t *testing.T) {
|
|
||||||
storage, server := newStorage(t)
|
|
||||||
defer server.Terminate(t)
|
|
||||||
defer storage.Store.DestroyFunc()
|
|
||||||
test := registrytest.New(t, storage.Store).ClusterScope().Namer(namer)
|
|
||||||
test.TestList(validNewThirdPartyResource("kind.domain.tld"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWatch(t *testing.T) {
|
|
||||||
storage, server := newStorage(t)
|
|
||||||
defer server.Terminate(t)
|
|
||||||
defer storage.Store.DestroyFunc()
|
|
||||||
test := registrytest.New(t, storage.Store).ClusterScope().Namer(namer)
|
|
||||||
test.TestWatch(
|
|
||||||
validNewThirdPartyResource("kind.domain.tld"),
|
|
||||||
// matching labels
|
|
||||||
[]labels.Set{},
|
|
||||||
// not matching labels
|
|
||||||
[]labels.Set{
|
|
||||||
{"foo": "bar"},
|
|
||||||
},
|
|
||||||
// matching fields
|
|
||||||
[]fields.Set{},
|
|
||||||
// not matching fields
|
|
||||||
[]fields.Set{
|
|
||||||
{"metadata.name": "bar"},
|
|
||||||
{"name": "foo"},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 thirdpartyresource
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
||||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
|
||||||
"k8s.io/apiserver/pkg/storage"
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions/validation"
|
|
||||||
)
|
|
||||||
|
|
||||||
// strategy implements behavior for ThirdPartyResource objects
|
|
||||||
type strategy struct {
|
|
||||||
runtime.ObjectTyper
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy is the default logic that applies when creating and updating ThirdPartyResource
|
|
||||||
// objects via the REST API.
|
|
||||||
var Strategy = strategy{api.Scheme}
|
|
||||||
|
|
||||||
var _ = rest.RESTCreateStrategy(Strategy)
|
|
||||||
|
|
||||||
var _ = rest.RESTUpdateStrategy(Strategy)
|
|
||||||
|
|
||||||
func (strategy) NamespaceScoped() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (strategy) GenerateName(base string) string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (strategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (strategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList {
|
|
||||||
return validation.ValidateThirdPartyResource(obj.(*extensions.ThirdPartyResource))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Canonicalize normalizes the object after validation.
|
|
||||||
func (strategy) Canonicalize(obj runtime.Object) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (strategy) AllowCreateOnUpdate() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (strategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (strategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList {
|
|
||||||
return validation.ValidateThirdPartyResourceUpdate(obj.(*extensions.ThirdPartyResource), old.(*extensions.ThirdPartyResource))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (strategy) AllowUnconditionalUpdate() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAttrs returns labels and fields of a given object for filtering purposes.
|
|
||||||
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) {
|
|
||||||
tpr, ok := obj.(*extensions.ThirdPartyResource)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, false, fmt.Errorf("not a ThirdPartyResource")
|
|
||||||
}
|
|
||||||
return labels.Set(tpr.Labels), SelectableFields(tpr), tpr.Initializers != nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Matcher returns a generic matcher for a given label and field selector.
|
|
||||||
func Matcher(label labels.Selector, field fields.Selector) storage.SelectionPredicate {
|
|
||||||
return storage.SelectionPredicate{
|
|
||||||
Label: label,
|
|
||||||
Field: field,
|
|
||||||
GetAttrs: GetAttrs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SelectableFields returns a field set that can be used for filter selection
|
|
||||||
func SelectableFields(obj *extensions.ThirdPartyResource) fields.Set {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 thirdpartyresource
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
_ "k8s.io/kubernetes/pkg/api"
|
|
||||||
"k8s.io/kubernetes/pkg/api/testapi"
|
|
||||||
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSelectableFieldLabelConversions(t *testing.T) {
|
|
||||||
apitesting.TestSelectableFieldLabelConversionsOfKind(t,
|
|
||||||
testapi.Extensions.GroupVersion().String(),
|
|
||||||
"ThirdPartyResource",
|
|
||||||
SelectableFields(&extensions.ThirdPartyResource{}),
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
licenses(["notice"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
"go_test",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = [
|
|
||||||
"codec.go",
|
|
||||||
"doc.go",
|
|
||||||
"registry.go",
|
|
||||||
"strategy.go",
|
|
||||||
"util.go",
|
|
||||||
],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
deps = [
|
|
||||||
"//pkg/api:go_default_library",
|
|
||||||
"//pkg/api/util:go_default_library",
|
|
||||||
"//pkg/apis/extensions:go_default_library",
|
|
||||||
"//pkg/apis/extensions/validation:go_default_library",
|
|
||||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
|
||||||
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/storage:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = [
|
|
||||||
"codec_test.go",
|
|
||||||
"strategy_test.go",
|
|
||||||
"util_test.go",
|
|
||||||
],
|
|
||||||
library = ":go_default_library",
|
|
||||||
tags = ["automanaged"],
|
|
||||||
deps = [
|
|
||||||
"//pkg/api:go_default_library",
|
|
||||||
"//pkg/api/testapi:go_default_library",
|
|
||||||
"//pkg/api/testing:go_default_library",
|
|
||||||
"//pkg/apis/extensions:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [
|
|
||||||
":package-srcs",
|
|
||||||
"//pkg/registry/extensions/thirdpartyresourcedata/storage:all-srcs",
|
|
||||||
],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
|
@ -1,593 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 thirdpartyresourcedata
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
gojson "encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
|
||||||
"k8s.io/api/extensions/v1beta1"
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/apimachinery/pkg/util/json"
|
|
||||||
"k8s.io/apimachinery/pkg/util/yaml"
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
|
||||||
apiutil "k8s.io/kubernetes/pkg/api/util"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type thirdPartyObjectConverter struct {
|
|
||||||
converter runtime.ObjectConvertor
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyObjectConverter) ConvertToVersion(in runtime.Object, outVersion runtime.GroupVersioner) (out runtime.Object, err error) {
|
|
||||||
switch in.(type) {
|
|
||||||
// This seems weird, but in this case the ThirdPartyResourceData is really just a wrapper on the raw 3rd party data.
|
|
||||||
// The actual thing printed/sent to server is the actual raw third party resource data, which only has one version.
|
|
||||||
case *extensions.ThirdPartyResourceData:
|
|
||||||
return in, nil
|
|
||||||
default:
|
|
||||||
return t.converter.ConvertToVersion(in, outVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyObjectConverter) Convert(in, out, context interface{}) error {
|
|
||||||
return t.converter.Convert(in, out, context)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyObjectConverter) ConvertFieldLabel(version, kind, label, value string) (string, string, error) {
|
|
||||||
return t.converter.ConvertFieldLabel(version, kind, label, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewThirdPartyObjectConverter(converter runtime.ObjectConvertor) runtime.ObjectConvertor {
|
|
||||||
return &thirdPartyObjectConverter{converter}
|
|
||||||
}
|
|
||||||
|
|
||||||
type thirdPartyResourceDataMapper struct {
|
|
||||||
mapper meta.RESTMapper
|
|
||||||
kind string
|
|
||||||
version string
|
|
||||||
group string
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ meta.RESTMapper = &thirdPartyResourceDataMapper{}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataMapper) getResource() schema.GroupVersionResource {
|
|
||||||
plural, _ := meta.UnsafeGuessKindToResource(t.getKind())
|
|
||||||
|
|
||||||
return plural
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataMapper) getKind() schema.GroupVersionKind {
|
|
||||||
return schema.GroupVersionKind{Group: t.group, Version: t.version, Kind: t.kind}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataMapper) isThirdPartyResource(partialResource schema.GroupVersionResource) bool {
|
|
||||||
actualResource := t.getResource()
|
|
||||||
if strings.ToLower(partialResource.Resource) != strings.ToLower(actualResource.Resource) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(partialResource.Group) != 0 && partialResource.Group != actualResource.Group {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(partialResource.Version) != 0 && partialResource.Version != actualResource.Version {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataMapper) ResourcesFor(resource schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
|
|
||||||
if t.isThirdPartyResource(resource) {
|
|
||||||
return []schema.GroupVersionResource{t.getResource()}, nil
|
|
||||||
}
|
|
||||||
return t.mapper.ResourcesFor(resource)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataMapper) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
|
|
||||||
if t.isThirdPartyResource(resource) {
|
|
||||||
return []schema.GroupVersionKind{t.getKind()}, nil
|
|
||||||
}
|
|
||||||
return t.mapper.KindsFor(resource)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataMapper) ResourceFor(resource schema.GroupVersionResource) (schema.GroupVersionResource, error) {
|
|
||||||
if t.isThirdPartyResource(resource) {
|
|
||||||
return t.getResource(), nil
|
|
||||||
}
|
|
||||||
return t.mapper.ResourceFor(resource)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
|
|
||||||
if t.isThirdPartyResource(resource) {
|
|
||||||
return t.getKind(), nil
|
|
||||||
}
|
|
||||||
return t.mapper.KindFor(resource)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) {
|
|
||||||
if len(versions) != 1 {
|
|
||||||
return nil, fmt.Errorf("unexpected set of versions: %v", versions)
|
|
||||||
}
|
|
||||||
if gk.Group != t.group {
|
|
||||||
return nil, fmt.Errorf("unknown group %q expected %s", gk.Group, t.group)
|
|
||||||
}
|
|
||||||
if gk.Kind != "ThirdPartyResourceData" {
|
|
||||||
return nil, fmt.Errorf("unknown kind %s expected %s", gk.Kind, t.kind)
|
|
||||||
}
|
|
||||||
if versions[0] != t.version {
|
|
||||||
return nil, fmt.Errorf("unknown version %q expected %q", versions[0], t.version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO figure out why we're doing this rewriting
|
|
||||||
extensionGK := schema.GroupKind{Group: extensions.GroupName, Kind: "ThirdPartyResourceData"}
|
|
||||||
|
|
||||||
mapping, err := t.mapper.RESTMapping(extensionGK, api.Registry.GroupOrDie(extensions.GroupName).GroupVersion.Version)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mapping.ObjectConvertor = &thirdPartyObjectConverter{mapping.ObjectConvertor}
|
|
||||||
return mapping, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) {
|
|
||||||
if gk.Group != t.group {
|
|
||||||
return nil, fmt.Errorf("unknown group %q expected %s", gk.Group, t.group)
|
|
||||||
}
|
|
||||||
if gk.Kind != "ThirdPartyResourceData" {
|
|
||||||
return nil, fmt.Errorf("unknown kind %s expected %s", gk.Kind, t.kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO figure out why we're doing this rewriting
|
|
||||||
extensionGK := schema.GroupKind{Group: extensions.GroupName, Kind: "ThirdPartyResourceData"}
|
|
||||||
|
|
||||||
mappings, err := t.mapper.RESTMappings(extensionGK, versions...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, m := range mappings {
|
|
||||||
m.ObjectConvertor = &thirdPartyObjectConverter{m.ObjectConvertor}
|
|
||||||
}
|
|
||||||
return mappings, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataMapper) ResourceSingularizer(resource string) (singular string, err error) {
|
|
||||||
return t.mapper.ResourceSingularizer(resource)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMapper(mapper meta.RESTMapper, kind, version, group string) meta.RESTMapper {
|
|
||||||
return &thirdPartyResourceDataMapper{
|
|
||||||
mapper: mapper,
|
|
||||||
kind: kind,
|
|
||||||
version: version,
|
|
||||||
group: group,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type thirdPartyResourceDataCodecFactory struct {
|
|
||||||
delegate runtime.NegotiatedSerializer
|
|
||||||
kind string
|
|
||||||
encodeGV schema.GroupVersion
|
|
||||||
decodeGV schema.GroupVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewNegotiatedSerializer(s runtime.NegotiatedSerializer, kind string, encodeGV, decodeGV schema.GroupVersion) runtime.NegotiatedSerializer {
|
|
||||||
return &thirdPartyResourceDataCodecFactory{
|
|
||||||
delegate: s,
|
|
||||||
kind: kind,
|
|
||||||
encodeGV: encodeGV,
|
|
||||||
decodeGV: decodeGV,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataCodecFactory) SupportedMediaTypes() []runtime.SerializerInfo {
|
|
||||||
for _, info := range t.delegate.SupportedMediaTypes() {
|
|
||||||
if info.MediaType == runtime.ContentTypeJSON {
|
|
||||||
return []runtime.SerializerInfo{info}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataCodecFactory) EncoderForVersion(s runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
|
|
||||||
return &thirdPartyResourceDataEncoder{delegate: t.delegate.EncoderForVersion(s, gv), gvk: t.encodeGV.WithKind(t.kind)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataCodecFactory) DecoderToVersion(s runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
|
|
||||||
return NewDecoder(t.delegate.DecoderToVersion(s, gv), t.kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCodec(delegate runtime.Codec, gvk schema.GroupVersionKind) runtime.Codec {
|
|
||||||
return runtime.NewCodec(NewEncoder(delegate, gvk), NewDecoder(delegate, gvk.Kind))
|
|
||||||
}
|
|
||||||
|
|
||||||
type thirdPartyResourceDataDecoder struct {
|
|
||||||
delegate runtime.Decoder
|
|
||||||
kind string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDecoder(delegate runtime.Decoder, kind string) runtime.Decoder {
|
|
||||||
return &thirdPartyResourceDataDecoder{delegate: delegate, kind: kind}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ runtime.Decoder = &thirdPartyResourceDataDecoder{}
|
|
||||||
|
|
||||||
func parseObject(data []byte) (map[string]interface{}, error) {
|
|
||||||
var mapObj map[string]interface{}
|
|
||||||
if err := json.Unmarshal(data, &mapObj); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapObj, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataDecoder) populate(data []byte) (runtime.Object, *schema.GroupVersionKind, error) {
|
|
||||||
mapObj, err := parseObject(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return t.populateFromObject(mapObj, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataDecoder) populateFromObject(mapObj map[string]interface{}, data []byte) (runtime.Object, *schema.GroupVersionKind, error) {
|
|
||||||
typeMeta := metav1.TypeMeta{}
|
|
||||||
if err := json.Unmarshal(data, &typeMeta); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
gv, err := schema.ParseGroupVersion(typeMeta.APIVersion)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
gvk := gv.WithKind(typeMeta.Kind)
|
|
||||||
|
|
||||||
isList := strings.HasSuffix(typeMeta.Kind, "List")
|
|
||||||
switch {
|
|
||||||
case !isList && (len(t.kind) == 0 || typeMeta.Kind == t.kind):
|
|
||||||
result := &extensions.ThirdPartyResourceData{}
|
|
||||||
if err := t.populateResource(result, mapObj, data); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return result, &gvk, nil
|
|
||||||
case isList && (len(t.kind) == 0 || typeMeta.Kind == t.kind+"List"):
|
|
||||||
list := &extensions.ThirdPartyResourceDataList{}
|
|
||||||
if err := t.populateListResource(list, mapObj); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return list, &gvk, nil
|
|
||||||
default:
|
|
||||||
return nil, nil, fmt.Errorf("unexpected kind: %s, expected %s", typeMeta.Kind, t.kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataDecoder) populateResource(objIn *extensions.ThirdPartyResourceData, mapObj map[string]interface{}, data []byte) error {
|
|
||||||
metadata, ok := mapObj["metadata"].(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unexpected object for metadata: %#v", mapObj["metadata"])
|
|
||||||
}
|
|
||||||
|
|
||||||
metadataData, err := json.Marshal(metadata)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(metadataData, &objIn.ObjectMeta); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override API Version with the ThirdPartyResourceData value
|
|
||||||
// TODO: fix this hard code
|
|
||||||
objIn.APIVersion = v1beta1.SchemeGroupVersion.String()
|
|
||||||
|
|
||||||
objIn.Data = data
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsThirdPartyObject(rawData []byte, gvk *schema.GroupVersionKind) (isThirdParty bool, gvkOut *schema.GroupVersionKind, err error) {
|
|
||||||
var gv schema.GroupVersion
|
|
||||||
if gvk == nil {
|
|
||||||
data, err := yaml.ToJSON(rawData)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
metadata := metav1.TypeMeta{}
|
|
||||||
if err = json.Unmarshal(data, &metadata); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
gv, err = schema.ParseGroupVersion(metadata.APIVersion)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
gvkOut = &schema.GroupVersionKind{
|
|
||||||
Group: gv.Group,
|
|
||||||
Version: gv.Version,
|
|
||||||
Kind: metadata.Kind,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
gv = gvk.GroupVersion()
|
|
||||||
gvkOut = gvk
|
|
||||||
}
|
|
||||||
return api.Registry.IsThirdPartyAPIGroupVersion(gv), gvkOut, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataDecoder) Decode(data []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
|
|
||||||
if into == nil {
|
|
||||||
if gvk == nil || gvk.Kind != t.kind {
|
|
||||||
if isThirdParty, _, err := IsThirdPartyObject(data, gvk); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
} else if !isThirdParty {
|
|
||||||
return t.delegate.Decode(data, gvk, into)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t.populate(data)
|
|
||||||
}
|
|
||||||
switch o := into.(type) {
|
|
||||||
case *extensions.ThirdPartyResourceData:
|
|
||||||
break
|
|
||||||
case *runtime.VersionedObjects:
|
|
||||||
// We're not sure that it's third party, we need to test
|
|
||||||
if gvk == nil || gvk.Kind != t.kind {
|
|
||||||
if isThirdParty, _, err := IsThirdPartyObject(data, gvk); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
} else if !isThirdParty {
|
|
||||||
return t.delegate.Decode(data, gvk, into)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
obj, outGVK, err := t.populate(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
o.Objects = []runtime.Object{
|
|
||||||
obj,
|
|
||||||
}
|
|
||||||
return o, outGVK, nil
|
|
||||||
default:
|
|
||||||
if gvk != nil && api.Registry.IsThirdPartyAPIGroupVersion(gvk.GroupVersion()) {
|
|
||||||
// delegate won't recognize a thirdparty group version
|
|
||||||
gvk = nil
|
|
||||||
}
|
|
||||||
return t.delegate.Decode(data, gvk, into)
|
|
||||||
}
|
|
||||||
|
|
||||||
thirdParty := into.(*extensions.ThirdPartyResourceData)
|
|
||||||
var mapObj map[string]interface{}
|
|
||||||
if err := json.Unmarshal(data, &mapObj); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
/*if gvk.Kind != "ThirdPartyResourceData" {
|
|
||||||
return nil, nil, fmt.Errorf("unexpected kind: %s", gvk.Kind)
|
|
||||||
}*/
|
|
||||||
actual := &schema.GroupVersionKind{}
|
|
||||||
if kindObj, found := mapObj["kind"]; !found {
|
|
||||||
if gvk == nil {
|
|
||||||
return nil, nil, runtime.NewMissingKindErr(string(data))
|
|
||||||
}
|
|
||||||
mapObj["kind"] = gvk.Kind
|
|
||||||
actual.Kind = gvk.Kind
|
|
||||||
} else {
|
|
||||||
kindStr, ok := kindObj.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, fmt.Errorf("unexpected object for 'kind': %v", kindObj)
|
|
||||||
}
|
|
||||||
if len(t.kind) > 0 && kindStr != t.kind {
|
|
||||||
return nil, nil, fmt.Errorf("kind doesn't match, expecting: %s, got %s", t.kind, kindStr)
|
|
||||||
}
|
|
||||||
actual.Kind = kindStr
|
|
||||||
}
|
|
||||||
if versionObj, found := mapObj["apiVersion"]; !found {
|
|
||||||
if gvk == nil {
|
|
||||||
return nil, nil, runtime.NewMissingVersionErr(string(data))
|
|
||||||
}
|
|
||||||
mapObj["apiVersion"] = gvk.GroupVersion().String()
|
|
||||||
actual.Group, actual.Version = gvk.Group, gvk.Version
|
|
||||||
} else {
|
|
||||||
versionStr, ok := versionObj.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, fmt.Errorf("unexpected object for 'apiVersion': %v", versionObj)
|
|
||||||
}
|
|
||||||
if gvk != nil && versionStr != gvk.GroupVersion().String() {
|
|
||||||
return nil, nil, fmt.Errorf("version doesn't match, expecting: %v, got %s", gvk.GroupVersion(), versionStr)
|
|
||||||
}
|
|
||||||
gv, err := schema.ParseGroupVersion(versionStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
actual.Group, actual.Version = gv.Group, gv.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
mapObj, err := parseObject(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, actual, err
|
|
||||||
}
|
|
||||||
if err := t.populateResource(thirdParty, mapObj, data); err != nil {
|
|
||||||
return nil, actual, err
|
|
||||||
}
|
|
||||||
return thirdParty, actual, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataDecoder) populateListResource(objIn *extensions.ThirdPartyResourceDataList, mapObj map[string]interface{}) error {
|
|
||||||
items, ok := mapObj["items"].([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("unexpected object for items: %#v", mapObj["items"])
|
|
||||||
}
|
|
||||||
objIn.Items = make([]extensions.ThirdPartyResourceData, len(items))
|
|
||||||
for ix := range items {
|
|
||||||
objData, err := json.Marshal(items[ix])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
objMap, err := parseObject(objData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := t.populateResource(&objIn.Items[ix], objMap, objData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type thirdPartyResourceDataEncoder struct {
|
|
||||||
delegate runtime.Encoder
|
|
||||||
gvk schema.GroupVersionKind
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEncoder(delegate runtime.Encoder, gvk schema.GroupVersionKind) runtime.Encoder {
|
|
||||||
return &thirdPartyResourceDataEncoder{delegate: delegate, gvk: gvk}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ runtime.Encoder = &thirdPartyResourceDataEncoder{}
|
|
||||||
|
|
||||||
func encodeToJSON(obj *extensions.ThirdPartyResourceData, stream io.Writer) error {
|
|
||||||
var objMap map[string]interface{}
|
|
||||||
if err := json.Unmarshal(obj.Data, &objMap); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
objMap["metadata"] = &obj.ObjectMeta
|
|
||||||
encoder := json.NewEncoder(stream)
|
|
||||||
return encoder.Encode(objMap)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataEncoder) Encode(obj runtime.Object, stream io.Writer) (err error) {
|
|
||||||
switch obj := obj.(type) {
|
|
||||||
case *extensions.ThirdPartyResourceData:
|
|
||||||
return encodeToJSON(obj, stream)
|
|
||||||
case *extensions.ThirdPartyResourceDataList:
|
|
||||||
// TODO: There are likely still better ways to do this...
|
|
||||||
listItems := make([]gojson.RawMessage, len(obj.Items))
|
|
||||||
|
|
||||||
for ix := range obj.Items {
|
|
||||||
buff := &bytes.Buffer{}
|
|
||||||
err := encodeToJSON(&obj.Items[ix], buff)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
listItems[ix] = gojson.RawMessage(buff.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.gvk.Empty() {
|
|
||||||
return fmt.Errorf("thirdPartyResourceDataEncoder was not given a target version")
|
|
||||||
}
|
|
||||||
|
|
||||||
encMap := struct {
|
|
||||||
// +optional
|
|
||||||
Kind string `json:"kind,omitempty"`
|
|
||||||
Items []gojson.RawMessage `json:"items"`
|
|
||||||
// +optional
|
|
||||||
Metadata metav1.ListMeta `json:"metadata,omitempty"`
|
|
||||||
// +optional
|
|
||||||
APIVersion string `json:"apiVersion,omitempty"`
|
|
||||||
}{
|
|
||||||
Kind: t.gvk.Kind + "List",
|
|
||||||
Items: listItems,
|
|
||||||
Metadata: obj.ListMeta,
|
|
||||||
APIVersion: t.gvk.GroupVersion().String(),
|
|
||||||
}
|
|
||||||
|
|
||||||
encBytes, err := json.Marshal(encMap)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = stream.Write(encBytes)
|
|
||||||
return err
|
|
||||||
case *metav1.InternalEvent:
|
|
||||||
event := &metav1.WatchEvent{}
|
|
||||||
err := metav1.Convert_versioned_InternalEvent_to_versioned_Event(obj, event, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
enc := json.NewEncoder(stream)
|
|
||||||
err = enc.Encode(event)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
case *metav1.WatchEvent:
|
|
||||||
// This is the same as the InternalEvent case above, except the caller
|
|
||||||
// already did the conversion for us (see #44350).
|
|
||||||
// In theory, we probably don't need the InternalEvent case anymore,
|
|
||||||
// but the test coverage for TPR is too low to risk removing it.
|
|
||||||
return json.NewEncoder(stream).Encode(obj)
|
|
||||||
case *metav1.Status, *metav1.APIResourceList:
|
|
||||||
return t.delegate.Encode(obj, stream)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unexpected object to encode: %#v", obj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewObjectCreator(group, version string, delegate runtime.ObjectCreater) runtime.ObjectCreater {
|
|
||||||
return &thirdPartyResourceDataCreator{group, version, delegate}
|
|
||||||
}
|
|
||||||
|
|
||||||
type thirdPartyResourceDataCreator struct {
|
|
||||||
group string
|
|
||||||
version string
|
|
||||||
delegate runtime.ObjectCreater
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataCreator) New(kind schema.GroupVersionKind) (out runtime.Object, err error) {
|
|
||||||
switch kind.Kind {
|
|
||||||
case "ThirdPartyResourceData":
|
|
||||||
if apiutil.GetGroupVersion(t.group, t.version) != kind.GroupVersion().String() {
|
|
||||||
return nil, fmt.Errorf("unknown kind %v", kind)
|
|
||||||
}
|
|
||||||
return &extensions.ThirdPartyResourceData{}, nil
|
|
||||||
case "ThirdPartyResourceDataList":
|
|
||||||
if apiutil.GetGroupVersion(t.group, t.version) != kind.GroupVersion().String() {
|
|
||||||
return nil, fmt.Errorf("unknown kind %v", kind)
|
|
||||||
}
|
|
||||||
return &extensions.ThirdPartyResourceDataList{}, nil
|
|
||||||
// TODO: this list needs to be formalized higher in the chain
|
|
||||||
case "ListOptions", "WatchEvent":
|
|
||||||
if apiutil.GetGroupVersion(t.group, t.version) == kind.GroupVersion().String() {
|
|
||||||
// Translate third party group to external group.
|
|
||||||
gvk := api.Registry.EnabledVersionsForGroup(api.GroupName)[0].WithKind(kind.Kind)
|
|
||||||
return t.delegate.New(gvk)
|
|
||||||
}
|
|
||||||
return t.delegate.New(kind)
|
|
||||||
default:
|
|
||||||
return t.delegate.New(kind)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewThirdPartyParameterCodec(p runtime.ParameterCodec) runtime.ParameterCodec {
|
|
||||||
return &thirdPartyParameterCodec{p}
|
|
||||||
}
|
|
||||||
|
|
||||||
type thirdPartyParameterCodec struct {
|
|
||||||
delegate runtime.ParameterCodec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyParameterCodec) DecodeParameters(parameters url.Values, from schema.GroupVersion, into runtime.Object) error {
|
|
||||||
return t.delegate.DecodeParameters(parameters, v1.SchemeGroupVersion, into)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *thirdPartyParameterCodec) EncodeParameters(obj runtime.Object, to schema.GroupVersion) (url.Values, error) {
|
|
||||||
return t.delegate.EncodeParameters(obj, v1.SchemeGroupVersion)
|
|
||||||
}
|
|
|
@ -1,329 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 thirdpartyresourcedata
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
|
||||||
"k8s.io/kubernetes/pkg/api/testapi"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Foo struct {
|
|
||||||
metav1.TypeMeta `json:",inline"`
|
|
||||||
metav1.ObjectMeta `json:"metadata,omitempty" description:"standard object metadata"`
|
|
||||||
|
|
||||||
SomeField string `json:"someField"`
|
|
||||||
OtherField int `json:"otherField"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Foo) GetObjectKind() schema.ObjectKind {
|
|
||||||
return schema.EmptyObjectKind
|
|
||||||
}
|
|
||||||
|
|
||||||
type FooList struct {
|
|
||||||
metav1.TypeMeta `json:",inline"`
|
|
||||||
metav1.ListMeta `json:"metadata,omitempty" description:"standard list metadata; see https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata"`
|
|
||||||
|
|
||||||
Items []Foo `json:"items"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCodec(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
into runtime.Object
|
|
||||||
obj *Foo
|
|
||||||
expectErr bool
|
|
||||||
name string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
into: &runtime.VersionedObjects{},
|
|
||||||
obj: &Foo{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
|
|
||||||
TypeMeta: metav1.TypeMeta{APIVersion: "company.com/v1", Kind: "Foo"},
|
|
||||||
},
|
|
||||||
expectErr: false,
|
|
||||||
name: "versioned objects list",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
obj: &Foo{ObjectMeta: metav1.ObjectMeta{Name: "bar"}},
|
|
||||||
expectErr: true,
|
|
||||||
name: "missing kind",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
obj: &Foo{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
|
|
||||||
TypeMeta: metav1.TypeMeta{APIVersion: "company.com/v1", Kind: "Foo"},
|
|
||||||
},
|
|
||||||
name: "basic",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
into: &extensions.ThirdPartyResourceData{},
|
|
||||||
obj: &Foo{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
|
|
||||||
TypeMeta: metav1.TypeMeta{Kind: "ThirdPartyResourceData"},
|
|
||||||
},
|
|
||||||
expectErr: true,
|
|
||||||
name: "broken kind",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
obj: &Foo{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "bar", ResourceVersion: "baz"},
|
|
||||||
TypeMeta: metav1.TypeMeta{APIVersion: "company.com/v1", Kind: "Foo"},
|
|
||||||
},
|
|
||||||
name: "resource version",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
obj: &Foo{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "bar",
|
|
||||||
CreationTimestamp: metav1.Time{Time: time.Unix(100, 0)},
|
|
||||||
},
|
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
APIVersion: "company.com/v1",
|
|
||||||
Kind: "Foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
name: "creation time",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
obj: &Foo{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "bar",
|
|
||||||
ResourceVersion: "baz",
|
|
||||||
Labels: map[string]string{"foo": "bar", "baz": "blah"},
|
|
||||||
},
|
|
||||||
TypeMeta: metav1.TypeMeta{APIVersion: "company.com/v1", Kind: "Foo"},
|
|
||||||
},
|
|
||||||
name: "labels",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
api.Registry.AddThirdPartyAPIGroupVersions(schema.GroupVersion{Group: "company.com", Version: "v1"})
|
|
||||||
for _, test := range tests {
|
|
||||||
d := &thirdPartyResourceDataDecoder{kind: "Foo", delegate: testapi.Extensions.Codec()}
|
|
||||||
e := &thirdPartyResourceDataEncoder{gvk: schema.GroupVersionKind{
|
|
||||||
Group: "company.com",
|
|
||||||
Version: "v1",
|
|
||||||
Kind: "Foo",
|
|
||||||
}, delegate: testapi.Extensions.Codec()}
|
|
||||||
data, err := json.Marshal(test.obj)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("[%s] unexpected error: %v", test.name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var obj runtime.Object
|
|
||||||
if test.into != nil {
|
|
||||||
err = runtime.DecodeInto(d, data, test.into)
|
|
||||||
obj = test.into
|
|
||||||
} else {
|
|
||||||
obj, err = runtime.Decode(d, data)
|
|
||||||
}
|
|
||||||
if err != nil && !test.expectErr {
|
|
||||||
t.Errorf("[%s] unexpected error: %v", test.name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if test.expectErr {
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("[%s] unexpected non-error", test.name)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var rsrcObj *extensions.ThirdPartyResourceData
|
|
||||||
switch o := obj.(type) {
|
|
||||||
case *extensions.ThirdPartyResourceData:
|
|
||||||
rsrcObj = o
|
|
||||||
case *runtime.VersionedObjects:
|
|
||||||
rsrcObj = o.First().(*extensions.ThirdPartyResourceData)
|
|
||||||
default:
|
|
||||||
t.Errorf("[%s] unexpected object: %v", test.name, obj)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(rsrcObj.ObjectMeta, test.obj.ObjectMeta) {
|
|
||||||
t.Errorf("[%s]\nexpected\n%v\nsaw\n%v\n", test.name, rsrcObj.ObjectMeta, test.obj.ObjectMeta)
|
|
||||||
}
|
|
||||||
var output Foo
|
|
||||||
if err := json.Unmarshal(rsrcObj.Data, &output); err != nil {
|
|
||||||
t.Errorf("[%s] unexpected error: %v", test.name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(&output, test.obj) {
|
|
||||||
t.Errorf("[%s]\nexpected\n%v\nsaw\n%v\n", test.name, test.obj, &output)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err = runtime.Encode(e, rsrcObj)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("[%s] unexpected error: %v", test.name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var output2 Foo
|
|
||||||
if err := json.Unmarshal(data, &output2); err != nil {
|
|
||||||
t.Errorf("[%s] unexpected error: %v", test.name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(&output2, test.obj) {
|
|
||||||
t.Errorf("[%s]\nexpected\n%v\nsaw\n%v\n", test.name, test.obj, &output2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreater(t *testing.T) {
|
|
||||||
creater := NewObjectCreator("creater group", "creater version", api.Scheme)
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
kind schema.GroupVersionKind
|
|
||||||
expectedObj runtime.Object
|
|
||||||
expectErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "valid ThirdPartyResourceData creation",
|
|
||||||
kind: schema.GroupVersionKind{Group: "creater group", Version: "creater version", Kind: "ThirdPartyResourceData"},
|
|
||||||
expectedObj: &extensions.ThirdPartyResourceData{},
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid ThirdPartyResourceData creation",
|
|
||||||
kind: schema.GroupVersionKind{Version: "invalid version", Kind: "ThirdPartyResourceData"},
|
|
||||||
expectedObj: nil,
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid ListOptions creation",
|
|
||||||
kind: schema.GroupVersionKind{Version: "v1", Kind: "ListOptions"},
|
|
||||||
expectedObj: &metav1.ListOptions{},
|
|
||||||
expectErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
out, err := creater.New(test.kind)
|
|
||||||
if err != nil && !test.expectErr {
|
|
||||||
t.Errorf("[%s] unexpected error: %v", test.name, err)
|
|
||||||
}
|
|
||||||
if err == nil && test.expectErr {
|
|
||||||
t.Errorf("[%s] unexpected non-error", test.name)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(test.expectedObj, out) {
|
|
||||||
t.Errorf("[%s] unexpected error: expect: %v, got: %v", test.name, test.expectedObj, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeToStreamForInternalEvent(t *testing.T) {
|
|
||||||
e := &thirdPartyResourceDataEncoder{gvk: schema.GroupVersionKind{
|
|
||||||
Group: "company.com",
|
|
||||||
Version: "v1",
|
|
||||||
Kind: "Foo",
|
|
||||||
}, delegate: testapi.Extensions.Codec()}
|
|
||||||
buf := bytes.NewBuffer([]byte{})
|
|
||||||
expected := &metav1.WatchEvent{
|
|
||||||
Type: "Added",
|
|
||||||
}
|
|
||||||
err := e.Encode(&metav1.InternalEvent{
|
|
||||||
Type: "Added",
|
|
||||||
}, buf)
|
|
||||||
|
|
||||||
jBytes, _ := json.Marshal(expected)
|
|
||||||
|
|
||||||
if string(jBytes) == buf.String() {
|
|
||||||
t.Errorf("unexpected encoding expected %s got %s", string(jBytes), buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error encoding: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestThirdPartyResourceDataListEncoding(t *testing.T) {
|
|
||||||
gv := schema.GroupVersion{Group: "stable.foo.faz", Version: "v1"}
|
|
||||||
gvk := gv.WithKind("Bar")
|
|
||||||
e := &thirdPartyResourceDataEncoder{delegate: testapi.Extensions.Codec(), gvk: gvk}
|
|
||||||
subject := &extensions.ThirdPartyResourceDataList{}
|
|
||||||
|
|
||||||
buf := bytes.NewBuffer([]byte{})
|
|
||||||
err := e.Encode(subject, buf)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("encoding unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
targetOutput := struct {
|
|
||||||
Kind string `json:"kind,omitempty"`
|
|
||||||
Items []json.RawMessage `json:"items"`
|
|
||||||
Metadata metav1.ListMeta `json:"metadata,omitempty"`
|
|
||||||
APIVersion string `json:"apiVersion,omitempty"`
|
|
||||||
}{}
|
|
||||||
err = json.Unmarshal(buf.Bytes(), &targetOutput)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unmarshal unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if expectedKind := gvk.Kind + "List"; expectedKind != targetOutput.Kind {
|
|
||||||
t.Errorf("unexpected kind on list got %s expected %s", targetOutput.Kind, expectedKind)
|
|
||||||
}
|
|
||||||
|
|
||||||
if targetOutput.Metadata != subject.ListMeta {
|
|
||||||
t.Errorf("metadata mismatch %v != %v", targetOutput.Metadata, subject.ListMeta)
|
|
||||||
}
|
|
||||||
|
|
||||||
if targetOutput.APIVersion != gv.String() {
|
|
||||||
t.Errorf("apiversion mismatch %v != %v", targetOutput.APIVersion, gv.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecodeNumbers(t *testing.T) {
|
|
||||||
gv := schema.GroupVersion{Group: "stable.foo.faz", Version: "v1"}
|
|
||||||
gvk := gv.WithKind("Foo")
|
|
||||||
e := &thirdPartyResourceDataEncoder{delegate: testapi.Extensions.Codec(), gvk: gvk}
|
|
||||||
d := &thirdPartyResourceDataDecoder{kind: "Foo", delegate: testapi.Extensions.Codec()}
|
|
||||||
|
|
||||||
// Use highest int64 number and 1000000.
|
|
||||||
subject := &extensions.ThirdPartyResourceDataList{
|
|
||||||
Items: []extensions.ThirdPartyResourceData{
|
|
||||||
{
|
|
||||||
Data: []byte(`{"num1": 9223372036854775807, "num2": 1000000}`),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode to get original JSON.
|
|
||||||
originalJSON := bytes.NewBuffer([]byte{})
|
|
||||||
err := e.Encode(subject, originalJSON)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("encoding unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode original JSON.
|
|
||||||
var into runtime.Object
|
|
||||||
into, _, err = d.Decode(originalJSON.Bytes(), &gvk, into)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("decoding unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if int is preserved.
|
|
||||||
decodedJSON := into.(*extensions.ThirdPartyResourceDataList).Items[0].Data
|
|
||||||
if !strings.Contains(string(decodedJSON), `"num1":9223372036854775807,"num2":1000000`) {
|
|
||||||
t.Errorf("Expected %s, got %s", `"num1":9223372036854775807,"num2":1000000`, string(decodedJSON))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 thirdpartyresourcedata provides Registry interface and its REST
|
|
||||||
// implementation for storing ThirdPartyResourceData api objects.
|
|
||||||
package thirdpartyresourcedata // import "k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata"
|
|
|
@ -1,83 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 thirdpartyresourcedata
|
|
||||||
|
|
||||||
import (
|
|
||||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
|
||||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Registry is an interface implemented by things that know how to store ThirdPartyResourceData objects.
|
|
||||||
type Registry interface {
|
|
||||||
ListThirdPartyResourceData(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*extensions.ThirdPartyResourceDataList, error)
|
|
||||||
WatchThirdPartyResourceData(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error)
|
|
||||||
GetThirdPartyResourceData(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*extensions.ThirdPartyResourceData, error)
|
|
||||||
CreateThirdPartyResourceData(ctx genericapirequest.Context, resource *extensions.ThirdPartyResourceData) (*extensions.ThirdPartyResourceData, error)
|
|
||||||
UpdateThirdPartyResourceData(ctx genericapirequest.Context, resource *extensions.ThirdPartyResourceData) (*extensions.ThirdPartyResourceData, error)
|
|
||||||
DeleteThirdPartyResourceData(ctx genericapirequest.Context, name string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// storage puts strong typing around storage calls
|
|
||||||
type storage struct {
|
|
||||||
rest.StandardStorage
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRegistry returns a new Registry interface for the given Storage. Any mismatched
|
|
||||||
// types will panic.
|
|
||||||
func NewRegistry(s rest.StandardStorage) Registry {
|
|
||||||
return &storage{s}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *storage) ListThirdPartyResourceData(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*extensions.ThirdPartyResourceDataList, error) {
|
|
||||||
obj, err := s.List(ctx, options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return obj.(*extensions.ThirdPartyResourceDataList), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *storage) WatchThirdPartyResourceData(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) {
|
|
||||||
return s.Watch(ctx, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *storage) GetThirdPartyResourceData(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*extensions.ThirdPartyResourceData, error) {
|
|
||||||
obj, err := s.Get(ctx, name, options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return obj.(*extensions.ThirdPartyResourceData), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *storage) CreateThirdPartyResourceData(ctx genericapirequest.Context, ThirdPartyResourceData *extensions.ThirdPartyResourceData) (*extensions.ThirdPartyResourceData, error) {
|
|
||||||
obj, err := s.Create(ctx, ThirdPartyResourceData, false)
|
|
||||||
return obj.(*extensions.ThirdPartyResourceData), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *storage) UpdateThirdPartyResourceData(ctx genericapirequest.Context, ThirdPartyResourceData *extensions.ThirdPartyResourceData) (*extensions.ThirdPartyResourceData, error) {
|
|
||||||
obj, _, err := s.Update(ctx, ThirdPartyResourceData.Name, rest.DefaultUpdatedObjectInfo(ThirdPartyResourceData, api.Scheme))
|
|
||||||
return obj.(*extensions.ThirdPartyResourceData), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *storage) DeleteThirdPartyResourceData(ctx genericapirequest.Context, name string) error {
|
|
||||||
_, _, err := s.Delete(ctx, name, nil)
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
licenses(["notice"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
"go_test",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = ["storage_test.go"],
|
|
||||||
library = ":go_default_library",
|
|
||||||
tags = ["automanaged"],
|
|
||||||
deps = [
|
|
||||||
"//pkg/apis/extensions:go_default_library",
|
|
||||||
"//pkg/registry/registrytest:go_default_library",
|
|
||||||
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/storage/etcd/testing:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["storage.go"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
deps = [
|
|
||||||
"//pkg/api:go_default_library",
|
|
||||||
"//pkg/apis/extensions:go_default_library",
|
|
||||||
"//pkg/registry/cachesize:go_default_library",
|
|
||||||
"//pkg/registry/extensions/thirdpartyresourcedata:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/internalversion:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/endpoints/request:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/generic:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/generic/registry:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
|
@ -1,127 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
|
||||||
"k8s.io/apiserver/pkg/registry/generic"
|
|
||||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
"k8s.io/kubernetes/pkg/registry/cachesize"
|
|
||||||
"k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata"
|
|
||||||
)
|
|
||||||
|
|
||||||
// errFrozen is a transient error to indicate that clients should retry with backoff.
|
|
||||||
var errFrozen = errors.NewServiceUnavailable("TPR data is temporarily frozen")
|
|
||||||
|
|
||||||
// REST implements a RESTStorage for ThirdPartyResourceData.
|
|
||||||
type REST struct {
|
|
||||||
*genericregistry.Store
|
|
||||||
kind string
|
|
||||||
frozen atomic.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Freeze causes all future calls to Create/Update/Delete/DeleteCollection to return a transient error.
|
|
||||||
// This is irreversible and meant for use when the TPR data is being deleted or migrated/abandoned.
|
|
||||||
func (r *REST) Freeze() {
|
|
||||||
r.frozen.Store(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *REST) isFrozen() bool {
|
|
||||||
return r.frozen.Load() != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create is a wrapper to support Freeze.
|
|
||||||
func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) {
|
|
||||||
if r.isFrozen() {
|
|
||||||
return nil, errFrozen
|
|
||||||
}
|
|
||||||
return r.Store.Create(ctx, obj, includeUninitialized)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update is a wrapper to support Freeze.
|
|
||||||
func (r *REST) Update(ctx genericapirequest.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) {
|
|
||||||
if r.isFrozen() {
|
|
||||||
return nil, false, errFrozen
|
|
||||||
}
|
|
||||||
return r.Store.Update(ctx, name, objInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete is a wrapper to support Freeze.
|
|
||||||
func (r *REST) Delete(ctx genericapirequest.Context, name string, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
|
||||||
if r.isFrozen() {
|
|
||||||
return nil, false, errFrozen
|
|
||||||
}
|
|
||||||
return r.Store.Delete(ctx, name, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteCollection is a wrapper to support Freeze.
|
|
||||||
func (r *REST) DeleteCollection(ctx genericapirequest.Context, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
|
|
||||||
if r.isFrozen() {
|
|
||||||
return nil, errFrozen
|
|
||||||
}
|
|
||||||
return r.Store.DeleteCollection(ctx, options, listOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewREST returns a registry which will store ThirdPartyResourceData in the given helper
|
|
||||||
func NewREST(optsGetter generic.RESTOptionsGetter, group, kind string) *REST {
|
|
||||||
resource := extensions.Resource("thirdpartyresourcedatas")
|
|
||||||
opts, err := optsGetter.GetRESTOptions(resource)
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // TODO: Propagate error up
|
|
||||||
}
|
|
||||||
|
|
||||||
// We explicitly do NOT do any decoration here yet.
|
|
||||||
opts.Decorator = generic.UndecoratedStorage // TODO use watchCacheSize=-1 to signal UndecoratedStorage
|
|
||||||
opts.ResourcePrefix = "/ThirdPartyResourceData/" + group + "/" + strings.ToLower(kind) + "s"
|
|
||||||
|
|
||||||
store := &genericregistry.Store{
|
|
||||||
Copier: api.Scheme,
|
|
||||||
NewFunc: func() runtime.Object { return &extensions.ThirdPartyResourceData{} },
|
|
||||||
NewListFunc: func() runtime.Object { return &extensions.ThirdPartyResourceDataList{} },
|
|
||||||
PredicateFunc: thirdpartyresourcedata.Matcher,
|
|
||||||
QualifiedResource: resource,
|
|
||||||
WatchCacheSize: cachesize.GetWatchCacheSizeByResource(resource.Resource),
|
|
||||||
|
|
||||||
CreateStrategy: thirdpartyresourcedata.Strategy,
|
|
||||||
UpdateStrategy: thirdpartyresourcedata.Strategy,
|
|
||||||
DeleteStrategy: thirdpartyresourcedata.Strategy,
|
|
||||||
}
|
|
||||||
options := &generic.StoreOptions{RESTOptions: opts, AttrFunc: thirdpartyresourcedata.GetAttrs} // Pass in opts to use UndecoratedStorage and custom ResourcePrefix
|
|
||||||
if err := store.CompleteWithOptions(options); err != nil {
|
|
||||||
panic(err) // TODO: Propagate error up
|
|
||||||
}
|
|
||||||
|
|
||||||
return &REST{
|
|
||||||
Store: store,
|
|
||||||
kind: kind,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implements the rest.KindProvider interface
|
|
||||||
func (r *REST) Kind() string {
|
|
||||||
return r.kind
|
|
||||||
}
|
|
|
@ -1,127 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
// Ensure that extensions/v1beta1 package is initialized.
|
|
||||||
_ "k8s.io/api/extensions/v1beta1"
|
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apiserver/pkg/registry/generic"
|
|
||||||
etcdtesting "k8s.io/apiserver/pkg/storage/etcd/testing"
|
|
||||||
"k8s.io/kubernetes/pkg/registry/registrytest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) {
|
|
||||||
etcdStorage, server := registrytest.NewEtcdStorage(t, extensions.GroupName)
|
|
||||||
restOptions := generic.RESTOptions{StorageConfig: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1}
|
|
||||||
return NewREST(restOptions, "foo", "bar"), server
|
|
||||||
}
|
|
||||||
|
|
||||||
func validNewThirdPartyResourceData(name string) *extensions.ThirdPartyResourceData {
|
|
||||||
return &extensions.ThirdPartyResourceData{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: name,
|
|
||||||
Namespace: metav1.NamespaceDefault,
|
|
||||||
},
|
|
||||||
Data: []byte("foobarbaz"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreate(t *testing.T) {
|
|
||||||
storage, server := newStorage(t)
|
|
||||||
defer server.Terminate(t)
|
|
||||||
defer storage.Store.DestroyFunc()
|
|
||||||
test := registrytest.New(t, storage.Store)
|
|
||||||
rsrc := validNewThirdPartyResourceData("foo")
|
|
||||||
rsrc.ObjectMeta = metav1.ObjectMeta{}
|
|
||||||
test.TestCreate(
|
|
||||||
// valid
|
|
||||||
rsrc,
|
|
||||||
// invalid
|
|
||||||
&extensions.ThirdPartyResourceData{},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdate(t *testing.T) {
|
|
||||||
storage, server := newStorage(t)
|
|
||||||
defer server.Terminate(t)
|
|
||||||
defer storage.Store.DestroyFunc()
|
|
||||||
test := registrytest.New(t, storage.Store)
|
|
||||||
test.TestUpdate(
|
|
||||||
// valid
|
|
||||||
validNewThirdPartyResourceData("foo"),
|
|
||||||
// updateFunc
|
|
||||||
func(obj runtime.Object) runtime.Object {
|
|
||||||
object := obj.(*extensions.ThirdPartyResourceData)
|
|
||||||
object.Data = []byte("new description")
|
|
||||||
return object
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDelete(t *testing.T) {
|
|
||||||
storage, server := newStorage(t)
|
|
||||||
defer server.Terminate(t)
|
|
||||||
defer storage.Store.DestroyFunc()
|
|
||||||
test := registrytest.New(t, storage.Store)
|
|
||||||
test.TestDelete(validNewThirdPartyResourceData("foo"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
|
||||||
storage, server := newStorage(t)
|
|
||||||
defer server.Terminate(t)
|
|
||||||
defer storage.Store.DestroyFunc()
|
|
||||||
test := registrytest.New(t, storage.Store)
|
|
||||||
test.TestGet(validNewThirdPartyResourceData("foo"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestList(t *testing.T) {
|
|
||||||
storage, server := newStorage(t)
|
|
||||||
defer server.Terminate(t)
|
|
||||||
defer storage.Store.DestroyFunc()
|
|
||||||
test := registrytest.New(t, storage.Store)
|
|
||||||
test.TestList(validNewThirdPartyResourceData("foo"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWatch(t *testing.T) {
|
|
||||||
storage, server := newStorage(t)
|
|
||||||
defer server.Terminate(t)
|
|
||||||
defer storage.Store.DestroyFunc()
|
|
||||||
test := registrytest.New(t, storage.Store)
|
|
||||||
test.TestWatch(
|
|
||||||
validNewThirdPartyResourceData("foo"),
|
|
||||||
// matching labels
|
|
||||||
[]labels.Set{},
|
|
||||||
// not matching labels
|
|
||||||
[]labels.Set{
|
|
||||||
{"foo": "bar"},
|
|
||||||
},
|
|
||||||
// matching fields
|
|
||||||
[]fields.Set{},
|
|
||||||
// not matching fields
|
|
||||||
[]fields.Set{
|
|
||||||
{"metadata.name": "bar"},
|
|
||||||
{"name": "foo"},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 thirdpartyresourcedata
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
|
||||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
|
||||||
apistorage "k8s.io/apiserver/pkg/storage"
|
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
|
||||||
"k8s.io/kubernetes/pkg/api"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions/validation"
|
|
||||||
)
|
|
||||||
|
|
||||||
// strategy implements behavior for ThirdPartyResource objects
|
|
||||||
type strategy struct {
|
|
||||||
runtime.ObjectTyper
|
|
||||||
names.NameGenerator
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy is the default logic that applies when creating and updating ThirdPartyResource
|
|
||||||
// objects via the REST API.
|
|
||||||
var Strategy = strategy{api.Scheme, names.SimpleNameGenerator}
|
|
||||||
|
|
||||||
var _ = rest.RESTCreateStrategy(Strategy)
|
|
||||||
|
|
||||||
var _ = rest.RESTUpdateStrategy(Strategy)
|
|
||||||
|
|
||||||
func (strategy) NamespaceScoped() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (strategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (strategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList {
|
|
||||||
return validation.ValidateThirdPartyResourceData(obj.(*extensions.ThirdPartyResourceData))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Canonicalize normalizes the object after validation.
|
|
||||||
func (strategy) Canonicalize(obj runtime.Object) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (strategy) AllowCreateOnUpdate() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (strategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (strategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList {
|
|
||||||
return validation.ValidateThirdPartyResourceDataUpdate(obj.(*extensions.ThirdPartyResourceData), old.(*extensions.ThirdPartyResourceData))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (strategy) AllowUnconditionalUpdate() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAttrs returns labels and fields of a given object for filtering purposes.
|
|
||||||
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, bool, error) {
|
|
||||||
tprd, ok := obj.(*extensions.ThirdPartyResourceData)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, false, fmt.Errorf("not a ThirdPartyResourceData")
|
|
||||||
}
|
|
||||||
return labels.Set(tprd.Labels), SelectableFields(tprd), tprd.Initializers != nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Matcher returns a generic matcher for a given label and field selector.
|
|
||||||
func Matcher(label labels.Selector, field fields.Selector) apistorage.SelectionPredicate {
|
|
||||||
return apistorage.SelectionPredicate{
|
|
||||||
Label: label,
|
|
||||||
Field: field,
|
|
||||||
GetAttrs: GetAttrs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SelectableFields returns a field set that can be used for filter selection
|
|
||||||
func SelectableFields(obj *extensions.ThirdPartyResourceData) fields.Set {
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 thirdpartyresourcedata
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
_ "k8s.io/kubernetes/pkg/api"
|
|
||||||
"k8s.io/kubernetes/pkg/api/testapi"
|
|
||||||
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSelectableFieldLabelConversions(t *testing.T) {
|
|
||||||
apitesting.TestSelectableFieldLabelConversionsOfKind(t,
|
|
||||||
testapi.Extensions.GroupVersion().String(),
|
|
||||||
"ThirdPartyResourceData",
|
|
||||||
SelectableFields(&extensions.ThirdPartyResourceData{}),
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 thirdpartyresourcedata
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExtractGroupVersionKind(list *extensions.ThirdPartyResourceList) ([]schema.GroupVersion, []schema.GroupVersionKind, error) {
|
|
||||||
gvs := []schema.GroupVersion{}
|
|
||||||
gvks := []schema.GroupVersionKind{}
|
|
||||||
for ix := range list.Items {
|
|
||||||
rsrc := &list.Items[ix]
|
|
||||||
kind, group, err := ExtractApiGroupAndKind(rsrc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
for _, version := range rsrc.Versions {
|
|
||||||
gv := schema.GroupVersion{Group: group, Version: version.Name}
|
|
||||||
gvs = append(gvs, gv)
|
|
||||||
gvks = append(gvks, schema.GroupVersionKind{Group: group, Version: version.Name, Kind: kind})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return gvs, gvks, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertToCamelCase(input string) string {
|
|
||||||
result := ""
|
|
||||||
toUpper := true
|
|
||||||
for ix := range input {
|
|
||||||
char := input[ix]
|
|
||||||
if toUpper {
|
|
||||||
result = result + string([]byte{(char - 32)})
|
|
||||||
toUpper = false
|
|
||||||
} else if char == '-' {
|
|
||||||
toUpper = true
|
|
||||||
} else {
|
|
||||||
result = result + string([]byte{char})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtractApiGroupAndKind(rsrc *extensions.ThirdPartyResource) (kind string, group string, err error) {
|
|
||||||
parts := strings.Split(rsrc.Name, ".")
|
|
||||||
if len(parts) < 3 {
|
|
||||||
return "", "", fmt.Errorf("unexpectedly short resource name: %s, expected at least <kind>.<domain>.<tld>", rsrc.Name)
|
|
||||||
}
|
|
||||||
return convertToCamelCase(parts[0]), strings.Join(parts[1:], "."), nil
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 thirdpartyresourcedata
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestExtractAPIGroupAndKind(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
input string
|
|
||||||
expectedKind string
|
|
||||||
expectedGroup string
|
|
||||||
expectErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: "foo.company.com",
|
|
||||||
expectedKind: "Foo",
|
|
||||||
expectedGroup: "company.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "cron-tab.company.com",
|
|
||||||
expectedKind: "CronTab",
|
|
||||||
expectedGroup: "company.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "foo",
|
|
||||||
expectErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
kind, group, err := ExtractApiGroupAndKind(&extensions.ThirdPartyResource{ObjectMeta: metav1.ObjectMeta{Name: test.input}})
|
|
||||||
if err != nil && !test.expectErr {
|
|
||||||
t.Errorf("unexpected error: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err == nil && test.expectErr {
|
|
||||||
t.Errorf("unexpected non-error")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if kind != test.expectedKind {
|
|
||||||
t.Errorf("expected: %s, saw: %s", test.expectedKind, kind)
|
|
||||||
}
|
|
||||||
if group != test.expectedGroup {
|
|
||||||
t.Errorf("expected: %s, saw: %s", test.expectedGroup, group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue