diff --git a/cmd/kubernetes-discovery/pkg/apiserver/apiserver.go b/cmd/kubernetes-discovery/pkg/apiserver/apiserver.go index 500384db5f2..39d16a4846b 100644 --- a/cmd/kubernetes-discovery/pkg/apiserver/apiserver.go +++ b/cmd/kubernetes-discovery/pkg/apiserver/apiserver.go @@ -17,10 +17,15 @@ limitations under the License. package apiserver import ( + "k8s.io/kubernetes/pkg/api/rest" "k8s.io/kubernetes/pkg/genericapiserver" "k8s.io/kubernetes/pkg/registry/generic" "k8s.io/kubernetes/pkg/runtime/schema" "k8s.io/kubernetes/pkg/version" + + "k8s.io/kubernetes/cmd/kubernetes-discovery/pkg/apis/apiregistration" + "k8s.io/kubernetes/cmd/kubernetes-discovery/pkg/apis/apiregistration/v1alpha1" + apiservicestorage "k8s.io/kubernetes/cmd/kubernetes-discovery/pkg/registry/apiservice" ) // TODO move to genericapiserver or something like that @@ -71,19 +76,17 @@ func (c completedConfig) New() (*APIDiscoveryServer, error) { GenericAPIServer: genericServer, } - // TODO switch to constants once we have an API - // TODO install RESTStorage for API - // apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo("apifederation.k8s.io") - // apiGroupInfo.GroupMeta.GroupVersion = schema.GroupVersion{Group: "apifederation.k8s.io", Version: "v1alpha1"} + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiregistration.GroupName) + apiGroupInfo.GroupMeta.GroupVersion = v1alpha1.SchemeGroupVersion - // v1alpha1storage := map[string]rest.Storage{} - // // v1alpha1storage["apiservices"] = apiserverstorage.NewREST(c.RESTOptionsGetter.NewFor(apifederation.Resource("apiservices"))) + v1alpha1storage := map[string]rest.Storage{} + v1alpha1storage["apiservices"] = apiservicestorage.NewREST(c.RESTOptionsGetter.NewFor(apiregistration.Resource("apiservices"))) - // apiGroupInfo.VersionedResourcesStorageMap["v1alpha1"] = v1alpha1storage + apiGroupInfo.VersionedResourcesStorageMap["v1alpha1"] = v1alpha1storage - // if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil { - // return nil, err - // } + if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil { + return nil, err + } return s, nil } diff --git a/cmd/kubernetes-discovery/pkg/cmd/server/start.go b/cmd/kubernetes-discovery/pkg/cmd/server/start.go index 009dd16d6dd..4139e8c4235 100644 --- a/cmd/kubernetes-discovery/pkg/cmd/server/start.go +++ b/cmd/kubernetes-discovery/pkg/cmd/server/start.go @@ -26,7 +26,6 @@ import ( "k8s.io/kubernetes/cmd/kubernetes-discovery/pkg/apiserver" "k8s.io/kubernetes/cmd/kubernetes-discovery/pkg/legacy" "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/genericapiserver" genericoptions "k8s.io/kubernetes/pkg/genericapiserver/options" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -35,6 +34,8 @@ import ( "k8s.io/kubernetes/pkg/runtime/schema" "k8s.io/kubernetes/pkg/storage/storagebackend" "k8s.io/kubernetes/pkg/util/wait" + + "k8s.io/kubernetes/cmd/kubernetes-discovery/pkg/apis/apiregistration/v1alpha1" ) const defaultEtcdPathPrefix = "/registry/kubernetes.io/kubernetes-discovery" @@ -57,7 +58,7 @@ func NewCommandStartDiscoveryServer(out, err io.Writer) *cobra.Command { StdErr: err, } o.Etcd.StorageConfig.Prefix = defaultEtcdPathPrefix - o.Etcd.StorageConfig.Codec = api.Codecs.LegacyCodec(registered.EnabledVersionsForGroup(api.GroupName)...) + o.Etcd.StorageConfig.Codec = api.Codecs.LegacyCodec(v1alpha1.SchemeGroupVersion) o.SecureServing.ServingOptions.BindPort = 9090 cmd := &cobra.Command{ diff --git a/cmd/kubernetes-discovery/pkg/registry/apiservice/etcd.go b/cmd/kubernetes-discovery/pkg/registry/apiservice/etcd.go new file mode 100644 index 00000000000..0943ecf0d82 --- /dev/null +++ b/cmd/kubernetes-discovery/pkg/registry/apiservice/etcd.go @@ -0,0 +1,99 @@ +/* +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 apiservice + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/generic/registry" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" + + "k8s.io/kubernetes/cmd/kubernetes-discovery/pkg/apis/apiregistration" +) + +// rest implements a RESTStorage for network policies against etcd +type REST struct { + *registry.Store +} + +// NewREST returns a RESTStorage object that will work against network policies. +func NewREST(opts generic.RESTOptions) *REST { + prefix := "/" + opts.ResourcePrefix + + newListFunc := func() runtime.Object { return &apiregistration.APIServiceList{} } + storageInterface, dFunc := opts.Decorator( + opts.StorageConfig, + 1000, // cache size + &apiregistration.APIService{}, + prefix, + strategy, + newListFunc, + getAttrs, + storage.NoTriggerPublisher, + ) + + store := ®istry.Store{ + NewFunc: func() runtime.Object { return &apiregistration.APIService{} }, + + // NewListFunc returns an object capable of storing results of an etcd list. + NewListFunc: newListFunc, + // Produces a APIService that etcd understands, to the root of the resource + // by combining the namespace in the context with the given prefix + KeyRootFunc: func(ctx api.Context) string { + return prefix + }, + // Produces a APIService that etcd understands, to the resource by combining + // the namespace in the context with the given prefix + KeyFunc: func(ctx api.Context, name string) (string, error) { + return registry.NoNamespaceKeyFunc(ctx, prefix, name) + }, + // Retrieve the name field of an apiserver + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*apiregistration.APIService).Name, nil + }, + // Used to match objects based on labels/fields for list and watch + PredicateFunc: MatchAPIService, + QualifiedResource: apiregistration.Resource("apiservers"), + EnableGarbageCollection: opts.EnableGarbageCollection, + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + // Used to validate controller creation + CreateStrategy: strategy, + + // Used to validate controller updates + UpdateStrategy: strategy, + DeleteStrategy: strategy, + + Storage: storageInterface, + DestroyFunc: dFunc, + } + return &REST{store} +} + +// getAttrs returns labels and fields of a given object for filtering purposes. +func getAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { + castObj, ok := obj.(*apiregistration.APIService) + if !ok { + return nil, nil, fmt.Errorf("given object is not an APIService.") + } + return labels.Set(castObj.ObjectMeta.Labels), APIServiceToSelectableFields(castObj), nil +} diff --git a/cmd/kubernetes-discovery/pkg/registry/apiservice/strategy.go b/cmd/kubernetes-discovery/pkg/registry/apiservice/strategy.go new file mode 100644 index 00000000000..a04c7a0d03e --- /dev/null +++ b/cmd/kubernetes-discovery/pkg/registry/apiservice/strategy.go @@ -0,0 +1,93 @@ +/* +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 apiservice + +import ( + "fmt" + + kapi "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/storage" + "k8s.io/kubernetes/pkg/util/validation/field" + + "k8s.io/kubernetes/cmd/kubernetes-discovery/pkg/apis/apiregistration" + "k8s.io/kubernetes/cmd/kubernetes-discovery/pkg/apis/apiregistration/validation" +) + +type apiServerStrategy struct { + runtime.ObjectTyper + kapi.NameGenerator +} + +var strategy = apiServerStrategy{kapi.Scheme, kapi.SimpleNameGenerator} + +func (apiServerStrategy) NamespaceScoped() bool { + return false +} + +func (apiServerStrategy) PrepareForCreate(ctx kapi.Context, obj runtime.Object) { + _ = obj.(*apiregistration.APIService) +} + +func (apiServerStrategy) PrepareForUpdate(ctx kapi.Context, obj, old runtime.Object) { + newAPIService := obj.(*apiregistration.APIService) + oldAPIService := old.(*apiregistration.APIService) + newAPIService.Status = oldAPIService.Status +} + +func (apiServerStrategy) Validate(ctx kapi.Context, obj runtime.Object) field.ErrorList { + return validation.ValidateAPIService(obj.(*apiregistration.APIService)) +} + +func (apiServerStrategy) AllowCreateOnUpdate() bool { + return false +} + +func (apiServerStrategy) AllowUnconditionalUpdate() bool { + return false +} + +func (apiServerStrategy) Canonicalize(obj runtime.Object) { +} + +func (apiServerStrategy) ValidateUpdate(ctx kapi.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateAPIServiceUpdate(obj.(*apiregistration.APIService), old.(*apiregistration.APIService)) +} + +// MatchAPIService is the filter used by the generic etcd backend to watch events +// from etcd to clients of the apiserver only interested in specific labels/fields. +func MatchAPIService(label labels.Selector, field fields.Selector) storage.SelectionPredicate { + return storage.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) { + apiserver, ok := obj.(*apiregistration.APIService) + if !ok { + return nil, nil, fmt.Errorf("given object is not a APIService.") + } + return labels.Set(apiserver.ObjectMeta.Labels), APIServiceToSelectableFields(apiserver), nil + }, + } +} + +// APIServiceToSelectableFields returns a field set that represents the object. +func APIServiceToSelectableFields(obj *apiregistration.APIService) fields.Set { + return generic.ObjectMetaFieldsSet(&obj.ObjectMeta, true) +}