Skip to content

Commit

Permalink
Merge pull request #816 from mengqiy/webhookbuilder
Browse files Browse the repository at this point in the history
✨ scaffold the webhook builder
  • Loading branch information
k8s-ci-robot committed Jul 3, 2019
2 parents b18a4e3 + f0a987e commit 285b0bc
Show file tree
Hide file tree
Showing 24 changed files with 668 additions and 182 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
26 changes: 21 additions & 5 deletions cmd/docs.go → cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,32 @@ package main

import (
"fmt"

"github.com/spf13/cobra"
)

func newDocsCmd() *cobra.Command {
return &cobra.Command{
Use: "docs",
Short: "Generate API reference docs. Coming soon",
Long: `Generate API reference docs. Coming soon`,
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(),
)

foundProject, version := getProjectVersion()
// It add webhook v2 command in the following 2 cases:
// - There are no PROJECT file found.
// - version == 2 is found in the PROJECT file.
if !foundProject || version == "2" {
cmd.AddCommand(
newWebhookV2Cmd(),
)
}

return cmd
}
26 changes: 22 additions & 4 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,18 @@ func main() {

rootCmd.AddCommand(
newInitProjectCmd(),
newAPICommand(),
newCreateCmd(),
version.NewVersionCmd(),
newDocsCmd(),
newVendorUpdateCmd(),
newAlphaCommand(),
)

foundProject, version := getProjectVersion()
if foundProject && version == "1" {
rootCmd.AddCommand(
newAlphaCommand(),
newVendorUpdateCmd(),
)
}

if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -181,3 +186,16 @@ After the scaffold is written, api will run make on the project.
},
}
}

// getProjectVersion tries to load PROJECT file and returns if the file exist
// and the version string
func getProjectVersion() (bool, string) {
if _, err := os.Stat("PROJECT"); os.IsNotExist(err) {
return false, ""
}
projectInfo, err := scaffold.LoadProjectFile("PROJECT")
if err != nil {
log.Fatalf("failed to read the PROJECT file: %v", err)
}
return true, projectInfo.Version
}
2 changes: 1 addition & 1 deletion cmd/webhook.go → cmd/webhook_v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,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)
os.Exit(0)
os.Exit(1)
}

fmt.Println("Writing scaffold for you to edit...")
Expand Down
125 changes: 125 additions & 0 deletions cmd/webhook_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
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, validating and (or) conversion webhooks.`,
Example: ` # Create defaulting and validating webhooks for CRD of group crew, version v1 and kind FirstMate.
kubebuilder create webhook --group crew --version v1 --kind FirstMate --defaulting --programmatic-validation
# Create conversion webhook for CRD of group crew, version v1 and kind FirstMate.
kubebuilder create webhook --group crew --version v1 --kind FirstMate --conversion
`,
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(1)
}

if !o.defaulting && !o.validation && !o.conversion {
fmt.Printf("kubebuilder webhook requires at least one of --defaulting, --programmatic-validation and --conversion to be true")
os.Exit(1)
}

if len(o.res.Resource) == 0 {
o.res.Resource = flect.Pluralize(strings.ToLower(o.res.Kind))
}

fmt.Println("Writing scaffold for you to edit...")
fmt.Println(filepath.Join("api", o.res.Version,
fmt.Sprintf("%s_webhook.go", strings.ToLower(o.res.Kind))))
if o.conversion {
fmt.Println(`Webhook server has been set up for you.
You need to implement the conversion.Hub and conversion.Convertible interfaces for your CRD types.`)
}
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)
}

},
}
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")
cmd.Flags().BoolVar(&o.validation, "conversion", false,
"if set, scaffold the conversion webhook")

return cmd
}

// webhookOptions represents commandline options for scaffolding a webhook.
type webhookV2Options struct {
res *resource.Resource
defaulting bool
validation bool
conversion 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 v0.2.0-beta.4
)
`
Loading

0 comments on commit 285b0bc

Please sign in to comment.