Skip to content

Commit 722b905

Browse files
committed
Plugin model; --pattern=addon flag that generates an addon operator
We implement the initial plugin model, starting with an in-process implementation that can support out-of-process later. As plugins do not yet form part of the stable interface for kubebuilder, we only enable them when the KUBEBUILDER_ENABLE_PLUGINS env var is set. We implement an initial plugin for addons: `--pattern=addon` creates a controller & resource that follow the style of https://github.com/kubernetes-sigs/kubebuilder-declarative-pattern This style of declarative addon operators is being investigated in the cluster-addons subject, with code at https://github.com/kubernetes-sigs/addon-operators
1 parent d3a8151 commit 722b905

24 files changed

+694
-69
lines changed

cmd/api.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ import (
2222
"log"
2323
"os"
2424
"os/exec"
25+
"strings"
2526

2627
"github.com/spf13/cobra"
2728
flag "github.com/spf13/pflag"
2829

2930
"sigs.k8s.io/kubebuilder/cmd/util"
3031
"sigs.k8s.io/kubebuilder/pkg/scaffold"
3132
"sigs.k8s.io/kubebuilder/pkg/scaffold/v1/resource"
33+
"sigs.k8s.io/kubebuilder/plugins/addon"
3234
)
3335

3436
type apiOptions struct {
@@ -37,6 +39,9 @@ type apiOptions struct {
3739

3840
// runMake indicates whether to run make or not after scaffolding APIs
3941
runMake bool
42+
43+
// pattern indicates that we should use a plugin to build according to a pattern
44+
pattern string
4045
}
4146

4247
func (o *apiOptions) bindCmdFlags(cmd *cobra.Command) {
@@ -48,6 +53,10 @@ func (o *apiOptions) bindCmdFlags(cmd *cobra.Command) {
4853
cmd.Flags().BoolVar(&o.apiScaffolder.DoController, "controller", true,
4954
"if set, generate the controller without prompting the user")
5055
o.controllerFlag = cmd.Flag("controller")
56+
if os.Getenv("KUBEBUILDER_ENABLE_PLUGINS") != "" {
57+
cmd.Flags().StringVar(&o.pattern, "pattern", "",
58+
"generates an API following an extension pattern (addon)")
59+
}
5160
o.apiScaffolder.Resource = resourceForFlags(cmd.Flags())
5261
}
5362

@@ -67,6 +76,17 @@ func resourceForFlags(f *flag.FlagSet) *resource.Resource {
6776
func (o *apiOptions) runAddAPI() {
6877
dieIfNoProject()
6978

79+
switch strings.ToLower(o.pattern) {
80+
case "":
81+
// Default pattern
82+
83+
case "addon":
84+
o.apiScaffolder.Plugins = append(o.apiScaffolder.Plugins, &addon.Plugin{})
85+
86+
default:
87+
log.Fatalf("unknown pattern %q", o.pattern)
88+
}
89+
7090
reader := bufio.NewReader(os.Stdin)
7191
if !o.resourceFlag.Changed {
7292
fmt.Println("Create Resource [y/n]")

cmd/vendor_update.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"log"
2121

2222
"github.com/spf13/cobra"
23+
"sigs.k8s.io/kubebuilder/pkg/model"
2324
"sigs.k8s.io/kubebuilder/pkg/scaffold"
2425
"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
2526
"sigs.k8s.io/kubebuilder/pkg/scaffold/project"
@@ -35,7 +36,9 @@ kubebuilder update vendor
3536
`,
3637
Run: func(cmd *cobra.Command, args []string) {
3738
dieIfNoProject()
38-
err := (&scaffold.Scaffold{}).Execute(input.Options{},
39+
err := (&scaffold.Scaffold{}).Execute(
40+
&model.Universe{},
41+
input.Options{},
3942
&project.GopkgToml{})
4043
if err != nil {
4144
log.Fatalf("error updating vendor dependecies %v", err)

cmd/webhook_v1.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/spf13/cobra"
2828
flag "github.com/spf13/pflag"
2929

30+
"sigs.k8s.io/kubebuilder/pkg/model"
3031
"sigs.k8s.io/kubebuilder/pkg/scaffold"
3132
"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
3233
"sigs.k8s.io/kubebuilder/pkg/scaffold/project"
@@ -68,7 +69,9 @@ This command is only available for v1 scaffolding project.
6869
o.res.Resource = flect.Pluralize(strings.ToLower(o.res.Kind))
6970
}
7071

71-
err = (&scaffold.Scaffold{}).Execute(input.Options{},
72+
err = (&scaffold.Scaffold{}).Execute(
73+
&model.Universe{},
74+
input.Options{},
7275
&manager.Webhook{},
7376
&webhook.AdmissionHandler{Resource: o.res, Config: webhook.Config{Server: o.server, Type: o.webhookType, Operations: o.operations}},
7477
&webhook.AdmissionWebhookBuilder{Resource: o.res, Config: webhook.Config{Server: o.server, Type: o.webhookType, Operations: o.operations}},

cmd/webhook_v2.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/gobuffalo/flect"
2727
"github.com/spf13/cobra"
2828

29+
"sigs.k8s.io/kubebuilder/pkg/model"
2930
"sigs.k8s.io/kubebuilder/pkg/scaffold"
3031
"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
3132
"sigs.k8s.io/kubebuilder/pkg/scaffold/project"
@@ -82,6 +83,7 @@ You need to implement the conversion.Hub and conversion.Convertible interfaces f
8283
Validating: o.validation,
8384
}
8485
err = (&scaffold.Scaffold{}).Execute(
86+
&model.Universe{},
8587
input.Options{},
8688
webhookScaffolder,
8789
)

pkg/model/types.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package model
2+
3+
import (
4+
"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
5+
)
6+
7+
// Universe describes the entire state of file generation
8+
type Universe struct {
9+
Boilerplate string `json:"boilerplate,omitempty"`
10+
11+
Resource *Resource `json:"resource,omitempty"`
12+
13+
Files []*File `json:"files,omitempty"`
14+
}
15+
16+
// Resource describes the resource currently being generated
17+
// TODO: Just use the resource type?
18+
type Resource struct {
19+
// Namespaced is true if the resource is namespaced
20+
Namespaced bool `json:"namespaces,omitempty"`
21+
22+
// Group is the API Group. Does not contain the domain.
23+
Group string `json:"group,omitempty"`
24+
25+
// Version is the API version - e.g. v1beta1
26+
Version string `json:"version,omitempty"`
27+
28+
// Kind is the API Kind.
29+
Kind string `json:"kind,omitempty"`
30+
31+
// Plural is the plural lowercase of Kind.
32+
Plural string `json:"plural,omitempty"`
33+
34+
// Resource is the API Resource.
35+
Resource string `json:"resource,omitempty"`
36+
37+
// ResourcePackage is the go package of the Resource
38+
GoPackage string `json:"goPackage,omitempty"`
39+
40+
// GroupDomain is the Group + "." + Domain for the Resource
41+
GroupDomain string `json:"groupDomain,omitempty"`
42+
}
43+
44+
// File describes a file that will be written
45+
type File struct {
46+
// Path is the file to write
47+
Path string `json:"path,omitempty"`
48+
49+
// Contents is the generated output
50+
Contents string `json:"contents,omitempty"`
51+
52+
// TODO: Move input.IfExistsAction into model
53+
// IfExistsAction determines what to do if the file exists
54+
IfExistsAction input.IfExistsAction `json:"ifExistsAction,omitempty"`
55+
}

pkg/scaffold/api.go

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ import (
2121
"path/filepath"
2222
"strings"
2323

24+
"github.com/gobuffalo/flect"
25+
"sigs.k8s.io/kubebuilder/pkg/model"
2426
"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
2527
"sigs.k8s.io/kubebuilder/pkg/scaffold/project"
28+
"sigs.k8s.io/kubebuilder/pkg/scaffold/util"
2629
"sigs.k8s.io/kubebuilder/pkg/scaffold/v1/controller"
2730
resourcev1 "sigs.k8s.io/kubebuilder/pkg/scaffold/v1/resource"
2831
resourcev2 "sigs.k8s.io/kubebuilder/pkg/scaffold/v2"
@@ -34,6 +37,9 @@ import (
3437
type API struct {
3538
scaffold *Scaffold
3639

40+
// Plugins is the list of plugins we should allow to transform our generated scaffolding
41+
Plugins []Plugin
42+
3743
Resource *resourcev1.Resource
3844

3945
project *input.ProjectFile
@@ -60,6 +66,7 @@ func (api *API) Validate() error {
6066
if api.Resource.Kind == "" {
6167
return fmt.Errorf("missing kind information for resource")
6268
}
69+
6370
return nil
6471
}
6572

@@ -89,6 +96,23 @@ func (api *API) Scaffold() error {
8996
}
9097
}
9198

99+
func (api *API) buildUniverse() *model.Universe {
100+
resource := &model.Resource{
101+
Namespaced: api.Resource.Namespaced,
102+
Group: api.Resource.Group,
103+
Version: api.Resource.Version,
104+
Kind: api.Resource.Kind,
105+
Resource: api.Resource.Resource,
106+
Plural: flect.Pluralize(strings.ToLower(api.Resource.Kind)),
107+
}
108+
109+
resource.GoPackage, resource.GroupDomain = util.GetResourceInfo(api.Resource, api.project.Repo, api.project.Domain)
110+
111+
return &model.Universe{
112+
Resource: resource,
113+
}
114+
}
115+
92116
func (api *API) scaffoldV1() error {
93117
r := api.Resource
94118

@@ -98,7 +122,7 @@ func (api *API) scaffoldV1() error {
98122
fmt.Println(filepath.Join("pkg", "apis", r.Group, r.Version,
99123
fmt.Sprintf("%s_types_test.go", strings.ToLower(r.Kind))))
100124

101-
err := (&Scaffold{}).Execute(input.Options{},
125+
err := (&Scaffold{}).Execute(api.buildUniverse(), input.Options{},
102126
&resourcev1.Register{Resource: r},
103127
&resourcev1.Types{Resource: r},
104128
&resourcev1.VersionSuiteTest{Resource: r},
@@ -125,7 +149,7 @@ func (api *API) scaffoldV1() error {
125149
fmt.Println(filepath.Join("pkg", "controller", strings.ToLower(r.Kind),
126150
fmt.Sprintf("%s_controller_test.go", strings.ToLower(r.Kind))))
127151

128-
err := (&Scaffold{}).Execute(input.Options{},
152+
err := (&Scaffold{}).Execute(api.buildUniverse(), input.Options{},
129153
&controller.Controller{Resource: r},
130154
&controller.AddController{Resource: r},
131155
&controller.Test{Resource: r},
@@ -150,8 +174,7 @@ func (api *API) scaffoldV2() error {
150174
fmt.Println(filepath.Join("api", r.Version,
151175
fmt.Sprintf("%s_types.go", strings.ToLower(r.Kind))))
152176

153-
err := (&Scaffold{}).Execute(
154-
input.Options{},
177+
files := []input.File{
155178
&resourcev2.Types{
156179
Input: input.Input{
157180
Path: filepath.Join("api", r.Version, fmt.Sprintf("%s_types.go", strings.ToLower(r.Kind))),
@@ -161,13 +184,18 @@ func (api *API) scaffoldV2() error {
161184
&resourcev2.CRDSample{Resource: r},
162185
&crdv2.EnableWebhookPatch{Resource: r},
163186
&crdv2.EnableCAInjectionPatch{Resource: r},
164-
)
165-
if err != nil {
187+
}
188+
189+
scaffold := &Scaffold{
190+
Plugins: api.Plugins,
191+
}
192+
193+
if err := scaffold.Execute(api.buildUniverse(), input.Options{}, files...); err != nil {
166194
return fmt.Errorf("error scaffolding APIs: %v", err)
167195
}
168196

169197
crdKustomization := &crdv2.Kustomization{Resource: r}
170-
err = (&Scaffold{}).Execute(
198+
err := (&Scaffold{}).Execute(api.buildUniverse(),
171199
input.Options{},
172200
crdKustomization,
173201
&crdv2.KustomizeConfig{},
@@ -203,6 +231,7 @@ func (api *API) scaffoldV2() error {
203231
ctrlScaffolder := &resourcev2.Controller{Resource: r}
204232
testsuiteScaffolder := &resourcev2.ControllerSuiteTest{Resource: r}
205233
err := (&Scaffold{}).Execute(
234+
api.buildUniverse(),
206235
input.Options{},
207236
testsuiteScaffolder,
208237
ctrlScaffolder,

pkg/scaffold/project.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"strings"
2525

2626
"sigs.k8s.io/kubebuilder/cmd/util"
27+
"sigs.k8s.io/kubebuilder/pkg/model"
2728
"sigs.k8s.io/kubebuilder/pkg/scaffold/input"
2829
"sigs.k8s.io/kubebuilder/pkg/scaffold/project"
2930
scaffoldv1 "sigs.k8s.io/kubebuilder/pkg/scaffold/v1"
@@ -77,6 +78,10 @@ func (p *V1Project) EnsureDependencies() (bool, error) {
7778
return true, c.Run()
7879
}
7980

81+
func (p *V1Project) buildUniverse() *model.Universe {
82+
return &model.Universe{}
83+
}
84+
8085
func (p *V1Project) Scaffold() error {
8186
p.Project.Version = project.Version1
8287

@@ -96,6 +101,7 @@ func (p *V1Project) Scaffold() error {
96101
}
97102

98103
err = s.Execute(
104+
p.buildUniverse(),
99105
input.Options{ProjectPath: projectInput.Path, BoilerplatePath: bpInput.Path},
100106
&p.Project,
101107
&p.Boilerplate,
@@ -109,6 +115,7 @@ func (p *V1Project) Scaffold() error {
109115

110116
s = &Scaffold{}
111117
return s.Execute(
118+
p.buildUniverse(),
112119
input.Options{ProjectPath: projectInput.Path, BoilerplatePath: bpInput.Path},
113120
&project.GitIgnore{},
114121
&project.KustomizeRBAC{},
@@ -147,6 +154,10 @@ func (p *V2Project) EnsureDependencies() (bool, error) {
147154
return true, c.Run()
148155
}
149156

157+
func (p *V2Project) buildUniverse() *model.Universe {
158+
return &model.Universe{}
159+
}
160+
150161
func (p *V2Project) Scaffold() error {
151162
p.Project.Version = project.Version2
152163

@@ -166,6 +177,7 @@ func (p *V2Project) Scaffold() error {
166177
}
167178

168179
err = s.Execute(
180+
p.buildUniverse(),
169181
input.Options{ProjectPath: projectInput.Path, BoilerplatePath: bpInput.Path},
170182
&p.Project,
171183
&p.Boilerplate,
@@ -179,6 +191,7 @@ func (p *V2Project) Scaffold() error {
179191

180192
s = &Scaffold{}
181193
return s.Execute(
194+
p.buildUniverse(),
182195
input.Options{ProjectPath: projectInput.Path, BoilerplatePath: bpInput.Path},
183196
&project.GitIgnore{},
184197
&metricsauthv2.KustomizePrometheusMetricsPatch{},

0 commit comments

Comments
 (0)