handle registered third parties

This commit is contained in:
deads2k 2017-03-27 16:12:17 -04:00
parent 5baa947c8c
commit b512073457
19 changed files with 1104 additions and 21 deletions

View File

@ -28,15 +28,15 @@ import (
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
)
// apiGroupHandler creates a webservice serving the supported versions, preferred version, and name
// APIGroupHandler creates a webservice serving the supported versions, preferred version, and name
// of a group. E.g., such a web service will be registered at /apis/extensions.
type apiGroupHandler struct {
type APIGroupHandler struct {
serializer runtime.NegotiatedSerializer
group metav1.APIGroup
}
func NewAPIGroupHandler(serializer runtime.NegotiatedSerializer, group metav1.APIGroup) *apiGroupHandler {
func NewAPIGroupHandler(serializer runtime.NegotiatedSerializer, group metav1.APIGroup) *APIGroupHandler {
if keepUnversioned(group.Name) {
// Because in release 1.1, /apis/extensions returns response with empty
// APIVersion, we use stripVersionNegotiatedSerializer to keep the
@ -44,13 +44,13 @@ func NewAPIGroupHandler(serializer runtime.NegotiatedSerializer, group metav1.AP
serializer = stripVersionNegotiatedSerializer{serializer}
}
return &apiGroupHandler{
return &APIGroupHandler{
serializer: serializer,
group: group,
}
}
func (s *apiGroupHandler) WebService() *restful.WebService {
func (s *APIGroupHandler) WebService() *restful.WebService {
mediaTypes, _ := negotiation.MediaTypesForSerializer(s.serializer)
ws := new(restful.WebService)
ws.Path(APIGroupPrefix + "/" + s.group.Name)
@ -65,6 +65,10 @@ func (s *apiGroupHandler) WebService() *restful.WebService {
}
// handle returns a handler which will return the api.GroupAndVersion of the group.
func (s *apiGroupHandler) handle(req *restful.Request, resp *restful.Response) {
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &s.group)
func (s *APIGroupHandler) handle(req *restful.Request, resp *restful.Response) {
s.ServeHTTP(resp.ResponseWriter, req.Request)
}
func (s *APIGroupHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, w, req, http.StatusOK, &s.group)
}

View File

@ -32,16 +32,22 @@ type APIResourceLister interface {
ListAPIResources() []metav1.APIResource
}
// apiVersionHandler creates a webservice serving the supported resources for the version
type APIResourceListerFunc func() []metav1.APIResource
func (f APIResourceListerFunc) ListAPIResources() []metav1.APIResource {
return f()
}
// APIVersionHandler creates a webservice serving the supported resources for the version
// E.g., such a web service will be registered at /apis/extensions/v1beta1.
type apiVersionHandler struct {
type APIVersionHandler struct {
serializer runtime.NegotiatedSerializer
groupVersion schema.GroupVersion
apiResourceLister APIResourceLister
}
func NewAPIVersionHandler(serializer runtime.NegotiatedSerializer, groupVersion schema.GroupVersion, apiResourceLister APIResourceLister) *apiVersionHandler {
func NewAPIVersionHandler(serializer runtime.NegotiatedSerializer, groupVersion schema.GroupVersion, apiResourceLister APIResourceLister) *APIVersionHandler {
if keepUnversioned(groupVersion.Group) {
// Because in release 1.1, /apis/extensions returns response with empty
// APIVersion, we use stripVersionNegotiatedSerializer to keep the
@ -49,14 +55,14 @@ func NewAPIVersionHandler(serializer runtime.NegotiatedSerializer, groupVersion
serializer = stripVersionNegotiatedSerializer{serializer}
}
return &apiVersionHandler{
return &APIVersionHandler{
serializer: serializer,
groupVersion: groupVersion,
apiResourceLister: apiResourceLister,
}
}
func (s *apiVersionHandler) AddToWebService(ws *restful.WebService) {
func (s *APIVersionHandler) AddToWebService(ws *restful.WebService) {
mediaTypes, _ := negotiation.MediaTypesForSerializer(s.serializer)
ws.Route(ws.GET("/").To(s.handle).
Doc("get available resources").
@ -67,7 +73,11 @@ func (s *apiVersionHandler) AddToWebService(ws *restful.WebService) {
}
// handle returns a handler which will return the api.VersionAndVersion of the group.
func (s *apiVersionHandler) handle(req *restful.Request, resp *restful.Response) {
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK,
func (s *APIVersionHandler) handle(req *restful.Request, resp *restful.Response) {
s.ServeHTTP(resp.ResponseWriter, req.Request)
}
func (s *APIVersionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
responsewriters.WriteObjectNegotiated(s.serializer, schema.GroupVersion{}, w, req, http.StatusOK,
&metav1.APIResourceList{GroupVersion: s.groupVersion.String(), APIResources: s.apiResourceLister.ListAPIResources()})
}

View File

@ -0,0 +1,12 @@
apiVersion: apiregistration.k8s.io/v1alpha1
kind: APIService
metadata:
name: v1alpha1.mygroup.example.com
spec:
insecureSkipTLSVerify: true
group: mygroup.example.com
priority: 500
service:
name: api
namespace: kube-apiextensions
version: v1alpha1

View File

@ -5,6 +5,7 @@ metadata:
spec:
group: mygroup.example.com
version: v1alpha1
scope: Namespaced
names:
name: noxus
singular: noxu

View File

@ -0,0 +1,6 @@
apiVersion: mygroup.example.com/v1alpha1
kind: Noxu
metadata:
name: alfa-noxu
spec:
key: value

View File

@ -33,7 +33,7 @@ type CustomResourceSpec struct {
// CustomResourceNames indicates the names to serve this CustomResource
type CustomResourceNames struct {
// Plural is the plural name of the resource to serve. It must match the name of the TPR-registration
// Plural is the plural name of the resource to serve. It must match the name of the CustomResource-registration
// too: plural.group and it must be all lowercase.
Plural string
// Singular is the singular name of the resource. It must be all lowercase Defaults to lowercased <kind>

View File

@ -33,7 +33,7 @@ type CustomResourceSpec struct {
// CustomResourceNames indicates the names to serve this CustomResource
type CustomResourceNames struct {
// Plural is the plural name of the resource to serve. It must match the name of the TPR-registration
// Plural is the plural name of the resource to serve. It must match the name of the CustomResource-registration
// too: plural.group and it must be all lowercase.
Plural string `json:"plural" protobuf:"bytes,1,opt,name=plural"`
// Singular is the singular name of the resource. It must be all lowercase Defaults to lowercased <kind>

View File

@ -9,18 +9,42 @@ load(
go_library(
name = "go_default_library",
srcs = ["apiserver.go"],
srcs = [
"apiserver.go",
"customresource_discovery.go",
"customresource_discovery_controller.go",
"customresource_handler.go",
],
tags = ["automanaged"],
deps = [
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apimachinery/registered: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/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types: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/version:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/discovery:go_default_library",
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers: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",
"//vendor/k8s.io/apiserver/pkg/server: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/util/workqueue:go_default_library",
"//vendor/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions:go_default_library",
"//vendor/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/install:go_default_library",
"//vendor/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1:go_default_library",
@ -28,6 +52,9 @@ go_library(
"//vendor/k8s.io/kube-apiextensions-server/pkg/client/clientset/internalclientset:go_default_library",
"//vendor/k8s.io/kube-apiextensions-server/pkg/client/informers/externalversions:go_default_library",
"//vendor/k8s.io/kube-apiextensions-server/pkg/client/informers/internalversion:go_default_library",
"//vendor/k8s.io/kube-apiextensions-server/pkg/client/informers/internalversion/apiextensions/internalversion:go_default_library",
"//vendor/k8s.io/kube-apiextensions-server/pkg/client/listers/apiextensions/internalversion:go_default_library",
"//vendor/k8s.io/kube-apiextensions-server/pkg/registry/customresource:go_default_library",
"//vendor/k8s.io/kube-apiextensions-server/pkg/registry/customresourcestorage:go_default_library",
],
)

View File

@ -17,6 +17,8 @@ limitations under the License.
package apiserver
import (
"time"
"k8s.io/apimachinery/pkg/apimachinery/announced"
"k8s.io/apimachinery/pkg/apimachinery/registered"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -24,17 +26,20 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/version"
"k8s.io/apiserver/pkg/endpoints/discovery"
genericregistry "k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/kube-apiextensions-server/pkg/apis/apiextensions"
"k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/install"
"k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1"
"k8s.io/kube-apiextensions-server/pkg/client/clientset/internalclientset"
internalinformers "k8s.io/kube-apiextensions-server/pkg/client/informers/internalversion"
"k8s.io/kube-apiextensions-server/pkg/registry/customresource"
// make sure the generated client works
_ "k8s.io/kube-apiextensions-server/pkg/client/clientset/clientset"
_ "k8s.io/kube-apiextensions-server/pkg/client/clientset/internalclientset"
_ "k8s.io/kube-apiextensions-server/pkg/client/informers/externalversions"
_ "k8s.io/kube-apiextensions-server/pkg/client/informers/internalversion"
)
@ -64,6 +69,8 @@ func init() {
type Config struct {
GenericConfig *genericapiserver.Config
CustomResourceRESTOptionsGetter genericregistry.RESTOptionsGetter
}
type CustomResources struct {
@ -76,6 +83,7 @@ type completedConfig struct {
// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
func (c *Config) Complete() completedConfig {
c.GenericConfig.EnableDiscovery = false
c.GenericConfig.Complete()
c.GenericConfig.Version = &version.Info{
@ -92,7 +100,7 @@ func (c *Config) SkipComplete() completedConfig {
}
// New returns a new instance of CustomResources from the given config.
func (c completedConfig) New() (*CustomResources, error) {
func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget, stopCh <-chan struct{}) (*CustomResources, error) {
genericServer, err := c.Config.GenericConfig.SkipComplete().New() // completion is done in Complete, no need for a second time
if err != nil {
return nil, err
@ -112,5 +120,41 @@ func (c completedConfig) New() (*CustomResources, error) {
return nil, err
}
customResourceClient, err := internalclientset.NewForConfig(s.GenericAPIServer.LoopbackClientConfig)
if err != nil {
return nil, err
}
customResourceInformers := internalinformers.NewSharedInformerFactory(customResourceClient, 5*time.Minute)
versionDiscoveryHandler := &customResourceVersionDiscoveryHandler{
discovery: map[schema.GroupVersion]*discovery.APIVersionHandler{},
delegate: delegationTarget.UnprotectedHandler(),
}
groupDiscoveryHandler := &customResourceGroupDiscoveryHandler{
discovery: map[string]*discovery.APIGroupHandler{},
delegate: delegationTarget.UnprotectedHandler(),
}
customResourceHandler := NewCustomResourceHandler(
versionDiscoveryHandler,
groupDiscoveryHandler,
s.GenericAPIServer.RequestContextMapper(),
customResourceInformers.Apiextensions().InternalVersion().CustomResources().Lister(),
delegationTarget.UnprotectedHandler(),
c.CustomResourceRESTOptionsGetter,
c.GenericConfig.AdmissionControl,
)
s.GenericAPIServer.FallThroughHandler.Handle("/apis/", customResourceHandler)
customResourceController := NewCustomResourceDiscoveryController(customResourceInformers.Apiextensions().InternalVersion().CustomResources(), versionDiscoveryHandler, groupDiscoveryHandler)
s.GenericAPIServer.AddPostStartHook("start-apiextensions-informers", func(context genericapiserver.PostStartHookContext) error {
customResourceInformers.Start(stopCh)
return nil
})
s.GenericAPIServer.AddPostStartHook("start-apiextensions-controllers", func(context genericapiserver.PostStartHookContext) error {
go customResourceController.Run(stopCh)
return nil
})
return s, nil
}

View File

@ -0,0 +1,127 @@
/*
Copyright 2017 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 apiserver
import (
"net/http"
"strings"
"sync"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/discovery"
)
type customResourceVersionDiscoveryHandler struct {
// TODO, writing is infrequent, optimize this
discoveryLock sync.RWMutex
discovery map[schema.GroupVersion]*discovery.APIVersionHandler
delegate http.Handler
}
func (r *customResourceVersionDiscoveryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
pathParts := splitPath(req.URL.Path)
// only match /apis/<group>/<version>
if len(pathParts) != 3 {
r.delegate.ServeHTTP(w, req)
return
}
discovery, ok := r.getDiscovery(schema.GroupVersion{Group: pathParts[1], Version: pathParts[2]})
if !ok {
r.delegate.ServeHTTP(w, req)
return
}
discovery.ServeHTTP(w, req)
}
func (r *customResourceVersionDiscoveryHandler) getDiscovery(version schema.GroupVersion) (*discovery.APIVersionHandler, bool) {
r.discoveryLock.RLock()
defer r.discoveryLock.RUnlock()
ret, ok := r.discovery[version]
return ret, ok
}
func (r *customResourceVersionDiscoveryHandler) setDiscovery(version schema.GroupVersion, discovery *discovery.APIVersionHandler) {
r.discoveryLock.Lock()
defer r.discoveryLock.Unlock()
r.discovery[version] = discovery
}
func (r *customResourceVersionDiscoveryHandler) unsetDiscovery(version schema.GroupVersion) {
r.discoveryLock.Lock()
defer r.discoveryLock.Unlock()
delete(r.discovery, version)
}
type customResourceGroupDiscoveryHandler struct {
// TODO, writing is infrequent, optimize this
discoveryLock sync.RWMutex
discovery map[string]*discovery.APIGroupHandler
delegate http.Handler
}
func (r *customResourceGroupDiscoveryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
pathParts := splitPath(req.URL.Path)
// only match /apis/<group>
if len(pathParts) != 2 {
r.delegate.ServeHTTP(w, req)
return
}
discovery, ok := r.getDiscovery(pathParts[1])
if !ok {
r.delegate.ServeHTTP(w, req)
return
}
discovery.ServeHTTP(w, req)
}
func (r *customResourceGroupDiscoveryHandler) getDiscovery(group string) (*discovery.APIGroupHandler, bool) {
r.discoveryLock.RLock()
defer r.discoveryLock.RUnlock()
ret, ok := r.discovery[group]
return ret, ok
}
func (r *customResourceGroupDiscoveryHandler) setDiscovery(group string, discovery *discovery.APIGroupHandler) {
r.discoveryLock.Lock()
defer r.discoveryLock.Unlock()
r.discovery[group] = discovery
}
func (r *customResourceGroupDiscoveryHandler) unsetDiscovery(group string) {
r.discoveryLock.Lock()
defer r.discoveryLock.Unlock()
delete(r.discovery, group)
}
// splitPath returns the segments for a URL path.
func splitPath(path string) []string {
path = strings.Trim(path, "/")
if path == "" {
return []string{}
}
return strings.Split(path, "/")
}

View File

@ -0,0 +1,209 @@
/*
Copyright 2017 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 apiserver
import (
"fmt"
"time"
"github.com/golang/glog"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/endpoints/discovery"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
"k8s.io/kube-apiextensions-server/pkg/apis/apiextensions"
informers "k8s.io/kube-apiextensions-server/pkg/client/informers/internalversion/apiextensions/internalversion"
listers "k8s.io/kube-apiextensions-server/pkg/client/listers/apiextensions/internalversion"
)
type CustomResourceDiscoveryController struct {
versionHandler *customResourceVersionDiscoveryHandler
groupHandler *customResourceGroupDiscoveryHandler
customResourceLister listers.CustomResourceLister
customResourcesSynced cache.InformerSynced
// To allow injection for testing.
syncFn func(version schema.GroupVersion) error
queue workqueue.RateLimitingInterface
}
func NewCustomResourceDiscoveryController(customResourceInformer informers.CustomResourceInformer, versionHandler *customResourceVersionDiscoveryHandler, groupHandler *customResourceGroupDiscoveryHandler) *CustomResourceDiscoveryController {
c := &CustomResourceDiscoveryController{
versionHandler: versionHandler,
groupHandler: groupHandler,
customResourceLister: customResourceInformer.Lister(),
customResourcesSynced: customResourceInformer.Informer().HasSynced,
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "CustomResourceDiscoveryController"),
}
customResourceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: c.addCustomResource,
UpdateFunc: c.updateCustomResource,
DeleteFunc: c.deleteCustomResource,
})
c.syncFn = c.sync
return c
}
func (c *CustomResourceDiscoveryController) sync(version schema.GroupVersion) error {
foundVersion := false
foundGroup := false
apiVersionsForDiscovery := []metav1.GroupVersionForDiscovery{}
apiResourcesForDiscovery := []metav1.APIResource{}
customResources, err := c.customResourceLister.List(labels.Everything())
if err != nil {
return err
}
for _, customResource := range customResources {
if customResource.Spec.Group != version.Group {
continue
}
foundGroup = true
apiVersionsForDiscovery = append(apiVersionsForDiscovery, metav1.GroupVersionForDiscovery{
GroupVersion: customResource.Spec.Group + "/" + customResource.Spec.Version,
Version: customResource.Spec.Version,
})
if customResource.Spec.Version != version.Version {
continue
}
foundVersion = true
apiResourcesForDiscovery = append(apiResourcesForDiscovery, metav1.APIResource{
Name: customResource.Spec.Names.Plural,
SingularName: customResource.Spec.Names.Singular,
Namespaced: customResource.Spec.Scope == apiextensions.NamespaceScoped,
Kind: customResource.Spec.Names.Kind,
Verbs: metav1.Verbs([]string{"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"}),
ShortNames: customResource.Spec.Names.ShortNames,
})
}
if !foundGroup {
c.groupHandler.unsetDiscovery(version.Group)
c.versionHandler.unsetDiscovery(version)
return nil
}
apiGroup := metav1.APIGroup{
Name: version.Group,
Versions: apiVersionsForDiscovery,
PreferredVersion: apiVersionsForDiscovery[0],
}
c.groupHandler.setDiscovery(version.Group, discovery.NewAPIGroupHandler(Codecs, apiGroup))
if !foundVersion {
c.versionHandler.unsetDiscovery(version)
return nil
}
c.versionHandler.setDiscovery(version, discovery.NewAPIVersionHandler(Codecs, version, discovery.APIResourceListerFunc(func() []metav1.APIResource {
return apiResourcesForDiscovery
})))
return nil
}
func (c *CustomResourceDiscoveryController) Run(stopCh <-chan struct{}) {
defer utilruntime.HandleCrash()
defer c.queue.ShutDown()
defer glog.Infof("Shutting down CustomResourceDiscoveryController")
glog.Infof("Starting CustomResourceDiscoveryController")
if !cache.WaitForCacheSync(stopCh, c.customResourcesSynced) {
utilruntime.HandleError(fmt.Errorf("timed out waiting for caches to sync"))
return
}
// only start one worker thread since its a slow moving API
go wait.Until(c.runWorker, time.Second, stopCh)
<-stopCh
}
func (c *CustomResourceDiscoveryController) runWorker() {
for c.processNextWorkItem() {
}
}
// processNextWorkItem deals with one key off the queue. It returns false when it's time to quit.
func (c *CustomResourceDiscoveryController) processNextWorkItem() bool {
key, quit := c.queue.Get()
if quit {
return false
}
defer c.queue.Done(key)
err := c.syncFn(key.(schema.GroupVersion))
if err == nil {
c.queue.Forget(key)
return true
}
utilruntime.HandleError(fmt.Errorf("%v failed with : %v", key, err))
c.queue.AddRateLimited(key)
return true
}
func (c *CustomResourceDiscoveryController) enqueue(obj *apiextensions.CustomResource) {
c.queue.Add(schema.GroupVersion{Group: obj.Spec.Group, Version: obj.Spec.Version})
}
func (c *CustomResourceDiscoveryController) addCustomResource(obj interface{}) {
castObj := obj.(*apiextensions.CustomResource)
glog.V(4).Infof("Adding %s", castObj.Name)
c.enqueue(castObj)
}
func (c *CustomResourceDiscoveryController) updateCustomResource(obj, _ interface{}) {
castObj := obj.(*apiextensions.CustomResource)
glog.V(4).Infof("Updating %s", castObj.Name)
c.enqueue(castObj)
}
func (c *CustomResourceDiscoveryController) deleteCustomResource(obj interface{}) {
castObj, ok := obj.(*apiextensions.CustomResource)
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
glog.Errorf("Couldn't get object from tombstone %#v", obj)
return
}
castObj, ok = tombstone.Obj.(*apiextensions.CustomResource)
if !ok {
glog.Errorf("Tombstone contained object that is not expected %#v", obj)
return
}
}
glog.V(4).Infof("Deleting %q", castObj.Name)
c.enqueue(castObj)
}

View File

@ -0,0 +1,366 @@
/*
Copyright 2017 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 apiserver
import (
"bytes"
"fmt"
"net/http"
"sync"
"sync/atomic"
"time"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"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/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/endpoints/handlers"
apirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/storage/storagebackend"
"k8s.io/client-go/discovery"
"k8s.io/kube-apiextensions-server/pkg/apis/apiextensions"
listers "k8s.io/kube-apiextensions-server/pkg/client/listers/apiextensions/internalversion"
"k8s.io/kube-apiextensions-server/pkg/registry/customresourcestorage"
)
// apisHandler serves the `/apis` endpoint.
// This is registered as a filter so that it never collides with any explictly registered endpoints
type customResourceHandler struct {
versionDiscoveryHandler *customResourceVersionDiscoveryHandler
groupDiscoveryHandler *customResourceGroupDiscoveryHandler
storageMutationLock sync.Mutex
// customStorage contains a map[types.UID]*customResourceInfo
customStorage atomic.Value
requestContextMapper apirequest.RequestContextMapper
customResourceLister listers.CustomResourceLister
delegate http.Handler
restOptionsGetter generic.RESTOptionsGetter
admission admission.Interface
}
// customResourceInfo stores enough information to serve the storage for the custom resource
type customResourceInfo struct {
storage *customresourcestorage.REST
requestScope handlers.RequestScope
}
func NewCustomResourceHandler(
versionDiscoveryHandler *customResourceVersionDiscoveryHandler,
groupDiscoveryHandler *customResourceGroupDiscoveryHandler,
requestContextMapper apirequest.RequestContextMapper,
customResourceLister listers.CustomResourceLister,
delegate http.Handler,
restOptionsGetter generic.RESTOptionsGetter,
admission admission.Interface) *customResourceHandler {
ret := &customResourceHandler{
versionDiscoveryHandler: versionDiscoveryHandler,
groupDiscoveryHandler: groupDiscoveryHandler,
customStorage: atomic.Value{},
requestContextMapper: requestContextMapper,
customResourceLister: customResourceLister,
delegate: delegate,
restOptionsGetter: restOptionsGetter,
admission: admission,
}
ret.customStorage.Store(map[types.UID]*customResourceInfo{})
return ret
}
func (r *customResourceHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
ctx, ok := r.requestContextMapper.Get(req)
if !ok {
http.Error(w, "missing context", http.StatusInternalServerError)
return
}
requestInfo, ok := apirequest.RequestInfoFrom(ctx)
if !ok {
http.Error(w, "missing requestInfo", http.StatusInternalServerError)
return
}
if !requestInfo.IsResourceRequest {
pathParts := splitPath(requestInfo.Path)
// only match /apis/<group>/<version>
if len(pathParts) == 3 {
r.versionDiscoveryHandler.ServeHTTP(w, req)
return
}
// only match /apis/<group>
if len(pathParts) == 2 {
r.groupDiscoveryHandler.ServeHTTP(w, req)
return
}
r.delegate.ServeHTTP(w, req)
return
}
if len(requestInfo.Subresource) > 0 {
http.NotFound(w, req)
return
}
customResourceName := requestInfo.Resource + "." + requestInfo.APIGroup
customResource, err := r.customResourceLister.Get(customResourceName)
if apierrors.IsNotFound(err) {
r.delegate.ServeHTTP(w, req)
return
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if customResource.Spec.Version != requestInfo.APIVersion {
r.delegate.ServeHTTP(w, req)
return
}
// TODO this is the point to do the condition checks
customResourceInfo := r.getServingInfoFor(customResource)
storage := customResourceInfo.storage
requestScope := customResourceInfo.requestScope
minRequestTimeout := 1 * time.Minute
switch requestInfo.Verb {
case "get":
handler := handlers.GetResource(storage, storage, requestScope)
handler(w, req)
return
case "list":
forceWatch := false
handler := handlers.ListResource(storage, storage, requestScope, forceWatch, minRequestTimeout)
handler(w, req)
return
case "watch":
forceWatch := true
handler := handlers.ListResource(storage, storage, requestScope, forceWatch, minRequestTimeout)
handler(w, req)
return
case "create":
handler := handlers.CreateResource(storage, requestScope, discovery.NewUnstructuredObjectTyper(nil), r.admission)
handler(w, req)
return
case "update":
handler := handlers.UpdateResource(storage, requestScope, discovery.NewUnstructuredObjectTyper(nil), r.admission)
handler(w, req)
return
case "patch":
handler := handlers.PatchResource(storage, requestScope, r.admission, unstructured.UnstructuredObjectConverter{})
handler(w, req)
return
case "delete":
allowsOptions := true
handler := handlers.DeleteResource(storage, allowsOptions, requestScope, r.admission)
handler(w, req)
return
default:
http.Error(w, fmt.Sprintf("unhandled verb %q", requestInfo.Verb), http.StatusMethodNotAllowed)
return
}
}
// removeDeadStorage removes REST storage that isn't being used
func (r *customResourceHandler) removeDeadStorage() {
// these don't have to be live. A snapshot is fine
// if we wrongly delete, that's ok. The rest storage will be recreated on the next request
// if we wrongly miss one, that's ok. We'll get it next time
storageMap := r.customStorage.Load().(map[types.UID]*customResourceInfo)
allCustomResources, err := r.customResourceLister.List(labels.Everything())
if err != nil {
utilruntime.HandleError(err)
return
}
for uid := range storageMap {
found := false
for _, customResource := range allCustomResources {
if customResource.UID == uid {
found = true
break
}
}
if !found {
delete(storageMap, uid)
}
}
r.storageMutationLock.Lock()
defer r.storageMutationLock.Unlock()
r.customStorage.Store(storageMap)
}
func (r *customResourceHandler) getServingInfoFor(customResource *apiextensions.CustomResource) *customResourceInfo {
storageMap := r.customStorage.Load().(map[types.UID]*customResourceInfo)
ret, ok := storageMap[customResource.UID]
if ok {
return ret
}
r.storageMutationLock.Lock()
defer r.storageMutationLock.Unlock()
ret, ok = storageMap[customResource.UID]
if ok {
return ret
}
storage := customresourcestorage.NewREST(
schema.GroupResource{Group: customResource.Spec.Group, Resource: customResource.Spec.Names.Plural},
schema.GroupVersionKind{Group: customResource.Spec.Group, Version: customResource.Spec.Version, Kind: customResource.Spec.Names.ListKind},
UnstructuredCopier{},
customresourcestorage.NewStrategy(discovery.NewUnstructuredObjectTyper(nil), customResource.Spec.Scope == apiextensions.NamespaceScoped),
r.restOptionsGetter,
)
parameterScheme := runtime.NewScheme()
parameterScheme.AddUnversionedTypes(schema.GroupVersion{Group: customResource.Spec.Group, Version: customResource.Spec.Version},
&metav1.ListOptions{},
&metav1.ExportOptions{},
&metav1.GetOptions{},
&metav1.DeleteOptions{},
)
parameterScheme.AddGeneratedDeepCopyFuncs(metav1.GetGeneratedDeepCopyFuncs()...)
parameterCodec := runtime.NewParameterCodec(parameterScheme)
requestScope := handlers.RequestScope{
Namer: handlers.ContextBasedNaming{
GetContext: func(req *http.Request) apirequest.Context {
ret, _ := r.requestContextMapper.Get(req)
return ret
},
SelfLinker: meta.NewAccessor(),
ClusterScoped: customResource.Spec.Scope == apiextensions.ClusterScoped,
},
ContextFunc: func(req *http.Request) apirequest.Context {
ret, _ := r.requestContextMapper.Get(req)
return ret
},
Serializer: UnstructuredNegotiatedSerializer{},
ParameterCodec: parameterCodec,
Creater: UnstructuredCreator{},
Convertor: unstructured.UnstructuredObjectConverter{},
Defaulter: UnstructuredDefaulter{},
Copier: UnstructuredCopier{},
Typer: discovery.NewUnstructuredObjectTyper(nil),
UnsafeConvertor: unstructured.UnstructuredObjectConverter{},
Resource: schema.GroupVersionResource{Group: customResource.Spec.Group, Version: customResource.Spec.Version, Resource: customResource.Spec.Names.Plural},
Kind: schema.GroupVersionKind{Group: customResource.Spec.Group, Version: customResource.Spec.Version, Kind: customResource.Spec.Names.Kind},
Subresource: "",
MetaGroupVersion: metav1.SchemeGroupVersion,
}
ret = &customResourceInfo{
storage: storage,
requestScope: requestScope,
}
storageMap[customResource.UID] = ret
r.customStorage.Store(storageMap)
return ret
}
type UnstructuredNegotiatedSerializer struct{}
func (s UnstructuredNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
return []runtime.SerializerInfo{
{
MediaType: "application/json",
EncodesAsText: true,
Serializer: json.NewSerializer(json.DefaultMetaFactory, UnstructuredCreator{}, discovery.NewUnstructuredObjectTyper(nil), false),
PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, UnstructuredCreator{}, discovery.NewUnstructuredObjectTyper(nil), true),
StreamSerializer: &runtime.StreamSerializerInfo{
EncodesAsText: true,
Serializer: json.NewSerializer(json.DefaultMetaFactory, UnstructuredCreator{}, discovery.NewUnstructuredObjectTyper(nil), false),
Framer: json.Framer,
},
},
}
}
func (s UnstructuredNegotiatedSerializer) EncoderForVersion(serializer runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
return unstructured.UnstructuredJSONScheme
}
func (s UnstructuredNegotiatedSerializer) DecoderToVersion(serializer runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
return unstructured.UnstructuredJSONScheme
}
type UnstructuredCreator struct{}
func (UnstructuredCreator) New(kind schema.GroupVersionKind) (runtime.Object, error) {
ret := &unstructured.Unstructured{}
ret.SetGroupVersionKind(kind)
return ret, nil
}
type UnstructuredCopier struct{}
func (UnstructuredCopier) Copy(obj runtime.Object) (runtime.Object, error) {
// serialize and deserialize to ensure a clean copy
buf := &bytes.Buffer{}
err := unstructured.UnstructuredJSONScheme.Encode(obj, buf)
if err != nil {
return nil, err
}
out := &unstructured.Unstructured{}
result, _, err := unstructured.UnstructuredJSONScheme.Decode(buf.Bytes(), nil, out)
return result, err
}
type UnstructuredDefaulter struct{}
func (UnstructuredDefaulter) Default(in runtime.Object) {}
type CustomResourceRESTOptionsGetter struct {
StorageConfig storagebackend.Config
StoragePrefix string
EnableWatchCache bool
EnableGarbageCollection bool
DeleteCollectionWorkers int
}
func (t CustomResourceRESTOptionsGetter) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) {
ret := generic.RESTOptions{
StorageConfig: &t.StorageConfig,
Decorator: generic.UndecoratedStorage,
EnableGarbageCollection: t.EnableGarbageCollection,
DeleteCollectionWorkers: t.DeleteCollectionWorkers,
ResourcePrefix: t.StoragePrefix + "/" + resource.Group + "/" + resource.Resource,
}
if t.EnableWatchCache {
ret.Decorator = genericregistry.StorageWithCacher
}
return ret, nil
}

View File

@ -13,6 +13,7 @@ go_library(
tags = ["automanaged"],
deps = [
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server/options:go_default_library",
"//vendor/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1:go_default_library",

View File

@ -23,6 +23,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
genericapiserver "k8s.io/apiserver/pkg/server"
genericoptions "k8s.io/apiserver/pkg/server/options"
"k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1"
@ -94,8 +95,19 @@ func (o CustomResourcesServerOptions) Config() (*apiserver.Config, error) {
return nil, err
}
customResourceRESTOptionsGetter := apiserver.CustomResourceRESTOptionsGetter{
StorageConfig: o.RecommendedOptions.Etcd.StorageConfig,
StoragePrefix: o.RecommendedOptions.Etcd.StorageConfig.Prefix,
EnableWatchCache: o.RecommendedOptions.Etcd.EnableWatchCache,
EnableGarbageCollection: o.RecommendedOptions.Etcd.EnableGarbageCollection,
DeleteCollectionWorkers: o.RecommendedOptions.Etcd.DeleteCollectionWorkers,
}
customResourceRESTOptionsGetter.StorageConfig.Codec = unstructured.UnstructuredJSONScheme
customResourceRESTOptionsGetter.StorageConfig.Copier = apiserver.UnstructuredCopier{}
config := &apiserver.Config{
GenericConfig: serverConfig,
GenericConfig: serverConfig,
CustomResourceRESTOptionsGetter: customResourceRESTOptionsGetter,
}
return config, nil
}
@ -106,7 +118,7 @@ func (o CustomResourcesServerOptions) RunCustomResourcesServer(stopCh <-chan str
return err
}
server, err := config.Complete().New()
server, err := config.Complete().New(genericapiserver.EmptyDelegate, stopCh)
if err != nil {
return err
}

View File

@ -0,0 +1,33 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"etcd.go",
"strategy.go",
],
tags = ["automanaged"],
deps = [
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/validation: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/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/validation/field: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/storage:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library",
],
)

View File

@ -0,0 +1,63 @@
/*
Copyright 2017 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 customresourcestorage
import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
)
// rest implements a RESTStorage for API services against etcd
type REST struct {
*genericregistry.Store
}
// NewREST returns a RESTStorage object that will work against API services.
func NewREST(resource schema.GroupResource, listKind schema.GroupVersionKind, copier runtime.ObjectCopier, strategy CustomResourceStorageStrategy, optsGetter generic.RESTOptionsGetter) *REST {
store := &genericregistry.Store{
Copier: copier,
NewFunc: func() runtime.Object { return &unstructured.Unstructured{} },
NewListFunc: func() runtime.Object {
// lists are never stored, only manufactured, so stomp in the right kind
ret := &unstructured.UnstructuredList{}
ret.SetGroupVersionKind(listKind)
return ret
},
ObjectNameFunc: func(obj runtime.Object) (string, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return "", err
}
return accessor.GetName(), nil
},
PredicateFunc: strategy.MatchCustomResourceStorage,
QualifiedResource: resource,
CreateStrategy: strategy,
UpdateStrategy: strategy,
DeleteStrategy: strategy,
}
options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: strategy.GetAttrs}
if err := store.CompleteWithOptions(options); err != nil {
panic(err) // TODO: Propagate error up
}
return &REST{store}
}

View File

@ -0,0 +1,115 @@
/*
Copyright 2017 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 customresourcestorage
import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/validation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"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/storage"
"k8s.io/apiserver/pkg/storage/names"
)
type CustomResourceStorageStrategy struct {
runtime.ObjectTyper
names.NameGenerator
namespaceScoped bool
}
func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool) CustomResourceStorageStrategy {
return CustomResourceStorageStrategy{typer, names.SimpleNameGenerator, namespaceScoped}
}
func (a CustomResourceStorageStrategy) NamespaceScoped() bool {
return a.namespaceScoped
}
func (CustomResourceStorageStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) {
}
func (CustomResourceStorageStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) {
}
func (a CustomResourceStorageStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList {
accessor, err := meta.Accessor(obj)
if err != nil {
return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())}
}
return validation.ValidateObjectMetaAccessor(accessor, a.namespaceScoped, validation.NameIsDNSSubdomain, field.NewPath("metadata"))
}
func (CustomResourceStorageStrategy) AllowCreateOnUpdate() bool {
return false
}
func (CustomResourceStorageStrategy) AllowUnconditionalUpdate() bool {
return false
}
func (CustomResourceStorageStrategy) Canonicalize(obj runtime.Object) {
}
func (CustomResourceStorageStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList {
objAccessor, err := meta.Accessor(obj)
if err != nil {
return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())}
}
oldAccessor, err := meta.Accessor(old)
if err != nil {
return field.ErrorList{field.Invalid(field.NewPath("metadata"), nil, err.Error())}
}
return validation.ValidateObjectMetaAccessorUpdate(objAccessor, oldAccessor, field.NewPath("metadata"))
return field.ErrorList{}
}
func (a CustomResourceStorageStrategy) GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, nil, err
}
return labels.Set(accessor.GetLabels()), objectMetaFieldsSet(accessor, a.namespaceScoped), nil
}
// objectMetaFieldsSet returns a fields that represent the ObjectMeta.
func objectMetaFieldsSet(objectMeta metav1.Object, namespaceScoped bool) fields.Set {
if namespaceScoped {
return fields.Set{
"metadata.name": objectMeta.GetName(),
"metadata.namespace": objectMeta.GetNamespace(),
}
}
return fields.Set{
"metadata.name": objectMeta.GetName(),
}
}
func (a CustomResourceStorageStrategy) MatchCustomResourceStorage(label labels.Selector, field fields.Selector) storage.SelectionPredicate {
return storage.SelectionPredicate{
Label: label,
Field: field,
GetAttrs: a.GetAttrs,
}
}

View File

@ -0,0 +1,22 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["basic_test.go"],
tags = ["automanaged"],
deps = [
"//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/unstructured:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1:go_default_library",
"//vendor/k8s.io/kube-apiextensions-server/test/integration/testserver:go_default_library",
],
)

View File

@ -0,0 +1,31 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"resources.go",
"start.go",
],
tags = ["automanaged"],
deps = [
"//vendor/github.com/pborman/uuid: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/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library",
"//vendor/k8s.io/apiserver/pkg/server:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1:go_default_library",
"//vendor/k8s.io/kube-apiextensions-server/pkg/apiserver:go_default_library",
"//vendor/k8s.io/kube-apiextensions-server/pkg/client/clientset/clientset:go_default_library",
"//vendor/k8s.io/kube-apiextensions-server/pkg/cmd/server:go_default_library",
],
)