diff --git a/staging/src/k8s.io/kube-apiextensions-server/main.go b/staging/src/k8s.io/kube-apiextensions-server/main.go new file mode 100644 index 00000000000..a062605e56f --- /dev/null +++ b/staging/src/k8s.io/kube-apiextensions-server/main.go @@ -0,0 +1,42 @@ +/* +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 main + +import ( + "flag" + "os" + "runtime" + + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apiserver/pkg/util/logs" + "k8s.io/kube-apiextensions-server/pkg/cmd/server" +) + +func main() { + logs.InitLogs() + defer logs.FlushLogs() + + if len(os.Getenv("GOMAXPROCS")) == 0 { + runtime.GOMAXPROCS(runtime.NumCPU()) + } + + cmd := server.NewCommandStartCustomResourcesServer(os.Stdout, os.Stderr, wait.NeverStop) + cmd.Flags().AddGoFlagSet(flag.CommandLine) + if err := cmd.Execute(); err != nil { + panic(err) + } +} diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/apiserver.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/apiserver.go new file mode 100644 index 00000000000..319707a37dc --- /dev/null +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/apiserver/apiserver.go @@ -0,0 +1,116 @@ +/* +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 ( + "k8s.io/apimachinery/pkg/apimachinery/announced" + "k8s.io/apimachinery/pkg/apimachinery/registered" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/version" + "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/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" +) + +var ( + groupFactoryRegistry = make(announced.APIGroupFactoryRegistry) + registry = registered.NewOrDie("") + Scheme = runtime.NewScheme() + Codecs = serializer.NewCodecFactory(Scheme) +) + +func init() { + install.Install(groupFactoryRegistry, registry, Scheme) + + // we need to add the options to empty v1 + metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Group: "", Version: "v1"}) + + unversioned := schema.GroupVersion{Group: "", Version: "v1"} + Scheme.AddUnversionedTypes(unversioned, + &metav1.Status{}, + &metav1.APIVersions{}, + &metav1.APIGroupList{}, + &metav1.APIGroup{}, + &metav1.APIResourceList{}, + ) +} + +type Config struct { + GenericConfig *genericapiserver.Config +} + +type CustomResources struct { + GenericAPIServer *genericapiserver.GenericAPIServer +} + +type completedConfig struct { + *Config +} + +// 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.Complete() + + c.GenericConfig.Version = &version.Info{ + Major: "0", + Minor: "1", + } + + return completedConfig{c} +} + +// SkipComplete provides a way to construct a server instance without config completion. +func (c *Config) SkipComplete() completedConfig { + return completedConfig{c} +} + +// New returns a new instance of CustomResources from the given config. +func (c completedConfig) New() (*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 + } + + s := &CustomResources{ + GenericAPIServer: genericServer, + } + + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiextensions.GroupName, registry, Scheme, metav1.ParameterCodec, Codecs) + apiGroupInfo.GroupMeta.GroupVersion = v1alpha1.SchemeGroupVersion + v1alpha1storage := map[string]rest.Storage{} + v1alpha1storage["customresources"] = customresource.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter) + apiGroupInfo.VersionedResourcesStorageMap["v1alpha1"] = v1alpha1storage + + if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil { + return nil, err + } + + return s, nil +} diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/cmd/server/start.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/cmd/server/start.go new file mode 100644 index 00000000000..e6776f505fa --- /dev/null +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/cmd/server/start.go @@ -0,0 +1,114 @@ +/* +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 server + +import ( + "fmt" + "io" + "net" + + "github.com/spf13/cobra" + + genericapiserver "k8s.io/apiserver/pkg/server" + genericoptions "k8s.io/apiserver/pkg/server/options" + "k8s.io/kube-apiextensions-server/pkg/apis/apiextensions/v1alpha1" + "k8s.io/kube-apiextensions-server/pkg/apiserver" +) + +const defaultEtcdPathPrefix = "/registry/apiextensions.kubernetes.io" + +type CustomResourcesServerOptions struct { + RecommendedOptions *genericoptions.RecommendedOptions + + StdOut io.Writer + StdErr io.Writer +} + +func NewCustomResourcesServerOptions(out, errOut io.Writer) *CustomResourcesServerOptions { + o := &CustomResourcesServerOptions{ + RecommendedOptions: genericoptions.NewRecommendedOptions(defaultEtcdPathPrefix, apiserver.Scheme, apiserver.Codecs.LegacyCodec(v1alpha1.SchemeGroupVersion)), + + StdOut: out, + StdErr: errOut, + } + + return o +} + +func NewCommandStartCustomResourcesServer(out, errOut io.Writer, stopCh <-chan struct{}) *cobra.Command { + o := NewCustomResourcesServerOptions(out, errOut) + + cmd := &cobra.Command{ + Short: "Launch an API extensions API server", + Long: "Launch an API extensions API server", + RunE: func(c *cobra.Command, args []string) error { + if err := o.Complete(); err != nil { + return err + } + if err := o.Validate(args); err != nil { + return err + } + if err := o.RunCustomResourcesServer(stopCh); err != nil { + return err + } + return nil + }, + } + + flags := cmd.Flags() + o.RecommendedOptions.AddFlags(flags) + + return cmd +} + +func (o CustomResourcesServerOptions) Validate(args []string) error { + return nil +} + +func (o *CustomResourcesServerOptions) Complete() error { + return nil +} + +func (o CustomResourcesServerOptions) Config() (*apiserver.Config, error) { + // TODO have a "real" external address + if err := o.RecommendedOptions.SecureServing.MaybeDefaultWithSelfSignedCerts("localhost", nil, []net.IP{net.ParseIP("127.0.0.1")}); err != nil { + return nil, fmt.Errorf("error creating self-signed certificates: %v", err) + } + + serverConfig := genericapiserver.NewConfig(apiserver.Codecs) + if err := o.RecommendedOptions.ApplyTo(serverConfig); err != nil { + return nil, err + } + + config := &apiserver.Config{ + GenericConfig: serverConfig, + } + return config, nil +} + +func (o CustomResourcesServerOptions) RunCustomResourcesServer(stopCh <-chan struct{}) error { + config, err := o.Config() + if err != nil { + return err + } + + server, err := config.Complete().New() + if err != nil { + return err + } + return server.GenericAPIServer.PrepareRun().Run(stopCh) +} diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresource/etcd.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresource/etcd.go new file mode 100644 index 00000000000..a4fcdc4a14a --- /dev/null +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresource/etcd.go @@ -0,0 +1,54 @@ +/* +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 customresource + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/generic" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + "k8s.io/kube-apiextensions-server/pkg/apis/apiextensions" +) + +// 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(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) *REST { + strategy := NewStrategy(scheme) + + store := &genericregistry.Store{ + Copier: scheme, + NewFunc: func() runtime.Object { return &apiextensions.CustomResource{} }, + NewListFunc: func() runtime.Object { return &apiextensions.CustomResourceList{} }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*apiextensions.CustomResource).Name, nil + }, + PredicateFunc: MatchCustomResource, + QualifiedResource: apiextensions.Resource("customresources"), + + CreateStrategy: strategy, + UpdateStrategy: strategy, + DeleteStrategy: strategy, + } + options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: GetAttrs} + if err := store.CompleteWithOptions(options); err != nil { + panic(err) // TODO: Propagate error up + } + return &REST{store} +} diff --git a/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresource/strategy.go b/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresource/strategy.go new file mode 100644 index 00000000000..59b275e70d1 --- /dev/null +++ b/staging/src/k8s.io/kube-apiextensions-server/pkg/registry/customresource/strategy.go @@ -0,0 +1,93 @@ +/* +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 customresource + +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/generic" + "k8s.io/apiserver/pkg/storage" + "k8s.io/apiserver/pkg/storage/names" + + "k8s.io/kube-apiextensions-server/pkg/apis/apiextensions" +) + +type apiServerStrategy struct { + runtime.ObjectTyper + names.NameGenerator +} + +func NewStrategy(typer runtime.ObjectTyper) apiServerStrategy { + return apiServerStrategy{typer, names.SimpleNameGenerator} +} + +func (apiServerStrategy) NamespaceScoped() bool { + return false +} + +func (apiServerStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) { +} + +func (apiServerStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) { +} + +func (apiServerStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList { + return field.ErrorList{} +} + +func (apiServerStrategy) AllowCreateOnUpdate() bool { + return false +} + +func (apiServerStrategy) AllowUnconditionalUpdate() bool { + return false +} + +func (apiServerStrategy) Canonicalize(obj runtime.Object) { +} + +func (apiServerStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList { + return field.ErrorList{} +} + +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { + apiserver, ok := obj.(*apiextensions.CustomResource) + if !ok { + return nil, nil, fmt.Errorf("given object is not a CustomResource.") + } + return labels.Set(apiserver.ObjectMeta.Labels), CustomResourceToSelectableFields(apiserver), nil +} + +// MatchCustomResource 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 MatchCustomResource(label labels.Selector, field fields.Selector) storage.SelectionPredicate { + return storage.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: GetAttrs, + } +} + +// CustomResourceToSelectableFields returns a field set that represents the object. +func CustomResourceToSelectableFields(obj *apiextensions.CustomResource) fields.Set { + return generic.ObjectMetaFieldsSet(&obj.ObjectMeta, true) +}