Skip to content

Commit

Permalink
✨ scaffold the webhook builder
Browse files Browse the repository at this point in the history
add a new command `kubebuilder webhook` to scaffold the webhooks.
--defaulting and --imperative-validation can be used to scaffold mutating and validating webhooks.
  • Loading branch information
Mengqi Yu committed Jun 26, 2019
1 parent 27c1cc0 commit 09126a1
Show file tree
Hide file tree
Showing 20 changed files with 542 additions and 158 deletions.
1 change: 1 addition & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ func main() {
rootCmd.AddCommand(
newInitProjectCmd(),
newAPICommand(),
newWebhookV2Cmd(),
version.NewVersionCmd(),
newDocsCmd(),
newVendorUpdateCmd(),
Expand Down
6 changes: 3 additions & 3 deletions cmd/webhook.go → cmd/webhook_v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ func newWebhookCmd() *cobra.Command {

cmd := &cobra.Command{
Use: "webhook",
Short: "Scaffold a webhook server",
Long: `Scaffold a webhook server if there is no existing server.
Short: "Scaffold a webhook server for v1 scaffolding.",
Long: `Scaffold a webhook server for v1 scaffolding. if there is no existing server.
Scaffolds webhook handlers based on group, version, kind and other user inputs.
This command is only available for v1 scaffolding project.
`,
Expand All @@ -58,7 +58,7 @@ This command is only available for v1 scaffolding project.
}

if projectInfo.Version != project.Version1 {
fmt.Printf("webhook scaffolding is not supported for this project version: %s \n", projectInfo.Version)
fmt.Printf("kubebuilder alpha webhook is for project version: 1, the version of this project is: %s \n", projectInfo.Version)
os.Exit(0)
}

Expand Down
119 changes: 119 additions & 0 deletions cmd/webhook_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
Copyright 2019 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 (
"fmt"
"log"
"os"
"path/filepath"
"strings"

"github.com/gobuffalo/flect"
"github.com/spf13/cobra"

"sigs.k8s.io/kubebuilder/pkg/scaffold"
"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
"sigs.k8s.io/kubebuilder/pkg/scaffold/project"
"sigs.k8s.io/kubebuilder/pkg/scaffold/v1/resource"
resourcev2 "sigs.k8s.io/kubebuilder/pkg/scaffold/v2"
"sigs.k8s.io/kubebuilder/pkg/scaffold/v2/webhook"
)

func newWebhookV2Cmd() *cobra.Command {
o := webhookV2Options{}

cmd := &cobra.Command{
Use: "webhook",
Short: "Scaffold a webhook using v2 scaffolding.",
Long: `Scaffold a webhook using v2 scaffolding. You can choose to scaffold defaulting and (or) validating webhooks.`,
Example: ` # Create defaulting and validating webhooks for CRD of group crew, version v1 and kind FirstMate.
kubebuilder webhook --group crew --version v1 --kind FirstMate --defaulting --programmatic-validation
`,
Run: func(cmd *cobra.Command, args []string) {
dieIfNoProject()

projectInfo, err := scaffold.LoadProjectFile("PROJECT")
if err != nil {
log.Fatalf("failed to read the PROJECT file: %v", err)
}

if projectInfo.Version != project.Version2 {
fmt.Printf("kubebuilder webhook is for project version: 2, the version of this project is: %s \n", projectInfo.Version)
os.Exit(0)
}

if !o.defaulting && !o.validation {
fmt.Printf("kubebuilder webhook requires at least one of --defaulting and --programmatic-validation")
os.Exit(0)
}

fmt.Println(filepath.Join("api", fmt.Sprintf("%s_webhook.go", strings.ToLower(o.res.Kind))))
webhookScaffolder := &webhook.Webhook{
Resource: o.res,
Defaulting: o.defaulting,
Validating: o.validation,
}
err = (&scaffold.Scaffold{}).Execute(
input.Options{},
webhookScaffolder,
)
if err != nil {
fmt.Printf("error scaffolding webhook: %v", err)
os.Exit(1)
}

err = (&resourcev2.Main{}).Update(
&resourcev2.MainUpdateOptions{
Project: &projectInfo,
WireResource: false,
WireController: false,
WireWebhook: true,
Resource: o.res,
})
if err != nil {
fmt.Printf("error updating main.go: %v", err)
os.Exit(1)
}

if projectInfo.Version != project.Version1 {
fmt.Printf("webhook scaffolding is not supported for this project version: %s \n", projectInfo.Version)
os.Exit(0)
}

fmt.Println("Writing scaffold for you to edit...")

if len(o.res.Resource) == 0 {
o.res.Resource = flect.Pluralize(strings.ToLower(o.res.Kind))
}
},
}
o.res = gvkForFlags(cmd.Flags())
cmd.Flags().BoolVar(&o.defaulting, "defaulting", false,
"if scaffold the defaulting webhook")
cmd.Flags().BoolVar(&o.validation, "programmatic-validation", false,
"if scaffold the validating webhook")

return cmd
}

// webhookOptions represents commandline options for scaffolding a webhook.
type webhookV2Options struct {
res *resource.Resource
defaulting bool
validation bool
}
2 changes: 2 additions & 0 deletions generated_golden.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ scaffold_test_project() {

$kb init --project-version $version --domain testproject.org --license apache2 --owner "The Kubernetes authors"
$kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --make=false
$kb webhook --group crew --version v1 --kind Captain --defaulting
$kb create api --group crew --version v1 --kind FirstMate --controller=true --resource=true --make=false
$kb webhook --group crew --version v1 --kind FirstMate --programmatic-validation
# TODO(droot): Adding a second group is a valid test case and kubebuilder is expected to report an error in this case. It
# doesn't do that currently so leaving it commented so that we can enable it later.
# $kb create api --group ship --version v1beta1 --kind Frigate --example=false --controller=true --resource=true --make=false
Expand Down
73 changes: 73 additions & 0 deletions pkg/scaffold/util/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
Copyright 2019 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 util

import (
"fmt"
"os"
"path"
"path/filepath"
"strings"

"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
"sigs.k8s.io/kubebuilder/pkg/scaffold/v1/resource"
)

func GetResourceInfo(r *resource.Resource, in input.Input) (resourcePackage, groupDomain string) {
// Use the k8s.io/api package for core resources
coreGroups := map[string]string{
"apps": "",
"admission": "k8s.io",
"admissionregistration": "k8s.io",
"auditregistration": "k8s.io",
"apiextensions": "k8s.io",
"authentication": "k8s.io",
"authorization": "k8s.io",
"autoscaling": "",
"batch": "",
"certificates": "k8s.io",
"coordination": "k8s.io",
"core": "",
"events": "k8s.io",
"extensions": "",
"imagepolicy": "k8s.io",
"networking": "k8s.io",
"node": "k8s.io",
"metrics": "k8s.io",
"policy": "",
"rbac.authorization": "k8s.io",
"scheduling": "k8s.io",
"setting": "k8s.io",
"storage": "k8s.io",
}
resourcePath := filepath.Join("api", r.Version, fmt.Sprintf("%s_types.go", strings.ToLower(r.Kind)))
if _, err := os.Stat(resourcePath); os.IsNotExist(err) {
if domain, found := coreGroups[r.Group]; found {
// TODO: support apiextensions.k8s.io and metrics.k8s.io.
// apiextensions.k8s.io is in k8s.io/apiextensions-apiserver/pkg/apis/apiextensions
// metrics.k8s.io is in k8s.io/metrics/pkg/apis/metrics
resourcePackage := path.Join("k8s.io", "api", r.Group)
groupDomain = r.Group
if domain != "" {
groupDomain = r.Group + "." + domain
}
return resourcePackage, groupDomain
}
// TODO: need to support '--resource-pkg-path' flag for specifying resourcePath
}
return path.Join(in.Repo, "api"), r.Group + "." + in.Domain
}
41 changes: 3 additions & 38 deletions pkg/scaffold/v1/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ limitations under the License.
package controller

import (
"fmt"
"os"
"path"
"path/filepath"
"strings"

"github.com/gobuffalo/flect"

"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
"sigs.k8s.io/kubebuilder/pkg/scaffold/util"
"sigs.k8s.io/kubebuilder/pkg/scaffold/v1/resource"
)

Expand All @@ -47,24 +46,7 @@ type Controller struct {

// GetInput implements input.File
func (a *Controller) GetInput() (input.Input, error) {
// Use the k8s.io/api package for core resources
coreGroups := map[string]string{
"apps": "",
"admissionregistration": "k8s.io",
"apiextensions": "k8s.io",
"authentication": "k8s.io",
"autoscaling": "",
"batch": "",
"certificates": "k8s.io",
"core": "",
"extensions": "",
"metrics": "k8s.io",
"policy": "",
"rbac.authorization": "k8s.io",
"storage": "k8s.io",
}

a.ResourcePackage, a.GroupDomain = getResourceInfo(coreGroups, a.Resource, a.Input)
a.ResourcePackage, a.GroupDomain = util.GetResourceInfo(a.Resource, a.Input)

if a.Plural == "" {
a.Plural = flect.Pluralize(strings.ToLower(a.Resource.Kind))
Expand All @@ -80,23 +62,6 @@ func (a *Controller) GetInput() (input.Input, error) {
return a.Input, nil
}

func getResourceInfo(coreGroups map[string]string, r *resource.Resource, in input.Input) (resourcePackage, groupDomain string) {
resourcePath := filepath.Join("pkg", "apis", r.Group, r.Version,
fmt.Sprintf("%s_types.go", strings.ToLower(r.Kind)))
if _, err := os.Stat(resourcePath); os.IsNotExist(err) {
if domain, found := coreGroups[r.Group]; found {
resourcePackage := path.Join("k8s.io", "api")
groupDomain = r.Group
if domain != "" {
groupDomain = r.Group + "." + domain
}
return resourcePackage, groupDomain
}
// TODO: need to support '--resource-pkg-path' flag for specifying resourcePath
}
return path.Join(in.Repo, "pkg", "apis"), r.Group + "." + in.Domain
}

var controllerTemplate = `{{ .Boilerplate }}
package {{ lower .Resource.Kind }}
Expand Down
20 changes: 2 additions & 18 deletions pkg/scaffold/v1/controller/controllertest.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"strings"

"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
"sigs.k8s.io/kubebuilder/pkg/scaffold/util"
"sigs.k8s.io/kubebuilder/pkg/scaffold/v1/resource"
)

Expand All @@ -42,24 +43,7 @@ func (a *Test) GetInput() (input.Input, error) {
strings.ToLower(a.Resource.Kind), strings.ToLower(a.Resource.Kind)+"_controller_test.go")
}

// Use the k8s.io/api package for core resources
coreGroups := map[string]string{
"apps": "",
"admissionregistration": "k8s.io",
"apiextensions": "k8s.io",
"authentication": "k8s.io",
"autoscaling": "",
"batch": "",
"certificates": "k8s.io",
"core": "",
"extensions": "",
"metrics": "k8s.io",
"policy": "",
"rbac.authorization": "k8s.io",
"storage": "k8s.io",
}

a.ResourcePackage, _ = getResourceInfo(coreGroups, a.Resource, a.Input)
a.ResourcePackage, _ = util.GetResourceInfo(a.Resource, a.Input)

a.TemplateBody = controllerTestTemplate
a.Input.IfExistsAction = input.Error
Expand Down
3 changes: 2 additions & 1 deletion pkg/scaffold/v1/webhook/admissionbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"strings"

"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
"sigs.k8s.io/kubebuilder/pkg/scaffold/util"
"sigs.k8s.io/kubebuilder/pkg/scaffold/v1/resource"
)

Expand Down Expand Up @@ -51,7 +52,7 @@ type AdmissionWebhookBuilder struct {

// GetInput implements input.File
func (a *AdmissionWebhookBuilder) GetInput() (input.Input, error) {
a.ResourcePackage, a.GroupDomain = getResourceInfo(coreGroups, a.Resource, a.Input)
a.ResourcePackage, a.GroupDomain = util.GetResourceInfo(a.Resource, a.Input)

if a.Type == "mutating" {
a.Mutating = true
Expand Down
3 changes: 2 additions & 1 deletion pkg/scaffold/v1/webhook/admissionhandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"strings"

"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
"sigs.k8s.io/kubebuilder/pkg/scaffold/util"
"sigs.k8s.io/kubebuilder/pkg/scaffold/v1/resource"
)

Expand Down Expand Up @@ -51,7 +52,7 @@ type AdmissionHandler struct {

// GetInput implements input.File
func (a *AdmissionHandler) GetInput() (input.Input, error) {
a.ResourcePackage, a.GroupDomain = getResourceInfo(coreGroups, a.Resource, a.Input)
a.ResourcePackage, a.GroupDomain = util.GetResourceInfo(a.Resource, a.Input)
a.Type = strings.ToLower(a.Type)
if a.Type == "mutating" {
a.Mutate = true
Expand Down
Loading

0 comments on commit 09126a1

Please sign in to comment.