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 create 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 28, 2019
1 parent b18a4e3 commit b34e3e9
Show file tree
Hide file tree
Showing 24 changed files with 661 additions and 176 deletions.
2 changes: 1 addition & 1 deletion cmd/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func newAPICommand() *cobra.Command {
}

apiCmd := &cobra.Command{
Use: "create api",
Use: "api",
Short: "Scaffold a Kubernetes API",
Long: `Scaffold a Kubernetes API by creating a Resource definition and / or a Controller.
Expand Down
39 changes: 39 additions & 0 deletions cmd/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
Copyright 2018 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"

"github.com/spf13/cobra"
)

func newCreateCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "Scaffold a Kubernetes API or webhook.",
Long: `Scaffold a Kubernetes API or webhook.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Coming soon.")
},
}
cmd.AddCommand(
newAPICommand(),
newWebhookV2Cmd(),
)
return cmd
}
2 changes: 1 addition & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func main() {

rootCmd.AddCommand(
newInitProjectCmd(),
newAPICommand(),
newCreateCmd(),
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("you should use kubebuilder create webhook command\n")
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 for an API resource.",
Long: `Scaffold a webhook for an API resource. 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 set, scaffold the defaulting webhook")
cmd.Flags().BoolVar(&o.validation, "programmatic-validation", false,
"if set, 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 create webhook --group crew --version v1 --kind Captain --defaulting
$kb create api --group crew --version v1 --kind FirstMate --controller=true --resource=true --make=false
$kb create 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
}
38 changes: 2 additions & 36 deletions pkg/scaffold/v2/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@ limitations under the License.
package v2

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 @@ -49,7 +47,7 @@ type Controller struct {
// GetInput implements input.File
func (a *Controller) GetInput() (input.Input, error) {

a.ResourcePackage, a.GroupDomain = getResourceInfo(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 @@ -64,38 +62,6 @@ func (a *Controller) GetInput() (input.Input, error) {
return a.Input, nil
}

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": "",
"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",
}
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 {
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
}

var controllerTemplate = `{{ .Boilerplate }}
package controllers
Expand Down
3 changes: 2 additions & 1 deletion pkg/scaffold/v2/controller_suitetest.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"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"
"sigs.k8s.io/kubebuilder/pkg/scaffold/v2/internal"
)
Expand Down Expand Up @@ -130,7 +131,7 @@ var _ = AfterSuite(func() {
// adding import paths and code setup for new types.
func (a *ControllerSuiteTest) Update() error {

a.ResourcePackage, a.GroupDomain = getResourceInfo(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 Down
2 changes: 1 addition & 1 deletion pkg/scaffold/v2/gomod.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@ module {{ .Repo }}
go 1.12
require (
sigs.k8s.io/controller-runtime v0.2.0-beta.2
sigs.k8s.io/controller-runtime da24e0fa23ff83f615764d71e7ec3eef4b893c0f
)
`
1 change: 1 addition & 0 deletions pkg/scaffold/v2/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ package {{ .Resource.Version }}
import (
"sigs.k8s.io/controller-runtime/pkg/scheme"
"k8s.io/apimachinery/pkg/runtime/schema"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
)
var (
Expand Down
Loading

0 comments on commit b34e3e9

Please sign in to comment.