From 93b61fc45c96f7a850707ab2a1f9e1e3a6bbcb66 Mon Sep 17 00:00:00 2001 From: "zuoxiu.jm" <291271447@qq.com> Date: Tue, 11 Jun 2019 16:58:26 +0800 Subject: [PATCH] generate dummy admission controller when specifying `--admission-controller` extend Config fields w/ admission controller and post-start hooks --- .../generators/admission_generator.go | 101 +++++++++++++ cmd/apiregister-gen/generators/package.go | 9 +- cmd/apiserver-boot/boot/create/resource.go | 143 +++++++++++++++++- example/cmd/apiserver/main.go | 11 +- example/plugin/admission/deepone/admission.go | 70 +++++++++ .../plugin/admission/festival/admission.go | 70 +++++++++ example/plugin/admission/initializer.go | 64 ++++++++ pkg/apiserver/apiserver.go | 13 +- pkg/cmd/server/admission.go | 12 ++ pkg/cmd/server/start.go | 56 +++++-- 10 files changed, 529 insertions(+), 20 deletions(-) create mode 100644 cmd/apiregister-gen/generators/admission_generator.go create mode 100644 example/plugin/admission/deepone/admission.go create mode 100644 example/plugin/admission/festival/admission.go create mode 100644 example/plugin/admission/initializer.go create mode 100644 pkg/cmd/server/admission.go diff --git a/cmd/apiregister-gen/generators/admission_generator.go b/cmd/apiregister-gen/generators/admission_generator.go new file mode 100644 index 0000000000..6ec7efc962 --- /dev/null +++ b/cmd/apiregister-gen/generators/admission_generator.go @@ -0,0 +1,101 @@ +package generators + +import ( + "fmt" + "io" + "k8s.io/klog" + "os" + "path/filepath" + "strings" + "text/template" + + "k8s.io/gengo/generator" +) + +var _ generator.Generator = &apiGenerator{} + +type admissionGenerator struct { + generator.DefaultGen + projectRootPath string + admissionKinds []string +} + +func CreateAdmissionGenerator(apis *APIs, filename string, projectRootPath string, outputBase string) generator.Generator { + admissionKinds := []string{} + // filter out those resources created w/ `--admission-controller` flag + for _, group := range apis.Groups { + for _, version := range group.Versions { + for _, resource := range version.Resources { + resourceAdmissionControllerPkg := filepath.Join(outputBase, projectRootPath, "plugin", "admission", strings.ToLower(resource.Kind)) + // if "/plugin/admission" package is present in the project, add it to the generated installation function + if _, err := os.Stat(resourceAdmissionControllerPkg); err == nil { + admissionKinds = append(admissionKinds, resource.Kind) + klog.V(5).Infof("found existing admission controller for resource: %v/%v", resource.Group, resource.Kind) + } + } + } + } + + return &admissionGenerator{ + generator.DefaultGen{OptionalName: filename}, + projectRootPath, + admissionKinds, + } +} + +func (d *admissionGenerator) Imports(c *generator.Context) []string { + imports := []string{ + "github.com/kubernetes-incubator/apiserver-builder-alpha/pkg/cmd/server", + "k8s.io/client-go/rest", + `genericserver "k8s.io/apiserver/pkg/server"`, + } + for _, kind := range d.admissionKinds { + imports = append(imports, fmt.Sprintf( + `. "%s/plugin/admission/%s"`, d.projectRootPath, strings.ToLower(kind))) + } + imports = append(imports, + fmt.Sprintf(`aggregatedclientset "%s/pkg/client/clientset_generated/clientset"`, d.projectRootPath)) + imports = append(imports, + fmt.Sprintf(`aggregatedinformerfactory "%s/pkg/client/informers_generated/externalversions"`, d.projectRootPath)) + imports = append(imports, + fmt.Sprintf(`intializer "%s/plugin/admission"`, d.projectRootPath)) + return imports +} + +type AdmissionGeneratorParam struct { + Kind string +} + +func (d *admissionGenerator) Finalize(context *generator.Context, w io.Writer) error { + if len(d.admissionKinds) == 0 { + return nil + } + + temp := template.Must(template.New("admission-install-template").Parse(AdmissionsInstallTemplate)) + return temp.Execute(w, &struct { + Admissions []string + }{ + Admissions: d.admissionKinds, + }) +} + +var AdmissionsInstallTemplate = ` +func init() { + server.AggregatedAdmissionInitializerGetter = GetAggregatedResourceAdmissionControllerInitializer +{{ range .Admissions -}} + server.AggregatedAdmissionPlugins["{{.}}"] = New{{.}}Plugin() +{{ end }} +} + +func GetAggregatedResourceAdmissionControllerInitializer(config *rest.Config) (admission.PluginInitializer, genericserver.PostStartHookFunc) { + // init aggregated resource clients + aggregatedResourceClient := aggregatedclientset.NewForConfigOrDie(config) + aggregatedInformerFactory := aggregatedinformerfactory.NewSharedInformerFactory(aggregatedResourceClient, 0) + aggregatedResourceInitializer := intializer.New(aggregatedResourceClient, aggregatedInformerFactory) + + return aggregatedResourceInitializer, func(context genericserver.PostStartHookContext) error { + aggregatedInformerFactory.Start(context.StopCh) + return nil + } +} +` diff --git a/cmd/apiregister-gen/generators/package.go b/cmd/apiregister-gen/generators/package.go index 8697c41b05..cf47eb1c83 100644 --- a/cmd/apiregister-gen/generators/package.go +++ b/cmd/apiregister-gen/generators/package.go @@ -104,9 +104,14 @@ func (g *Gen) Packages(context *generator.Context, arguments *args.GeneratorArgs g.p = append(g.p, factory.createPackage(gen)) } - factory := &packageFactory{b.APIs.Pkg.Path, arguments} + apisFactory := &packageFactory{b.APIs.Pkg.Path, arguments} gen := CreateApisGenerator(b.APIs, arguments.OutputFileBaseName) - g.p = append(g.p, factory.createPackage(gen)) + g.p = append(g.p, apisFactory.createPackage(gen)) + + projectRootPath := filepath.Dir(filepath.Dir(b.APIs.Pkg.Path)) + admissionFactory := &packageFactory{filepath.Join(projectRootPath, "plugin", "admission", "install"), arguments} + admissionGen := CreateAdmissionGenerator(b.APIs, arguments.OutputFileBaseName, projectRootPath, b.arguments.OutputBase) + g.p = append(g.p, admissionFactory.createPackage(admissionGen)) return g.p } diff --git a/cmd/apiserver-boot/boot/create/resource.go b/cmd/apiserver-boot/boot/create/resource.go index dc59e69475..cb51b90ada 100644 --- a/cmd/apiserver-boot/boot/create/resource.go +++ b/cmd/apiserver-boot/boot/create/resource.go @@ -37,6 +37,7 @@ import ( var kindName string var resourceName string var nonNamespacedKind bool +var generateAdmissionController bool var createResourceCmd = &cobra.Command{ Use: "resource", @@ -52,6 +53,7 @@ func AddCreateResource(cmd *cobra.Command) { RegisterResourceFlags(createResourceCmd) createResourceCmd.Flags().BoolVar(&nonNamespacedKind, "non-namespaced", false, "if set, the API kind will be non namespaced") + createResourceCmd.Flags().BoolVar(&generateAdmissionController, "admission-controller", false, "if set, an admission controller for the resources will be generated") cmd.AddCommand(createResourceCmd) } @@ -153,6 +155,33 @@ func createResource(boilerplate string) { } } + if generateAdmissionController { + // write the admission-controller initializer if it is missing + os.MkdirAll(filepath.Join("plugin", "admission"), 0700) + admissionInitializerFileName := "initializer.go" + path = filepath.Join(dir, "plugin", "admission", admissionInitializerFileName) + created = util.WriteIfNotFound(path, "admission-initializer-template", admissionControllerInitializerTemplate, a) + if !created { + if !found { + log.Printf("admission initializer already exists.") + found = true + } + } + + // write the admission controller if it is missing + os.MkdirAll(filepath.Join("plugin", "admission", strings.ToLower(kindName)), 0700) + admissionControllerFileName := "admission.go" + path = filepath.Join(dir, "plugin", "admission", strings.ToLower(kindName), admissionControllerFileName) + created = util.WriteIfNotFound(path, "admission-controller-template", admissionControllerTemplate, a) + if !created { + if !found { + log.Printf("admission controller for kind %s test already exists.", kindName) + found = true + } + } + } + + // write controller-runtime scaffolding templates r := &resource.Resource{ Namespaced: !nonNamespacedKind, Group: groupName, @@ -164,11 +193,11 @@ func createResource(boilerplate string) { err = (&scaffold.Scaffold{}).Execute(input.Options{ BoilerplatePath: "boilerplate.go.txt", }, &Controller{ - Resource: r, - Input: input.Input{ - IfExistsAction: input.Skip, - }, - }) + Resource: r, + Input: input.Input{ + IfExistsAction: input.Skip, + }, + }) if err != nil { klog.Warningf("failed generating %v controller: %v", kindName, err) } @@ -414,3 +443,107 @@ metadata: name: {{ lower .Kind }}-example spec: ` + +var admissionControllerTemplate = ` +{{.BoilerPlate}} + +package {{ lower .Kind }}admission + +import ( + aggregatedadmission "{{.Repo}}/plugin/admission" + aggregatedinformerfactory "{{.Repo}}/pkg/client/informers_generated/externalversions" + aggregatedclientset "{{.Repo}}/pkg/client/clientset_generated/clientset" + genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/apiserver/pkg/admission" +) + +var _ admission.Interface = &{{ lower .Kind }}Plugin{} +var _ admission.MutationInterface = &{{ lower .Kind }}Plugin{} +var _ admission.ValidationInterface = &{{ lower .Kind }}Plugin{} +var _ genericadmissioninitializer.WantsExternalKubeInformerFactory = &{{ lower .Kind }}Plugin{} +var _ genericadmissioninitializer.WantsExternalKubeClientSet = &{{ lower .Kind }}Plugin{} +var _ aggregatedadmission.WantsAggregatedResourceInformerFactory = &{{ lower .Kind }}Plugin{} +var _ aggregatedadmission.WantsAggregatedResourceClientSet = &{{ lower .Kind }}Plugin{} + +func New{{ .Kind }}Plugin() *{{ lower .Kind }}Plugin { + return &{{ lower .Kind }}Plugin{ + Handler: admission.NewHandler(admission.Create, admission.Update), + } +} + +type {{ lower .Kind }}Plugin struct { + *admission.Handler +} + +func (p *{{ lower .Kind }}Plugin) ValidateInitialization() error { + return nil +} + +func (p *{{ lower .Kind }}Plugin) Admit(a admission.Attributes) error { + return nil +} + +func (p *{{ lower .Kind }}Plugin) Validate(a admission.Attributes) error { + return nil +} + +func (p *{{ lower .Kind }}Plugin) SetAggregatedResourceInformerFactory(aggregatedinformerfactory.SharedInformerFactory) {} + +func (p *{{ lower .Kind }}Plugin) SetAggregatedResourceClientSet(aggregatedclientset.Interface) {} + +func (p *{{ lower .Kind }}Plugin) SetExternalKubeInformerFactory(informers.SharedInformerFactory) {} + +func (p *{{ lower .Kind }}Plugin) SetExternalKubeClientSet(kubernetes.Interface) {} +` + +var admissionControllerInitializerTemplate = ` +{{.BoilerPlate}} + +package admission + +import ( + aggregatedclientset "{{.Repo}}/pkg/client/clientset_generated/clientset" + aggregatedinformerfactory "{{.Repo}}/pkg/client/informers_generated/externalversions" + "k8s.io/apiserver/pkg/admission" +) + +// WantsAggregatedResourceClientSet defines a function which sets external ClientSet for admission plugins that need it +type WantsAggregatedResourceClientSet interface { + SetAggregatedResourceClientSet(aggregatedclientset.Interface) + admission.InitializationValidator +} + +// WantsAggregatedResourceInformerFactory defines a function which sets InformerFactory for admission plugins that need it +type WantsAggregatedResourceInformerFactory interface { + SetAggregatedResourceInformerFactory(aggregatedinformerfactory.SharedInformerFactory) + admission.InitializationValidator +} + +// New creates an instance of admission plugins initializer. +func New( + clientset aggregatedclientset.Interface, + informers aggregatedinformerfactory.SharedInformerFactory, +) pluginInitializer { + return pluginInitializer{ + aggregatedResourceClient: clientset, + aggregatedResourceInformers: informers, + } +} + +type pluginInitializer struct { + aggregatedResourceClient aggregatedclientset.Interface + aggregatedResourceInformers aggregatedinformerfactory.SharedInformerFactory +} + +func (i pluginInitializer) Initialize(plugin admission.Interface) { + if wants, ok := plugin.(WantsAggregatedResourceClientSet); ok { + wants.SetAggregatedResourceClientSet(i.aggregatedResourceClient) + } + if wants, ok := plugin.(WantsAggregatedResourceInformerFactory); ok { + wants.SetAggregatedResourceInformerFactory(i.aggregatedResourceInformers) + } +} + +` diff --git a/example/cmd/apiserver/main.go b/example/cmd/apiserver/main.go index c6a83c2b0f..2ba0ee7dae 100644 --- a/example/cmd/apiserver/main.go +++ b/example/cmd/apiserver/main.go @@ -20,8 +20,17 @@ import ( "github.com/kubernetes-incubator/apiserver-builder-alpha/example/pkg/apis" "github.com/kubernetes-incubator/apiserver-builder-alpha/example/pkg/openapi" "github.com/kubernetes-incubator/apiserver-builder-alpha/pkg/cmd/server" + + // import the package to install custom admission controllers and custom admission initializers + _ "github.com/kubernetes-incubator/apiserver-builder-alpha/example/plugin/admission/install" ) func main() { - server.StartApiServer("/registry/sample.kubernetes.io", apis.GetAllApiBuilders(), openapi.GetOpenAPIDefinitions, "Api", "v0") + server.StartApiServer( + "/registry/sample.kubernetes.io", + apis.GetAllApiBuilders(), + openapi.GetOpenAPIDefinitions, + "Api", + "v0", + ) } diff --git a/example/plugin/admission/deepone/admission.go b/example/plugin/admission/deepone/admission.go new file mode 100644 index 0000000000..b103802aa5 --- /dev/null +++ b/example/plugin/admission/deepone/admission.go @@ -0,0 +1,70 @@ + +/* +Copyright YEAR 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 deeponeadmission + +import ( + "fmt" + aggregatedadmission "github.com/kubernetes-incubator/apiserver-builder-alpha/example/plugin/admission" + aggregatedinformerfactory "github.com/kubernetes-incubator/apiserver-builder-alpha/example/pkg/client/informers_generated/externalversions" + aggregatedclientset "github.com/kubernetes-incubator/apiserver-builder-alpha/example/pkg/client/clientset_generated/clientset" + genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/apiserver/pkg/admission" +) + +var _ admission.Interface = &deeponePlugin{} +var _ admission.MutationInterface = &deeponePlugin{} +var _ admission.ValidationInterface = &deeponePlugin{} +var _ genericadmissioninitializer.WantsExternalKubeInformerFactory = &deeponePlugin{} +var _ genericadmissioninitializer.WantsExternalKubeClientSet = &deeponePlugin{} +var _ aggregatedadmission.WantsAggregatedResourceInformerFactory = &deeponePlugin{} +var _ aggregatedadmission.WantsAggregatedResourceClientSet = &deeponePlugin{} + +func NewDeepOnePlugin() *deeponePlugin { + return &deeponePlugin{ + Handler: admission.NewHandler(admission.Create, admission.Update), + } +} + +type deeponePlugin struct { + *admission.Handler +} + +func (p *deeponePlugin) ValidateInitialization() error { + return nil +} + +func (p *deeponePlugin) Admit(a admission.Attributes) error { + fmt.Println("admitting deepones") + return nil +} + +func (p *deeponePlugin) Validate(a admission.Attributes) error { + return nil +} + +func (p *deeponePlugin) SetAggregatedResourceInformerFactory(aggregatedinformerfactory.SharedInformerFactory) {} + +func (p *deeponePlugin) SetAggregatedResourceClientSet(aggregatedclientset.Interface) {} + +func (p *deeponePlugin) SetExternalKubeInformerFactory(informers.SharedInformerFactory) {} + +func (p *deeponePlugin) SetExternalKubeClientSet(kubernetes.Interface) {} diff --git a/example/plugin/admission/festival/admission.go b/example/plugin/admission/festival/admission.go new file mode 100644 index 0000000000..abb9ca046d --- /dev/null +++ b/example/plugin/admission/festival/admission.go @@ -0,0 +1,70 @@ + +/* +Copyright YEAR 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 festivaladmission + +import ( + "fmt" + aggregatedadmission "github.com/kubernetes-incubator/apiserver-builder-alpha/example/plugin/admission" + aggregatedinformerfactory "github.com/kubernetes-incubator/apiserver-builder-alpha/example/pkg/client/informers_generated/externalversions" + aggregatedclientset "github.com/kubernetes-incubator/apiserver-builder-alpha/example/pkg/client/clientset_generated/clientset" + genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + "k8s.io/apiserver/pkg/admission" +) + +var _ admission.Interface = &festivalPlugin{} +var _ admission.MutationInterface = &festivalPlugin{} +var _ admission.ValidationInterface = &festivalPlugin{} +var _ genericadmissioninitializer.WantsExternalKubeInformerFactory = &festivalPlugin{} +var _ genericadmissioninitializer.WantsExternalKubeClientSet = &festivalPlugin{} +var _ aggregatedadmission.WantsAggregatedResourceInformerFactory = &festivalPlugin{} +var _ aggregatedadmission.WantsAggregatedResourceClientSet = &festivalPlugin{} + +func NewFestivalPlugin() *festivalPlugin { + return &festivalPlugin{ + Handler: admission.NewHandler(admission.Create, admission.Update), + } +} + +type festivalPlugin struct { + *admission.Handler +} + +func (p *festivalPlugin) ValidateInitialization() error { + return nil +} + +func (p *festivalPlugin) Admit(a admission.Attributes) error { + fmt.Println("admitting festivals") + return nil +} + +func (p *festivalPlugin) Validate(a admission.Attributes) error { + return nil +} + +func (p *festivalPlugin) SetAggregatedResourceInformerFactory(aggregatedinformerfactory.SharedInformerFactory) {} + +func (p *festivalPlugin) SetAggregatedResourceClientSet(aggregatedclientset.Interface) {} + +func (p *festivalPlugin) SetExternalKubeInformerFactory(informers.SharedInformerFactory) {} + +func (p *festivalPlugin) SetExternalKubeClientSet(kubernetes.Interface) {} diff --git a/example/plugin/admission/initializer.go b/example/plugin/admission/initializer.go new file mode 100644 index 0000000000..b0c3c5e801 --- /dev/null +++ b/example/plugin/admission/initializer.go @@ -0,0 +1,64 @@ + +/* +Copyright YEAR 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 admission + +import ( + aggregatedclientset "github.com/kubernetes-incubator/apiserver-builder-alpha/example/pkg/client/clientset_generated/clientset" + aggregatedinformerfactory "github.com/kubernetes-incubator/apiserver-builder-alpha/example/pkg/client/informers_generated/externalversions" + "k8s.io/apiserver/pkg/admission" +) + +// WantsAggregatedResourceClientSet defines a function which sets external ClientSet for admission plugins that need it +type WantsAggregatedResourceClientSet interface { + SetAggregatedResourceClientSet(aggregatedclientset.Interface) + admission.InitializationValidator +} + +// WantsAggregatedResourceInformerFactory defines a function which sets InformerFactory for admission plugins that need it +type WantsAggregatedResourceInformerFactory interface { + SetAggregatedResourceInformerFactory(aggregatedinformerfactory.SharedInformerFactory) + admission.InitializationValidator +} + +// New creates an instance of admission plugins initializer. +func New( + clientset aggregatedclientset.Interface, + informers aggregatedinformerfactory.SharedInformerFactory, +) pluginInitializer { + return pluginInitializer{ + aggregatedResourceClient: clientset, + aggregatedResourceInformers: informers, + } +} + +type pluginInitializer struct { + aggregatedResourceClient aggregatedclientset.Interface + aggregatedResourceInformers aggregatedinformerfactory.SharedInformerFactory +} + +func (i pluginInitializer) Initialize(plugin admission.Interface) { + if wants, ok := plugin.(WantsAggregatedResourceClientSet); ok { + wants.SetAggregatedResourceClientSet(i.aggregatedResourceClient) + } + if wants, ok := plugin.(WantsAggregatedResourceInformerFactory); ok { + wants.SetAggregatedResourceInformerFactory(i.aggregatedResourceInformers) + } +} + diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 74ee684a42..f1eb80353f 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -17,14 +17,13 @@ limitations under the License. package apiserver import ( + "github.com/kubernetes-incubator/apiserver-builder-alpha/pkg/builders" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/version" genericapiserver "k8s.io/apiserver/pkg/server" - - "github.com/kubernetes-incubator/apiserver-builder-alpha/pkg/builders" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" ) type Installer struct { @@ -52,12 +51,16 @@ func (c *Config) Init() *Config { &metav1.APIResourceList{}, ) + // initialize admission controllers + return c } type Config struct { RecommendedConfig *genericapiserver.RecommendedConfig InsecureServingInfo *genericapiserver.DeprecatedInsecureServingInfo + + PostStartHooks map[string]genericapiserver.PostStartHookFunc } // Server contains state for a Kubernetes cluster master/api server. @@ -96,6 +99,10 @@ func (c completedConfig) New() (*Server, error) { return nil, err } + for hookName, hook := range c.PostStartHooks { + genericServer.AddPostStartHookOrDie(hookName, hook) + } + s := &Server{ GenericAPIServer: genericServer, } diff --git a/pkg/cmd/server/admission.go b/pkg/cmd/server/admission.go new file mode 100644 index 0000000000..89780f1ada --- /dev/null +++ b/pkg/cmd/server/admission.go @@ -0,0 +1,12 @@ +package server + +import ( + "k8s.io/apiserver/pkg/admission" + genericapiserver "k8s.io/apiserver/pkg/server" + "k8s.io/client-go/rest" +) + +var ( + AggregatedAdmissionInitializerGetter func(config *rest.Config) (admission.PluginInitializer, genericapiserver.PostStartHookFunc) + AggregatedAdmissionPlugins = make(map[string]admission.Interface) +) diff --git a/pkg/cmd/server/start.go b/pkg/cmd/server/start.go index 42c52a262e..d291eff0a7 100644 --- a/pkg/cmd/server/start.go +++ b/pkg/cmd/server/start.go @@ -21,6 +21,8 @@ import ( "flag" "fmt" "io" + "k8s.io/apiserver/pkg/admission" + "k8s.io/client-go/kubernetes/scheme" "net/http" "os" @@ -113,6 +115,10 @@ func NewCommandStartServer(etcdPath string, out, errOut io.Writer, builders []*b stopCh <-chan struct{}, title, version string, tweakConfigFuncs ...func(apiServer *apiserver.Config) error) (*cobra.Command, *ServerOptions) { o := NewServerOptions(etcdPath, out, errOut, builders) + for pluginName := range AggregatedAdmissionPlugins { + o.RecommendedOptions.Admission.RecommendedPluginOrder = append(o.RecommendedOptions.Admission.RecommendedPluginOrder, pluginName) + } + // Support overrides cmd := &cobra.Command{ Short: "Launch an API server", @@ -200,6 +206,12 @@ func (o ServerOptions) Config(tweakConfigFuncs ...func(config *apiserver.Config) return nil, err } + // TODO(yue9944882): for backward-compatibility, a loopback client is optional in the server. But if the client is + // missing, server will have to lose the following additional functionalities: + // - all admission controllers: almost all admission controllers relies on injecting loopback client or loopback + // informers. + // - delegated authentication/authorization: the server will not be able to request kube-apiserver for delegated + // authn/authz apis. loopbackClientOptional := true loopbackKubeConfig, kubeInformerFactory, err := o.buildLoopback() if loopbackClientOptional { @@ -215,6 +227,16 @@ func (o ServerOptions) Config(tweakConfigFuncs ...func(config *apiserver.Config) } } + var insecureServingInfo *genericapiserver.DeprecatedInsecureServingInfo + if err := o.InsecureServingOptions.ApplyTo(&insecureServingInfo, &serverConfig.LoopbackClientConfig); err != nil { + return nil, err + } + config := &apiserver.Config{ + RecommendedConfig: serverConfig, + InsecureServingInfo: insecureServingInfo, + PostStartHooks: make(map[string]genericapiserver.PostStartHookFunc), + } + if o.RunDelegatedAuth { err := applyOptions( &serverConfig.Config, @@ -224,21 +246,35 @@ func (o ServerOptions) Config(tweakConfigFuncs ...func(config *apiserver.Config) func(cfg *genericapiserver.Config) error { return o.RecommendedOptions.Authorization.ApplyTo(&cfg.Authorization) }, + func(cfg *genericapiserver.Config) error { + if kubeInformerFactory != nil { + pluginInitializers := []admission.PluginInitializer{} + if AggregatedAdmissionInitializerGetter != nil { + initializer, postStartHook := AggregatedAdmissionInitializerGetter(loopbackKubeConfig) + pluginInitializers = append(pluginInitializers, initializer) + config.PostStartHooks["aggregated-resource-informer"] = postStartHook + } else { + klog.Warning("skip admission controller initialization because no custom admission controllers are installed") + } + for name, plugin := range AggregatedAdmissionPlugins { + plugin := plugin + o.RecommendedOptions.Admission.Plugins.Register(name, func(config io.Reader) (admission.Interface, error) { + // TODO(yue9944882): support admission controller config file reading + return plugin, nil + }) + } + return o.RecommendedOptions.Admission.ApplyTo(cfg, kubeInformerFactory, loopbackKubeConfig, scheme.Scheme, pluginInitializers...) + } else { + klog.Info("skip admission controller initialization because `--kubeconfig` is not specified") + } + return nil + }, ) if err != nil { return nil, err } } - var insecureServingInfo *genericapiserver.DeprecatedInsecureServingInfo - if err := o.InsecureServingOptions.ApplyTo(&insecureServingInfo, &serverConfig.LoopbackClientConfig); err != nil { - return nil, err - } - - config := &apiserver.Config{ - RecommendedConfig: serverConfig, - InsecureServingInfo: insecureServingInfo, - } for _, tweakConfigFunc := range tweakConfigFuncs { if err := tweakConfigFunc(config); err != nil { return nil, err @@ -252,11 +288,13 @@ func (o *ServerOptions) buildLoopback() (*rest.Config, informers.SharedInformerF var err error // TODO(yue9944882): protobuf serialization? if len(o.RecommendedOptions.CoreAPI.CoreAPIKubeconfigPath) == 0 { + klog.Infof("loading in-cluster loopback client...") loopbackConfig, err = rest.InClusterConfig() if err != nil { return nil, nil, err } } else { + klog.Infof("loading out-of-cluster loopback client according to `--kubeconfig` settings...") loopbackConfig, err = clientcmd.BuildConfigFromFlags("", o.RecommendedOptions.CoreAPI.CoreAPIKubeconfigPath) if err != nil { return nil, nil, err