Skip to content

Commit 1afe5ad

Browse files
author
Mengqi Yu
committed
✨ scaffold the webhook builder
add a new command `kubebuilder create webhook` to scaffold the webhooks. --defaulting, --imperative-validation and --converison can be used to scaffold mutating, validating and conversion webhooks.
1 parent b18a4e3 commit 1afe5ad

24 files changed

+665
-174
lines changed

cmd/api.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ func newAPICommand() *cobra.Command {
112112
}
113113

114114
apiCmd := &cobra.Command{
115-
Use: "create api",
115+
Use: "api",
116116
Short: "Scaffold a Kubernetes API",
117117
Long: `Scaffold a Kubernetes API by creating a Resource definition and / or a Controller.
118118

cmd/create.go

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/spf13/cobra"
23+
)
24+
25+
func newCreateCmd() *cobra.Command {
26+
cmd := &cobra.Command{
27+
Use: "create",
28+
Short: "Scaffold a Kubernetes API or webhook.",
29+
Long: `Scaffold a Kubernetes API or webhook.`,
30+
Run: func(cmd *cobra.Command, args []string) {
31+
fmt.Println("Coming soon.")
32+
},
33+
}
34+
cmd.AddCommand(
35+
newAPICommand(),
36+
newWebhookV2Cmd(),
37+
)
38+
return cmd
39+
}

cmd/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func main() {
115115

116116
rootCmd.AddCommand(
117117
newInitProjectCmd(),
118-
newAPICommand(),
118+
newCreateCmd(),
119119
version.NewVersionCmd(),
120120
newDocsCmd(),
121121
newVendorUpdateCmd(),

cmd/webhook.go cmd/webhook_v1.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ This command is only available for v1 scaffolding project.
5959

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

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

cmd/webhook_v2.go

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"fmt"
21+
"log"
22+
"os"
23+
"path/filepath"
24+
"strings"
25+
26+
"github.com/gobuffalo/flect"
27+
"github.com/spf13/cobra"
28+
29+
"sigs.k8s.io/kubebuilder/pkg/scaffold"
30+
"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
31+
"sigs.k8s.io/kubebuilder/pkg/scaffold/project"
32+
"sigs.k8s.io/kubebuilder/pkg/scaffold/v1/resource"
33+
resourcev2 "sigs.k8s.io/kubebuilder/pkg/scaffold/v2"
34+
"sigs.k8s.io/kubebuilder/pkg/scaffold/v2/webhook"
35+
)
36+
37+
func newWebhookV2Cmd() *cobra.Command {
38+
o := webhookV2Options{}
39+
40+
cmd := &cobra.Command{
41+
Use: "webhook",
42+
Short: "Scaffold a webhook for an API resource.",
43+
Long: `Scaffold a webhook for an API resource. You can choose to scaffold defaulting, validating and (or) conversion webhooks.`,
44+
Example: ` # Create defaulting and validating webhooks for CRD of group crew, version v1 and kind FirstMate.
45+
kubebuilder create webhook --group crew --version v1 --kind FirstMate --defaulting --programmatic-validation
46+
47+
# Create conversion webhook for CRD of group crew, version v1 and kind FirstMate.
48+
kubebuilder create webhook --group crew --version v1 --kind FirstMate --conversion
49+
`,
50+
Run: func(cmd *cobra.Command, args []string) {
51+
dieIfNoProject()
52+
53+
projectInfo, err := scaffold.LoadProjectFile("PROJECT")
54+
if err != nil {
55+
log.Fatalf("failed to read the PROJECT file: %v", err)
56+
}
57+
58+
if projectInfo.Version != project.Version2 {
59+
fmt.Printf("kubebuilder webhook is for project version: 2, the version of this project is: %s \n", projectInfo.Version)
60+
os.Exit(1)
61+
}
62+
63+
if !o.defaulting && !o.validation && !o.conversion {
64+
fmt.Printf("kubebuilder webhook requires at least one of --defaulting, --programmatic-validation and --conversion to be true")
65+
os.Exit(1)
66+
}
67+
68+
if len(o.res.Resource) == 0 {
69+
o.res.Resource = flect.Pluralize(strings.ToLower(o.res.Kind))
70+
}
71+
72+
fmt.Println("Writing scaffold for you to edit...")
73+
fmt.Println(filepath.Join("api", o.res.Version,
74+
fmt.Sprintf("%s_webhook.go", strings.ToLower(o.res.Kind))))
75+
if o.conversion {
76+
fmt.Println(`Webhook server has been set up for you.
77+
You need to implement the conversion.Hub and conversion.Convertible interfaces for your CRD types.`)
78+
}
79+
webhookScaffolder := &webhook.Webhook{
80+
Resource: o.res,
81+
Defaulting: o.defaulting,
82+
Validating: o.validation,
83+
}
84+
err = (&scaffold.Scaffold{}).Execute(
85+
input.Options{},
86+
webhookScaffolder,
87+
)
88+
if err != nil {
89+
fmt.Printf("error scaffolding webhook: %v", err)
90+
os.Exit(1)
91+
}
92+
93+
err = (&resourcev2.Main{}).Update(
94+
&resourcev2.MainUpdateOptions{
95+
Project: &projectInfo,
96+
WireResource: false,
97+
WireController: false,
98+
WireWebhook: true,
99+
Resource: o.res,
100+
})
101+
if err != nil {
102+
fmt.Printf("error updating main.go: %v", err)
103+
os.Exit(1)
104+
}
105+
106+
},
107+
}
108+
o.res = gvkForFlags(cmd.Flags())
109+
cmd.Flags().BoolVar(&o.defaulting, "defaulting", false,
110+
"if set, scaffold the defaulting webhook")
111+
cmd.Flags().BoolVar(&o.validation, "programmatic-validation", false,
112+
"if set, scaffold the validating webhook")
113+
cmd.Flags().BoolVar(&o.validation, "conversion", false,
114+
"if set, scaffold the conversion webhook")
115+
116+
return cmd
117+
}
118+
119+
// webhookOptions represents commandline options for scaffolding a webhook.
120+
type webhookV2Options struct {
121+
res *resource.Resource
122+
defaulting bool
123+
validation bool
124+
conversion bool
125+
}

generated_golden.sh

+2
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ scaffold_test_project() {
6464

6565
$kb init --project-version $version --domain testproject.org --license apache2 --owner "The Kubernetes authors"
6666
$kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --make=false
67+
$kb create webhook --group crew --version v1 --kind Captain --defaulting
6768
$kb create api --group crew --version v1 --kind FirstMate --controller=true --resource=true --make=false
69+
$kb create webhook --group crew --version v1 --kind FirstMate --programmatic-validation
6870
# TODO(droot): Adding a second group is a valid test case and kubebuilder is expected to report an error in this case. It
6971
# doesn't do that currently so leaving it commented so that we can enable it later.
7072
# $kb create api --group ship --version v1beta1 --kind Frigate --example=false --controller=true --resource=true --make=false

pkg/scaffold/util/util.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
Copyright 2019 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package util
18+
19+
import (
20+
"fmt"
21+
"os"
22+
"path"
23+
"path/filepath"
24+
"strings"
25+
26+
"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
27+
"sigs.k8s.io/kubebuilder/pkg/scaffold/v1/resource"
28+
)
29+
30+
func GetResourceInfo(r *resource.Resource, in input.Input) (resourcePackage, groupDomain string) {
31+
// Use the k8s.io/api package for core resources
32+
coreGroups := map[string]string{
33+
"apps": "",
34+
"admission": "k8s.io",
35+
"admissionregistration": "k8s.io",
36+
"auditregistration": "k8s.io",
37+
"apiextensions": "k8s.io",
38+
"authentication": "k8s.io",
39+
"authorization": "k8s.io",
40+
"autoscaling": "",
41+
"batch": "",
42+
"certificates": "k8s.io",
43+
"coordination": "k8s.io",
44+
"core": "",
45+
"events": "k8s.io",
46+
"extensions": "",
47+
"imagepolicy": "k8s.io",
48+
"networking": "k8s.io",
49+
"node": "k8s.io",
50+
"metrics": "k8s.io",
51+
"policy": "",
52+
"rbac.authorization": "k8s.io",
53+
"scheduling": "k8s.io",
54+
"setting": "k8s.io",
55+
"storage": "k8s.io",
56+
}
57+
resourcePath := filepath.Join("api", r.Version, fmt.Sprintf("%s_types.go", strings.ToLower(r.Kind)))
58+
if _, err := os.Stat(resourcePath); os.IsNotExist(err) {
59+
if domain, found := coreGroups[r.Group]; found {
60+
// TODO: support apiextensions.k8s.io and metrics.k8s.io.
61+
// apiextensions.k8s.io is in k8s.io/apiextensions-apiserver/pkg/apis/apiextensions
62+
// metrics.k8s.io is in k8s.io/metrics/pkg/apis/metrics
63+
resourcePackage := path.Join("k8s.io", "api", r.Group)
64+
groupDomain = r.Group
65+
if domain != "" {
66+
groupDomain = r.Group + "." + domain
67+
}
68+
return resourcePackage, groupDomain
69+
}
70+
// TODO: need to support '--resource-pkg-path' flag for specifying resourcePath
71+
}
72+
return path.Join(in.Repo, "api"), r.Group + "." + in.Domain
73+
}

pkg/scaffold/v2/controller.go

+2-36
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,13 @@ limitations under the License.
1717
package v2
1818

1919
import (
20-
"fmt"
21-
"os"
22-
"path"
2320
"path/filepath"
2421
"strings"
2522

2623
"github.com/gobuffalo/flect"
2724

2825
"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
26+
"sigs.k8s.io/kubebuilder/pkg/scaffold/util"
2927
"sigs.k8s.io/kubebuilder/pkg/scaffold/v1/resource"
3028
)
3129

@@ -49,7 +47,7 @@ type Controller struct {
4947
// GetInput implements input.File
5048
func (a *Controller) GetInput() (input.Input, error) {
5149

52-
a.ResourcePackage, a.GroupDomain = getResourceInfo(a.Resource, a.Input)
50+
a.ResourcePackage, a.GroupDomain = util.GetResourceInfo(a.Resource, a.Input)
5351

5452
if a.Plural == "" {
5553
a.Plural = flect.Pluralize(strings.ToLower(a.Resource.Kind))
@@ -64,38 +62,6 @@ func (a *Controller) GetInput() (input.Input, error) {
6462
return a.Input, nil
6563
}
6664

67-
func getResourceInfo(r *resource.Resource, in input.Input) (resourcePackage, groupDomain string) {
68-
// Use the k8s.io/api package for core resources
69-
coreGroups := map[string]string{
70-
"apps": "",
71-
"admissionregistration": "k8s.io",
72-
"apiextensions": "k8s.io",
73-
"authentication": "k8s.io",
74-
"autoscaling": "",
75-
"batch": "",
76-
"certificates": "k8s.io",
77-
"core": "",
78-
"extensions": "",
79-
"metrics": "k8s.io",
80-
"policy": "",
81-
"rbac.authorization": "k8s.io",
82-
"storage": "k8s.io",
83-
}
84-
resourcePath := filepath.Join("api", r.Version, fmt.Sprintf("%s_types.go", strings.ToLower(r.Kind)))
85-
if _, err := os.Stat(resourcePath); os.IsNotExist(err) {
86-
if domain, found := coreGroups[r.Group]; found {
87-
resourcePackage := path.Join("k8s.io", "api", r.Group)
88-
groupDomain = r.Group
89-
if domain != "" {
90-
groupDomain = r.Group + "." + domain
91-
}
92-
return resourcePackage, groupDomain
93-
}
94-
// TODO: need to support '--resource-pkg-path' flag for specifying resourcePath
95-
}
96-
return path.Join(in.Repo, "api"), r.Group + "." + in.Domain
97-
}
98-
9965
var controllerTemplate = `{{ .Boilerplate }}
10066
10167
package controllers

pkg/scaffold/v2/controller_suitetest.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
"github.com/gobuffalo/flect"
2525
"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
26+
"sigs.k8s.io/kubebuilder/pkg/scaffold/util"
2627
"sigs.k8s.io/kubebuilder/pkg/scaffold/v1/resource"
2728
"sigs.k8s.io/kubebuilder/pkg/scaffold/v2/internal"
2829
)
@@ -130,7 +131,7 @@ var _ = AfterSuite(func() {
130131
// adding import paths and code setup for new types.
131132
func (a *ControllerSuiteTest) Update() error {
132133

133-
a.ResourcePackage, a.GroupDomain = getResourceInfo(a.Resource, a.Input)
134+
a.ResourcePackage, a.GroupDomain = util.GetResourceInfo(a.Resource, a.Input)
134135
if a.Plural == "" {
135136
a.Plural = flect.Pluralize(strings.ToLower(a.Resource.Kind))
136137
}

pkg/scaffold/v2/gomod.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@ module {{ .Repo }}
4343
go 1.12
4444
4545
require (
46-
sigs.k8s.io/controller-runtime v0.2.0-beta.2
46+
sigs.k8s.io/controller-runtime v0.2.0-beta.4
4747
)
4848
`

pkg/scaffold/v2/group.go

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ package {{ .Resource.Version }}
5757
import (
5858
"sigs.k8s.io/controller-runtime/pkg/scheme"
5959
"k8s.io/apimachinery/pkg/runtime/schema"
60+
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
6061
)
6162
6263
var (

0 commit comments

Comments
 (0)