Skip to content

Commit

Permalink
generate dummy admission controller when specifying `--admission-cont…
Browse files Browse the repository at this point in the history
…roller`

extend Config fields w/ admission controller and post-start hooks
  • Loading branch information
yue9944882 committed Jun 26, 2019
1 parent 8a7491d commit 93b61fc
Show file tree
Hide file tree
Showing 10 changed files with 529 additions and 20 deletions.
101 changes: 101 additions & 0 deletions cmd/apiregister-gen/generators/admission_generator.go
Original file line number Diff line number Diff line change
@@ -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 "<repo>/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
}
}
`
9 changes: 7 additions & 2 deletions cmd/apiregister-gen/generators/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
143 changes: 138 additions & 5 deletions cmd/apiserver-boot/boot/create/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
var kindName string
var resourceName string
var nonNamespacedKind bool
var generateAdmissionController bool

var createResourceCmd = &cobra.Command{
Use: "resource",
Expand All @@ -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)
}
Expand Down Expand Up @@ -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,
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
}
}
`
11 changes: 10 additions & 1 deletion example/cmd/apiserver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
}
70 changes: 70 additions & 0 deletions example/plugin/admission/deepone/admission.go
Original file line number Diff line number Diff line change
@@ -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) {}
Loading

0 comments on commit 93b61fc

Please sign in to comment.